From a431ca0f8b7f8967e89a35caddf1e41e53eee290 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 2 Nov 2016 11:39:12 +0100 Subject: [PATCH 001/174] Don't execute git hooks if you create branch as part of other change Currently, our procedure for adding a commit requires us to execute `CreateBranchService` before file creation. It's OK, but also we do execute `git hooks` (the `PostReceive` sidekiq job) as part of this process. However, this hook is execute before the file is actually committed, so the ref is updated. Secondly, we do execute a `git hooks` after committing file and updating ref. This results in duplicate `PostReceive` jobs, where the first one is completely invalid. This change makes the branch creation, something that is intermediate step of bigger process (file creation or update, commit cherry pick or revert) to not execute git hooks. --- app/models/repository.rb | 8 ++++++-- app/services/commits/change_service.rb | 2 +- app/services/create_branch_service.rb | 4 ++-- app/services/files/base_service.rb | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 30be7262438..0776c7ccc5d 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -160,14 +160,18 @@ class Repository tags.find { |tag| tag.name == name } end - def add_branch(user, branch_name, target) + def add_branch(user, branch_name, target, with_hooks: true) oldrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name target = commit(target).try(:id) return false unless target - GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do + if with_hooks + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do + update_ref!(ref, target, oldrev) + end + else update_ref!(ref, target, oldrev) end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 1c82599c579..2d4c9788d02 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -55,7 +55,7 @@ module Commits return success if repository.find_branch(new_branch) result = CreateBranchService.new(@project, current_user) - .execute(new_branch, @target_branch, source_project: @source_project) + .execute(new_branch, @target_branch, source_project: @source_project, with_hooks: false) if result[:status] == :error raise ChangeError, "There was an error creating the source branch: #{result[:message]}" diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 757fc35a78f..a6a3461e17b 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,7 +1,7 @@ require_relative 'base_service' class CreateBranchService < BaseService - def execute(branch_name, ref, source_project: @project) + def execute(branch_name, ref, source_project: @project, with_hooks: true) valid_branch = Gitlab::GitRefValidator.validate(branch_name) unless valid_branch @@ -26,7 +26,7 @@ class CreateBranchService < BaseService repository.find_branch(branch_name) else - repository.add_branch(current_user, branch_name, ref) + repository.add_branch(current_user, branch_name, ref, with_hooks: with_hooks) end if new_branch diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 9bd4bd464f7..1802b932e03 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -74,7 +74,7 @@ module Files end def create_target_branch - result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project) + result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project, with_hooks: false) unless result[:status] == :success raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}") From 92aa402882cb9e2badc8213dd88913ad21b49857 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 5 Nov 2016 02:48:58 +0800 Subject: [PATCH 002/174] Add a test to make sure hooks are fire only once when updating a file to a different branch. --- spec/services/files/update_service_spec.rb | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb index d3c37c7820f..6fadee9304b 100644 --- a/spec/services/files/update_service_spec.rb +++ b/spec/services/files/update_service_spec.rb @@ -6,7 +6,10 @@ describe Files::UpdateService do let(:project) { create(:project) } let(:user) { create(:user) } let(:file_path) { 'files/ruby/popen.rb' } - let(:new_contents) { "New Content" } + let(:new_contents) { 'New Content' } + let(:target_branch) { project.default_branch } + let(:last_commit_sha) { nil } + let(:commit_params) do { file_path: file_path, @@ -16,7 +19,7 @@ describe Files::UpdateService do last_commit_sha: last_commit_sha, source_project: project, source_branch: project.default_branch, - target_branch: project.default_branch, + target_branch: target_branch } end @@ -54,18 +57,6 @@ describe Files::UpdateService do end context "when the last_commit_sha is not supplied" do - let(:commit_params) do - { - file_path: file_path, - commit_message: "Update File", - file_content: new_contents, - file_content_encoding: "text", - source_project: project, - source_branch: project.default_branch, - target_branch: project.default_branch, - } - end - it "returns a hash with the :success status " do results = subject.execute @@ -80,5 +71,15 @@ describe Files::UpdateService do expect(results.data).to eq(new_contents) end end + + context 'when target branch is different than source branch' do + let(:target_branch) { "#{project.default_branch}-new" } + + it 'fires hooks only once' do + expect(GitHooksService).to receive(:new).once.and_call_original + + subject.execute + end + end end end From 3128641f7eb93fec0930ebfb83a93dfa5e0b343a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 01:41:14 +0800 Subject: [PATCH 003/174] Revert "Don't execute git hooks if you create branch as part of other change" This reverts commit a431ca0f8b7f8967e89a35caddf1e41e53eee290. --- app/models/repository.rb | 8 ++------ app/services/commits/change_service.rb | 2 +- app/services/create_branch_service.rb | 4 ++-- app/services/files/base_service.rb | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index feaaacd02a9..063dc74021d 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -162,18 +162,14 @@ class Repository tags.find { |tag| tag.name == name } end - def add_branch(user, branch_name, target, with_hooks: true) + def add_branch(user, branch_name, target) oldrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name target = commit(target).try(:id) return false unless target - if with_hooks - GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do - update_ref!(ref, target, oldrev) - end - else + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do update_ref!(ref, target, oldrev) end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 2d4c9788d02..1c82599c579 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -55,7 +55,7 @@ module Commits return success if repository.find_branch(new_branch) result = CreateBranchService.new(@project, current_user) - .execute(new_branch, @target_branch, source_project: @source_project, with_hooks: false) + .execute(new_branch, @target_branch, source_project: @source_project) if result[:status] == :error raise ChangeError, "There was an error creating the source branch: #{result[:message]}" diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index a6a3461e17b..757fc35a78f 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,7 +1,7 @@ require_relative 'base_service' class CreateBranchService < BaseService - def execute(branch_name, ref, source_project: @project, with_hooks: true) + def execute(branch_name, ref, source_project: @project) valid_branch = Gitlab::GitRefValidator.validate(branch_name) unless valid_branch @@ -26,7 +26,7 @@ class CreateBranchService < BaseService repository.find_branch(branch_name) else - repository.add_branch(current_user, branch_name, ref, with_hooks: with_hooks) + repository.add_branch(current_user, branch_name, ref) end if new_branch diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 1802b932e03..9bd4bd464f7 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -74,7 +74,7 @@ module Files end def create_target_branch - result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project, with_hooks: false) + result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project) unless result[:status] == :success raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}") From 0b5a2eef8fa5ff4976f97883b631ec28f0553f6a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 04:02:10 +0800 Subject: [PATCH 004/174] Add `source_branch` option for various git operations If `source_branch` option is passed, and target branch cannot be found, `Repository#update_branch_with_hooks` would try to create a new branch from `source_branch`. This way, we could make changes in the new branch while only firing the hooks once for the changes. Previously, we can only create a new branch first then make changes to the new branch, firing hooks twice. This behaviour is bad for CI. Fixes #7237 --- app/models/repository.rb | 98 +++++++++++++++------ app/services/commits/change_service.rb | 10 +-- app/services/create_branch_service.rb | 22 ++--- app/services/files/base_service.rb | 11 ++- app/services/files/create_dir_service.rb | 9 +- app/services/files/create_service.rb | 11 ++- app/services/files/delete_service.rb | 9 +- app/services/files/multi_service.rb | 3 +- app/services/files/update_service.rb | 3 +- app/services/validate_new_branch_service.rb | 22 +++++ 10 files changed, 145 insertions(+), 53 deletions(-) create mode 100644 app/services/validate_new_branch_service.rb diff --git a/app/models/repository.rb b/app/models/repository.rb index 063dc74021d..b6581486983 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -786,8 +786,12 @@ class Repository @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref } end - def commit_dir(user, path, message, branch, author_email: nil, author_name: nil) - update_branch_with_hooks(user, branch) do |ref| + def commit_dir(user, path, message, branch, + author_email: nil, author_name: nil, source_branch: nil) + update_branch_with_hooks( + user, + branch, + source_branch: source_branch) do |ref| options = { commit: { branch: ref, @@ -802,8 +806,12 @@ class Repository end end - def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil) - update_branch_with_hooks(user, branch) do |ref| + def commit_file(user, path, content, message, branch, update, + author_email: nil, author_name: nil, source_branch: nil) + update_branch_with_hooks( + user, + branch, + source_branch: source_branch) do |ref| options = { commit: { branch: ref, @@ -823,8 +831,13 @@ class Repository end end - def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil) - update_branch_with_hooks(user, branch) do |ref| + def update_file(user, path, content, + branch:, previous_path:, message:, + author_email: nil, author_name: nil, source_branch: nil) + update_branch_with_hooks( + user, + branch, + source_branch: source_branch) do |ref| options = { commit: { branch: ref, @@ -849,8 +862,12 @@ class Repository end end - def remove_file(user, path, message, branch, author_email: nil, author_name: nil) - update_branch_with_hooks(user, branch) do |ref| + def remove_file(user, path, message, branch, + author_email: nil, author_name: nil, source_branch: nil) + update_branch_with_hooks( + user, + branch, + source_branch: source_branch) do |ref| options = { commit: { branch: ref, @@ -868,17 +885,18 @@ class Repository end end - def multi_action(user:, branch:, message:, actions:, author_email: nil, author_name: nil) - update_branch_with_hooks(user, branch) do |ref| + def multi_action(user:, branch:, message:, actions:, + author_email: nil, author_name: nil, source_branch: nil) + update_branch_with_hooks( + user, + branch, + source_branch: source_branch) do |ref| index = rugged.index parents = [] - branch = find_branch(ref) - if branch - last_commit = branch.dereferenced_target - index.read_tree(last_commit.raw_commit.tree) - parents = [last_commit.sha] - end + last_commit = find_branch(ref).dereferenced_target + index.read_tree(last_commit.raw_commit.tree) + parents = [last_commit.sha] actions.each do |action| case action[:action] @@ -967,7 +985,10 @@ class Repository return false unless revert_tree_id - update_branch_with_hooks(user, base_branch) do + update_branch_with_hooks( + user, + base_branch, + source_branch: revert_tree_id) do committer = user_to_committer(user) source_sha = Rugged::Commit.create(rugged, message: commit.revert_message, @@ -984,7 +1005,10 @@ class Repository return false unless cherry_pick_tree_id - update_branch_with_hooks(user, base_branch) do + update_branch_with_hooks( + user, + base_branch, + source_branch: cherry_pick_tree_id) do committer = user_to_committer(user) source_sha = Rugged::Commit.create(rugged, message: commit.message, @@ -1082,11 +1106,11 @@ class Repository fetch_ref(path_to_repo, ref, ref_path) end - def update_branch_with_hooks(current_user, branch) + def update_branch_with_hooks(current_user, branch, source_branch: nil) update_autocrlf_option ref = Gitlab::Git::BRANCH_REF_PREFIX + branch - target_branch = find_branch(branch) + target_branch, new_branch_added = raw_ensure_branch(branch, source_branch) was_empty = empty? # Make commit @@ -1096,7 +1120,7 @@ class Repository raise CommitError.new('Failed to create commit') end - if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? + if rugged.lookup(newrev).parent_ids.empty? oldrev = Gitlab::Git::BLANK_SHA else oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha) @@ -1105,11 +1129,9 @@ class Repository GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do update_ref!(ref, newrev, oldrev) - if was_empty || !target_branch - # If repo was empty expire cache - after_create if was_empty - after_create_branch - end + # If repo was empty expire cache + after_create if was_empty + after_create_branch if was_empty || new_branch_added end newrev @@ -1169,4 +1191,28 @@ class Repository def repository_event(event, tags = {}) Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags)) end + + def raw_ensure_branch(branch_name, source_branch) + old_branch = find_branch(branch_name) + + if old_branch + [old_branch, false] + elsif source_branch + oldrev = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + target = commit(source_branch).try(:id) + + unless target + raise CommitError.new( + "Cannot find branch #{branch_name} nor #{source_branch}") + end + + update_ref!(ref, target, oldrev) + + [find_branch(branch_name), true] + else + raise CommitError.new( + "Cannot find branch #{branch_name} and source_branch is not set") + end + end end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 1c82599c579..04b28cfaed8 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -29,7 +29,7 @@ module Commits tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch) if tree_id - create_target_branch(into) if @create_merge_request + validate_target_branch(into) if @create_merge_request repository.public_send(action, current_user, @commit, into, tree_id) success @@ -50,12 +50,12 @@ module Commits true end - def create_target_branch(new_branch) + def validate_target_branch(new_branch) # Temporary branch exists and contains the change commit - return success if repository.find_branch(new_branch) + return if repository.find_branch(new_branch) - result = CreateBranchService.new(@project, current_user) - .execute(new_branch, @target_branch, source_project: @source_project) + result = ValidateNewBranchService.new(@project, current_user). + execute(new_branch) if result[:status] == :error raise ChangeError, "There was an error creating the source branch: #{result[:message]}" diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 757fc35a78f..f4270a09928 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -2,18 +2,9 @@ require_relative 'base_service' class CreateBranchService < BaseService def execute(branch_name, ref, source_project: @project) - valid_branch = Gitlab::GitRefValidator.validate(branch_name) + failure = validate_new_branch(branch_name) - unless valid_branch - return error('Branch name is invalid') - end - - repository = project.repository - existing_branch = repository.find_branch(branch_name) - - if existing_branch - return error('Branch already exists') - end + return failure if failure new_branch = if source_project != @project repository.fetch_ref( @@ -41,4 +32,13 @@ class CreateBranchService < BaseService def success(branch) super().merge(branch: branch) end + + private + + def validate_new_branch(branch_name) + result = ValidateNewBranchService.new(project, current_user). + execute(branch_name) + + error(result[:message]) if result[:status] == :error + end end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 9bd4bd464f7..6779bd2818a 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -23,9 +23,7 @@ module Files validate # Create new branch if it different from source_branch - if different_branch? - create_target_branch - end + validate_target_branch if different_branch? result = commit if result @@ -73,10 +71,11 @@ module Files end end - def create_target_branch - result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project) + def validate_target_branch + result = ValidateNewBranchService.new(project, current_user). + execute(@target_branch) - unless result[:status] == :success + if result[:status] == :error raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}") end end diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb index d00d78cee7e..c59b3f8c70c 100644 --- a/app/services/files/create_dir_service.rb +++ b/app/services/files/create_dir_service.rb @@ -3,7 +3,14 @@ require_relative "base_service" module Files class CreateDirService < Files::BaseService def commit - repository.commit_dir(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name) + repository.commit_dir( + current_user, + @file_path, + @commit_message, + @target_branch, + author_email: @author_email, + author_name: @author_name, + source_branch: @source_branch) end def validate diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index bf127843d55..6d0a0f2629d 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -3,7 +3,16 @@ require_relative "base_service" module Files class CreateService < Files::BaseService def commit - repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false, author_email: @author_email, author_name: @author_name) + repository.commit_file( + current_user, + @file_path, + @file_content, + @commit_message, + @target_branch, + false, + author_email: @author_email, + author_name: @author_name, + source_branch: @source_branch) end def validate diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index 8b27ad51789..79d592731e9 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -3,7 +3,14 @@ require_relative "base_service" module Files class DeleteService < Files::BaseService def commit - repository.remove_file(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name) + repository.remove_file( + current_user, + @file_path, + @commit_message, + @target_branch, + author_email: @author_email, + author_name: @author_name, + source_branch: @source_branch) end end end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index d28912e1301..0550dec15a6 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -11,7 +11,8 @@ module Files message: @commit_message, actions: params[:actions], author_email: @author_email, - author_name: @author_name + author_name: @author_name, + source_branch: @source_branch ) end diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index c17fdb8d1f1..f3a766ed9fd 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -10,7 +10,8 @@ module Files previous_path: @previous_path, message: @commit_message, author_email: @author_email, - author_name: @author_name) + author_name: @author_name, + source_branch: @source_branch) end private diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb new file mode 100644 index 00000000000..2f61be184ce --- /dev/null +++ b/app/services/validate_new_branch_service.rb @@ -0,0 +1,22 @@ +require_relative 'base_service' + +class ValidateNewBranchService < BaseService + def execute(branch_name) + valid_branch = Gitlab::GitRefValidator.validate(branch_name) + + unless valid_branch + return error('Branch name is invalid') + end + + repository = project.repository + existing_branch = repository.find_branch(branch_name) + + if existing_branch + return error('Branch already exists') + end + + success + rescue GitHooksService::PreReceiveError => ex + error(ex.message) + end +end From 92a438263fafdd7c4163f09e63815dc805cb8d12 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 04:27:21 +0800 Subject: [PATCH 005/174] Fix issues found by rubocop --- app/models/repository.rb | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index b6581486983..beafe9060bf 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -786,7 +786,8 @@ class Repository @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref } end - def commit_dir(user, path, message, branch, + def commit_dir( + user, path, message, branch, author_email: nil, author_name: nil, source_branch: nil) update_branch_with_hooks( user, @@ -806,7 +807,9 @@ class Repository end end - def commit_file(user, path, content, message, branch, update, + # rubocop:disable Metrics/ParameterLists + def commit_file( + user, path, content, message, branch, update, author_email: nil, author_name: nil, source_branch: nil) update_branch_with_hooks( user, @@ -830,8 +833,11 @@ class Repository Gitlab::Git::Blob.commit(raw_repository, options) end end + # rubocop:enable Metrics/ParameterLists - def update_file(user, path, content, + # rubocop:disable Metrics/ParameterLists + def update_file( + user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil, source_branch: nil) update_branch_with_hooks( @@ -861,8 +867,10 @@ class Repository end end end + # rubocop:enable Metrics/ParameterLists - def remove_file(user, path, message, branch, + def remove_file( + user, path, message, branch, author_email: nil, author_name: nil, source_branch: nil) update_branch_with_hooks( user, @@ -885,14 +893,14 @@ class Repository end end - def multi_action(user:, branch:, message:, actions:, + def multi_action( + user:, branch:, message:, actions:, author_email: nil, author_name: nil, source_branch: nil) update_branch_with_hooks( user, branch, source_branch: source_branch) do |ref| index = rugged.index - parents = [] last_commit = find_branch(ref).dereferenced_target index.read_tree(last_commit.raw_commit.tree) From 8fca786bdee6fa23a5e000bf23e65b153fc1bf73 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 04:46:54 +0800 Subject: [PATCH 006/174] They're never referred --- app/models/repository.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index beafe9060bf..b2b5d528840 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -998,7 +998,7 @@ class Repository base_branch, source_branch: revert_tree_id) do committer = user_to_committer(user) - source_sha = Rugged::Commit.create(rugged, + Rugged::Commit.create(rugged, message: commit.revert_message, author: committer, committer: committer, @@ -1018,7 +1018,7 @@ class Repository base_branch, source_branch: cherry_pick_tree_id) do committer = user_to_committer(user) - source_sha = Rugged::Commit.create(rugged, + Rugged::Commit.create(rugged, message: commit.message, author: { email: commit.author_email, From 30d7b5c32cfb97d7c1e57fb8874069077097c89d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 05:29:24 +0800 Subject: [PATCH 007/174] Fix the case when it's a whole new branch --- app/models/repository.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index b2b5d528840..5e7bb309967 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1128,7 +1128,7 @@ class Repository raise CommitError.new('Failed to create commit') end - if rugged.lookup(newrev).parent_ids.empty? + if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? oldrev = Gitlab::Git::BLANK_SHA else oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha) @@ -1219,8 +1219,7 @@ class Repository [find_branch(branch_name), true] else - raise CommitError.new( - "Cannot find branch #{branch_name} and source_branch is not set") + [nil, true] # Empty branch end end end From eddee5fe8770d79c80fdb0d91731f866c14c9b8d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 06:01:54 +0800 Subject: [PATCH 008/174] Make sure we create target branch for cherry/revert --- app/models/repository.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 5e7bb309967..0f3e98db420 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -988,7 +988,8 @@ class Repository end def revert(user, commit, base_branch, revert_tree_id = nil) - source_sha = find_branch(base_branch).dereferenced_target.sha + source_sha = raw_ensure_branch(base_branch, source_commit: commit). + first.dereferenced_target.sha revert_tree_id ||= check_revert_content(commit, base_branch) return false unless revert_tree_id @@ -1008,7 +1009,8 @@ class Repository end def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) - source_sha = find_branch(base_branch).dereferenced_target.sha + source_sha = raw_ensure_branch(base_branch, source_commit: commit). + first.dereferenced_target.sha cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) return false unless cherry_pick_tree_id @@ -1118,7 +1120,8 @@ class Repository update_autocrlf_option ref = Gitlab::Git::BRANCH_REF_PREFIX + branch - target_branch, new_branch_added = raw_ensure_branch(branch, source_branch) + target_branch, new_branch_added = + raw_ensure_branch(branch, source_branch: source_branch) was_empty = empty? # Make commit @@ -1200,19 +1203,19 @@ class Repository Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags)) end - def raw_ensure_branch(branch_name, source_branch) + def raw_ensure_branch(branch_name, source_commit: nil, source_branch: nil) old_branch = find_branch(branch_name) if old_branch [old_branch, false] - elsif source_branch + elsif source_commit || source_branch oldrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - target = commit(source_branch).try(:id) + target = (source_commit || commit(source_branch)).try(:sha) unless target raise CommitError.new( - "Cannot find branch #{branch_name} nor #{source_branch}") + "Cannot find branch #{branch_name} nor #{source_commit.try(:sha) ||source_branch}") end update_ref!(ref, target, oldrev) From 30bcc3de41d86e02c27940e0e8e4a1a82183520e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 06:18:24 +0800 Subject: [PATCH 009/174] Add missing space due to Sublime Text It's Sublime Text's fault. Its word wrapping is not code friendly --- app/models/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 0f3e98db420..89293fa8b4d 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1215,7 +1215,7 @@ class Repository unless target raise CommitError.new( - "Cannot find branch #{branch_name} nor #{source_commit.try(:sha) ||source_branch}") + "Cannot find branch #{branch_name} nor #{source_commit.try(:sha) || source_branch}") end update_ref!(ref, target, oldrev) From 5de74551ace7b6df9fdb2a3c8aa30c836d693728 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 06:55:54 +0800 Subject: [PATCH 010/174] Branch could be nil if it's an empty repo --- app/models/repository.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 89293fa8b4d..c4bdc84348e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -901,10 +901,15 @@ class Repository branch, source_branch: source_branch) do |ref| index = rugged.index + branch_commit = find_branch(ref) - last_commit = find_branch(ref).dereferenced_target - index.read_tree(last_commit.raw_commit.tree) - parents = [last_commit.sha] + parents = if branch_commit + last_commit = branch_commit.dereferenced_target + index.read_tree(last_commit.raw_commit.tree) + [last_commit.sha] + else + [] + end actions.each do |action| case action[:action] From d8fe2fac7e681ddbff3c7a5338f939eb2d540e38 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 07:22:50 +0800 Subject: [PATCH 011/174] Make sure we have the branch on the other project --- app/models/project.rb | 11 +++++++++++ app/services/create_branch_service.rb | 10 +--------- app/services/files/base_service.rb | 10 +++++++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 94aabafce20..1208e5da6fa 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -988,6 +988,17 @@ class Project < ActiveRecord::Base Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) end + def fetch_ref(source_project, branch_name, ref) + repository.fetch_ref( + source_project.repository.path_to_repo, + "refs/heads/#{ref}", + "refs/heads/#{branch_name}" + ) + + repository.after_create_branch + repository.find_branch(branch_name) + end + # Expires various caches before a project is renamed. def expire_caches_before_rename(old_path) repo = Repository.new(old_path, self) diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index f4270a09928..ecb5994fab8 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -7,15 +7,7 @@ class CreateBranchService < BaseService return failure if failure new_branch = if source_project != @project - repository.fetch_ref( - source_project.repository.path_to_repo, - "refs/heads/#{ref}", - "refs/heads/#{branch_name}" - ) - - repository.after_create_branch - - repository.find_branch(branch_name) + @project.fetch_ref(source_project, branch_name, ref) else repository.add_branch(current_user, branch_name, ref) end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 6779bd2818a..fd62246eddb 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -23,7 +23,7 @@ module Files validate # Create new branch if it different from source_branch - validate_target_branch if different_branch? + ensure_target_branch if different_branch? result = commit if result @@ -71,6 +71,14 @@ module Files end end + def ensure_target_branch + validate_target_branch + + if @source_project != project + @project.fetch_ref(@source_project, @target_branch, @source_branch) + end + end + def validate_target_branch result = ValidateNewBranchService.new(project, current_user). execute(@target_branch) From a68a62011d03c15d6116dc1e6dcb9514040a51f5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 07:53:36 +0800 Subject: [PATCH 012/174] Don't pass source_branch if it doesn't exist --- app/controllers/concerns/creates_commit.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index dacb5679dd3..643b61af1b2 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,9 +4,10 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables + source_branch = @ref if @repository.find_branch(@ref) commit_params = @commit_params.merge( source_project: @project, - source_branch: @ref, + source_branch: source_branch, target_branch: @target_branch ) From 39d83f72e7af78d503ef278e22eda25f90322f4b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 08:10:32 +0800 Subject: [PATCH 013/174] Add a few comments to explain implementation detail --- app/models/repository.rb | 2 ++ app/services/files/base_service.rb | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/models/repository.rb b/app/models/repository.rb index c4bdc84348e..4e3e70192ab 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1121,6 +1121,8 @@ class Repository fetch_ref(path_to_repo, ref, ref_path) end + # Whenever `source_branch` is passed, if `branch` doesn't exist, it would + # be created from `source_branch`. def update_branch_with_hooks(current_user, branch, source_branch: nil) update_autocrlf_option diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index fd62246eddb..8689c83d40e 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -75,6 +75,9 @@ module Files validate_target_branch if @source_project != project + # Make sure we have the source_branch in target project, + # and use source_branch as target_branch directly to avoid + # unnecessary source branch in target project. @project.fetch_ref(@source_project, @target_branch, @source_branch) end end From cd22fdd531c151cf1bbc307e52c565e86070fe3b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 08:21:16 +0800 Subject: [PATCH 014/174] @ref might not exist --- app/controllers/concerns/creates_commit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 643b61af1b2..5d11f286e9a 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,7 +4,7 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - source_branch = @ref if @repository.find_branch(@ref) + source_branch = @ref if @ref && @repository.find_branch(@ref) commit_params = @commit_params.merge( source_project: @project, source_branch: source_branch, From 3e69c716f0b81b2c3b863d3c8a5e6346b9978fc1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 15 Nov 2016 20:03:18 +0800 Subject: [PATCH 015/174] Try if branch_exists? would work, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_18424135 --- app/controllers/concerns/creates_commit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 5d11f286e9a..e5c40446314 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,7 +4,7 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - source_branch = @ref if @ref && @repository.find_branch(@ref) + source_branch = @ref if @ref && @repository.branch_exists?(@ref) commit_params = @commit_params.merge( source_project: @project, source_branch: source_branch, From 1a21fa26f6f4ef70157c58329687976fc3f555f7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 2 Nov 2016 11:11:47 +0000 Subject: [PATCH 016/174] Improved ref switcher dropdown performance Closes #18202 --- app/assets/javascripts/project.js | 21 ++++++++++++++++----- app/controllers/projects_controller.rb | 7 +++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 016d999d77e..78ab69206af 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -63,7 +63,8 @@ return $.ajax({ url: $dropdown.data('refs-url'), data: { - ref: $dropdown.data('ref') + ref: $dropdown.data('ref'), + search: term }, dataType: "json" }).done(function(refs) { @@ -72,16 +73,26 @@ }, selectable: true, filterable: true, + filterRemote: true, filterByText: true, fieldName: $dropdown.data('field-name'), renderRow: function(ref) { - var link; + var li = document.createElement('li'); + if (ref.header != null) { - return $('
  • ').addClass('dropdown-header').text(ref.header); + li.className = 'dropdown-header'; + li.textContent = ref.header; } else { - link = $('').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', ref); - return $('
  • ').append(link); + var link = document.createElement('a'); + link.href = '#'; + link.className = ref.name === selected ? 'is-active' : ''; + link.textContent = ref.name; + link.dataset.ref = ref.name; + + li.appendChild(link); } + + return li; }, id: function(obj, $el) { return $el.attr('data-ref'); diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a8a18b4fa16..fe0670aa246 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -267,12 +267,15 @@ class ProjectsController < Projects::ApplicationController end def refs + branches = BranchesFinder.new(@repository, params).execute + options = { - 'Branches' => @repository.branch_names, + 'Branches' => Kaminari.paginate_array(branches).page(params[:page]).per(100), } unless @repository.tag_count.zero? - options['Tags'] = VersionSorter.rsort(@repository.tag_names) + tags = TagsFinder.new(@repository, params).execute + options['Tags'] = Kaminari.paginate_array(tags).page(params[:page]).per(100) end # If reference is commit id - we should add it to branch/tag selectbox From af02f6ae9d500b0174cae106891b626d1dcae351 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 2 Nov 2016 11:31:00 +0000 Subject: [PATCH 017/174] Use cloneNode instead of createElement --- app/assets/javascripts/project.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 78ab69206af..7ac070a9c37 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -54,6 +54,11 @@ }; Project.prototype.initRefSwitcher = function() { + var refListItem = document.createElement('li'), + refLink = document.createElement('a'); + + refLink.href = '#'; + return $('.js-project-refs-dropdown').each(function() { var $dropdown, selected; $dropdown = $(this); @@ -77,21 +82,24 @@ filterByText: true, fieldName: $dropdown.data('field-name'), renderRow: function(ref) { - var li = document.createElement('li'); + var li = refListItem.cloneNode(false); if (ref.header != null) { li.className = 'dropdown-header'; li.textContent = ref.header; } else { - var link = document.createElement('a'); - link.href = '#'; - link.className = ref.name === selected ? 'is-active' : ''; + var link = refLink.cloneNode(false); + + if (ref.name === selected) { + link.className = 'is-active'; + } + link.textContent = ref.name; link.dataset.ref = ref.name; li.appendChild(link); } - + return li; }, id: function(obj, $el) { From ba2089e01711eb3f97770235b86ae9e59862ee8b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 2 Nov 2016 12:15:25 +0000 Subject: [PATCH 018/174] Uses take rather than Kaminari --- app/controllers/projects_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index fe0670aa246..6affadfa0a6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -270,12 +270,12 @@ class ProjectsController < Projects::ApplicationController branches = BranchesFinder.new(@repository, params).execute options = { - 'Branches' => Kaminari.paginate_array(branches).page(params[:page]).per(100), + 'Branches' => branches.take(100), } unless @repository.tag_count.zero? tags = TagsFinder.new(@repository, params).execute - options['Tags'] = Kaminari.paginate_array(tags).page(params[:page]).per(100) + options['Tags'] = tags.take(100) end # If reference is commit id - we should add it to branch/tag selectbox From f9750b4912c6f0e4c7b0b7d213f95223d5d1a593 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 3 Nov 2016 12:16:15 +0000 Subject: [PATCH 019/174] Changed how the data is returned - we only care about the branch/tag name --- app/assets/javascripts/project.js | 4 ++-- app/controllers/projects_controller.rb | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 7ac070a9c37..e7db0620848 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -94,8 +94,8 @@ link.className = 'is-active'; } - link.textContent = ref.name; - link.dataset.ref = ref.name; + link.textContent = ref; + link.dataset.ref = ref; li.appendChild(link); } diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 6affadfa0a6..d7bc31b0718 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -267,14 +267,15 @@ class ProjectsController < Projects::ApplicationController end def refs - branches = BranchesFinder.new(@repository, params).execute + branches = BranchesFinder.new(@repository, params).execute.map(&:name) options = { 'Branches' => branches.take(100), } unless @repository.tag_count.zero? - tags = TagsFinder.new(@repository, params).execute + tags = TagsFinder.new(@repository, params).execute.map(&:name) + options['Tags'] = tags.take(100) end From 08dd831f02daeeefedf65302a9a9575733943357 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 3 Nov 2016 12:25:08 +0000 Subject: [PATCH 020/174] Fixed the active branch selected indicator --- app/assets/javascripts/project.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index e7db0620848..04e18098544 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -90,7 +90,7 @@ } else { var link = refLink.cloneNode(false); - if (ref.name === selected) { + if (ref === selected) { link.className = 'is-active'; } From e95d43dfc5303dcb9e86359049b5f798e8351431 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 7 Nov 2016 16:58:50 +0000 Subject: [PATCH 021/174] Fixed up tests --- .../protected_branches/protected_branch_dropdown.js.es6 | 2 +- spec/features/projects/ref_switcher_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 index e3f226e9a2a..9b551a1ed8c 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 @@ -48,7 +48,7 @@ class ProtectedBranchDropdown { onClickCreateWildcard() { // Refresh the dropdown's data, which ends up calling `getProtectedBranches` this.$dropdown.data('glDropdown').remote.execute(); - this.$dropdown.data('glDropdown').selectRowAtIndex(0); + this.$dropdown.data('glDropdown').selectRowAtIndex(gon.open_branches.length); } getProtectedBranches(term, callback) { diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb index 472491188c9..38fe2d92885 100644 --- a/spec/features/projects/ref_switcher_spec.rb +++ b/spec/features/projects/ref_switcher_spec.rb @@ -17,14 +17,15 @@ feature 'Ref switcher', feature: true, js: true do page.within '.project-refs-form' do input = find('input[type="search"]') - input.set 'expand' + input.set 'binary' + wait_for_ajax input.native.send_keys :down input.native.send_keys :down input.native.send_keys :enter end - expect(page).to have_title 'expand-collapse-files' + expect(page).to have_title 'binary-encoding' end it "user selects ref with special characters" do From 50d58c14cd2cd1b8fb1bb9e4a7a1091b5af90c04 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 21 Nov 2016 17:45:27 +0000 Subject: [PATCH 022/174] Fixed protected branches dropdown --- .../protected_branches/protected_branch_dropdown.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 index 9b551a1ed8c..1ab72ee49e4 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 @@ -48,7 +48,7 @@ class ProtectedBranchDropdown { onClickCreateWildcard() { // Refresh the dropdown's data, which ends up calling `getProtectedBranches` this.$dropdown.data('glDropdown').remote.execute(); - this.$dropdown.data('glDropdown').selectRowAtIndex(gon.open_branches.length); + this.$dropdown.data('glDropdown').selectRowAtIndex(); } getProtectedBranches(term, callback) { From b82f415f09dd67da010a8c08397a13499e70efeb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 22 Nov 2016 18:14:41 +0800 Subject: [PATCH 023/174] Move all branch creation to raw_ensure_branch, and keep it only called in update_branch_with_hooks. --- app/models/project.rb | 11 ---- app/models/repository.rb | 85 ++++++++++++++++++++------- app/services/create_branch_service.rb | 8 +-- app/services/files/base_service.rb | 13 +--- app/services/files/update_service.rb | 1 + 5 files changed, 69 insertions(+), 49 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 5346e18b051..f8a54324341 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -998,17 +998,6 @@ class Project < ActiveRecord::Base Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) end - def fetch_ref(source_project, branch_name, ref) - repository.fetch_ref( - source_project.repository.path_to_repo, - "refs/heads/#{ref}", - "refs/heads/#{branch_name}" - ) - - repository.after_create_branch - repository.find_branch(branch_name) - end - # Expires various caches before a project is renamed. def expire_caches_before_rename(old_path) repo = Repository.new(old_path, self) diff --git a/app/models/repository.rb b/app/models/repository.rb index a029993db7f..9162f494a60 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -784,13 +784,16 @@ class Repository @tags ||= raw_repository.tags end + # rubocop:disable Metrics/ParameterLists def commit_dir( user, path, message, branch, - author_email: nil, author_name: nil, source_branch: nil) + author_email: nil, author_name: nil, + source_branch: nil, source_project: project) update_branch_with_hooks( user, branch, - source_branch: source_branch) do |ref| + source_branch: source_branch, + source_project: source_project) do |ref| options = { commit: { branch: ref, @@ -804,15 +807,18 @@ class Repository raw_repository.mkdir(path, options) end end + # rubocop:enable Metrics/ParameterLists # rubocop:disable Metrics/ParameterLists def commit_file( user, path, content, message, branch, update, - author_email: nil, author_name: nil, source_branch: nil) + author_email: nil, author_name: nil, + source_branch: nil, source_project: project) update_branch_with_hooks( user, branch, - source_branch: source_branch) do |ref| + source_branch: source_branch, + source_project: source_project) do |ref| options = { commit: { branch: ref, @@ -837,11 +843,13 @@ class Repository def update_file( user, path, content, branch:, previous_path:, message:, - author_email: nil, author_name: nil, source_branch: nil) + author_email: nil, author_name: nil, + source_branch: nil, source_project: project) update_branch_with_hooks( user, branch, - source_branch: source_branch) do |ref| + source_branch: source_branch, + source_project: source_project) do |ref| options = { commit: { branch: ref, @@ -867,13 +875,16 @@ class Repository end # rubocop:enable Metrics/ParameterLists + # rubocop:disable Metrics/ParameterLists def remove_file( user, path, message, branch, - author_email: nil, author_name: nil, source_branch: nil) + author_email: nil, author_name: nil, + source_branch: nil, source_project: project) update_branch_with_hooks( user, branch, - source_branch: source_branch) do |ref| + source_branch: source_branch, + source_project: source_project) do |ref| options = { commit: { branch: ref, @@ -890,14 +901,18 @@ class Repository Gitlab::Git::Blob.remove(raw_repository, options) end end + # rubocop:enable Metrics/ParameterLists + # rubocop:disable Metrics/ParameterLists def multi_action( user:, branch:, message:, actions:, - author_email: nil, author_name: nil, source_branch: nil) + author_email: nil, author_name: nil, + source_branch: nil, source_project: project) update_branch_with_hooks( user, branch, - source_branch: source_branch) do |ref| + source_branch: source_branch, + source_project: source_project) do |ref| index = rugged.index branch_commit = find_branch(ref) @@ -942,6 +957,7 @@ class Repository Rugged::Commit.create(rugged, options) end end + # rubocop:enable Metrics/ParameterLists def get_committer_and_author(user, email: nil, name: nil) committer = user_to_committer(user) @@ -991,8 +1007,6 @@ class Repository end def revert(user, commit, base_branch, revert_tree_id = nil) - source_sha = raw_ensure_branch(base_branch, source_commit: commit). - first.dereferenced_target.sha revert_tree_id ||= check_revert_content(commit, base_branch) return false unless revert_tree_id @@ -1000,8 +1014,11 @@ class Repository update_branch_with_hooks( user, base_branch, - source_branch: revert_tree_id) do + source_commit: commit) do + + source_sha = find_branch(base_branch).dereferenced_target.sha committer = user_to_committer(user) + Rugged::Commit.create(rugged, message: commit.revert_message, author: committer, @@ -1012,8 +1029,6 @@ class Repository end def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) - source_sha = raw_ensure_branch(base_branch, source_commit: commit). - first.dereferenced_target.sha cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) return false unless cherry_pick_tree_id @@ -1021,8 +1036,11 @@ class Repository update_branch_with_hooks( user, base_branch, - source_branch: cherry_pick_tree_id) do + source_commit: commit) do + + source_sha = find_branch(base_branch).dereferenced_target.sha committer = user_to_committer(user) + Rugged::Commit.create(rugged, message: commit.message, author: { @@ -1130,12 +1148,19 @@ class Repository # Whenever `source_branch` is passed, if `branch` doesn't exist, it would # be created from `source_branch`. - def update_branch_with_hooks(current_user, branch, source_branch: nil) + def update_branch_with_hooks( + current_user, branch, + source_branch: nil, source_commit: nil, source_project: project) update_autocrlf_option - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch target_branch, new_branch_added = - raw_ensure_branch(branch, source_branch: source_branch) + raw_ensure_branch( + branch, + source_branch: source_branch, + source_project: source_project + ) + + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch was_empty = empty? # Make commit @@ -1244,11 +1269,31 @@ class Repository Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags)) end - def raw_ensure_branch(branch_name, source_commit: nil, source_branch: nil) + def raw_ensure_branch( + branch_name, source_commit: nil, source_branch: nil, source_project: nil) old_branch = find_branch(branch_name) + if source_commit && source_branch + raise ArgumentError, + 'Should only pass either :source_branch or :source_commit, not both' + end + if old_branch [old_branch, false] + elsif project != source_project + unless source_branch + raise ArgumentError, + 'Should also pass :source_branch if' + + ' :source_project is different from current project' + end + + fetch_ref( + source_project.repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch}", + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}" + ) + + [find_branch(branch_name), true] elsif source_commit || source_branch oldrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index ecb5994fab8..b2bc3626c0f 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,16 +1,12 @@ require_relative 'base_service' class CreateBranchService < BaseService - def execute(branch_name, ref, source_project: @project) + def execute(branch_name, ref) failure = validate_new_branch(branch_name) return failure if failure - new_branch = if source_project != @project - @project.fetch_ref(source_project, branch_name, ref) - else - repository.add_branch(current_user, branch_name, ref) - end + new_branch = repository.add_branch(current_user, branch_name, ref) if new_branch success(new_branch) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 8689c83d40e..6779bd2818a 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -23,7 +23,7 @@ module Files validate # Create new branch if it different from source_branch - ensure_target_branch if different_branch? + validate_target_branch if different_branch? result = commit if result @@ -71,17 +71,6 @@ module Files end end - def ensure_target_branch - validate_target_branch - - if @source_project != project - # Make sure we have the source_branch in target project, - # and use source_branch as target_branch directly to avoid - # unnecessary source branch in target project. - @project.fetch_ref(@source_project, @target_branch, @source_branch) - end - end - def validate_target_branch result = ValidateNewBranchService.new(project, current_user). execute(@target_branch) diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index f3a766ed9fd..14e5af4d8c6 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -11,6 +11,7 @@ module Files message: @commit_message, author_email: @author_email, author_name: @author_name, + source_project: @source_project, source_branch: @source_branch) end From 6cd2256c84c9a785a6b15864ab170084fdebf45f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 24 Nov 2016 17:56:26 +0800 Subject: [PATCH 024/174] Should also pass source_commit to raw_ensure_branch --- app/models/repository.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/repository.rb b/app/models/repository.rb index 9162f494a60..276d8829873 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1157,6 +1157,7 @@ class Repository raw_ensure_branch( branch, source_branch: source_branch, + source_commit: source_commit, source_project: source_project ) From e866985be81f54002667117e03dae6680829a462 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 24 Nov 2016 23:35:28 +0800 Subject: [PATCH 025/174] Fix local name from branch to branch_name --- app/models/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 276d8829873..6abe498ebde 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1291,7 +1291,7 @@ class Repository fetch_ref( source_project.repository.path_to_repo, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch}", - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}" + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch_name}" ) [find_branch(branch_name), true] From c916f293cecf3d112f0338aec0a9dc9e3577f17e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 25 Nov 2016 15:07:27 +0800 Subject: [PATCH 026/174] Add some explanation to Repository#update_branch_with_hooks --- app/models/repository.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 6abe498ebde..09e8cc060c7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1146,8 +1146,11 @@ class Repository fetch_ref(path_to_repo, ref, ref_path) end - # Whenever `source_branch` is passed, if `branch` doesn't exist, it would - # be created from `source_branch`. + # Whenever `source_branch` or `source_commit` is passed, if `branch` + # doesn't exist, it would be created from `source_branch` or + # `source_commit`. Should only pass one of them, not both. + # If `source_project` is passed, and the branch doesn't exist, + # it would try to find the source from it instead of current repository. def update_branch_with_hooks( current_user, branch, source_branch: nil, source_commit: nil, source_project: project) From a52dc7cec70ef97b2755fb9cef7d6b489062310c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Dec 2016 03:13:15 +0800 Subject: [PATCH 027/174] Introduce GitOperationService and wrap every git operation inside GitHooksService. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19210942 TODO: Fix tests for update_branch_with_hooks --- app/models/repository.rb | 174 ++++---------------------- app/services/git_operation_service.rb | 168 +++++++++++++++++++++++++ spec/models/repository_spec.rb | 12 +- 3 files changed, 197 insertions(+), 157 deletions(-) create mode 100644 app/services/git_operation_service.rb diff --git a/app/models/repository.rb b/app/models/repository.rb index 6bfa24f6329..491d2ab69b2 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -5,7 +5,7 @@ class Repository attr_accessor :path_with_namespace, :project - class CommitError < StandardError; end + CommitError = Class.new(StandardError) # Methods that cache data from the Git repository. # @@ -64,10 +64,6 @@ class Repository @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo) end - def update_autocrlf_option - raw_repository.autocrlf = :input if raw_repository.autocrlf != :input - end - # Return absolute path to repository def path_to_repo @path_to_repo ||= File.expand_path( @@ -172,54 +168,39 @@ class Repository tags.find { |tag| tag.name == name } end - def add_branch(user, branch_name, target) - oldrev = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - target = commit(target).try(:id) + def add_branch(user, branch_name, ref) + newrev = commit(ref).try(:sha) - return false unless target + return false unless newrev - GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do - update_ref!(ref, target, oldrev) - end + GitOperationService.new(user, self).add_branch(branch_name, newrev) after_create_branch find_branch(branch_name) end def add_tag(user, tag_name, target, message = nil) - oldrev = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::TAG_REF_PREFIX + tag_name - target = commit(target).try(:id) - - return false unless target - + newrev = commit(target).try(:id) options = { message: message, tagger: user_to_committer(user) } if message - GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do |service| - raw_tag = rugged.tags.create(tag_name, target, options) - service.newrev = raw_tag.target_id - end + return false unless newrev + + GitOperationService.new(user, self).add_tag(tag_name, newrev, options) find_tag(tag_name) end def rm_branch(user, branch_name) before_remove_branch - branch = find_branch(branch_name) - oldrev = branch.try(:dereferenced_target).try(:id) - newrev = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do - update_ref!(ref, newrev, oldrev) - end + GitOperationService.new(user, self).rm_branch(branch) after_remove_branch true end + # TODO: why we don't pass user here? def rm_tag(tag_name) before_remove_tag @@ -245,21 +226,6 @@ class Repository false end - def update_ref!(name, newrev, oldrev) - # We use 'git update-ref' because libgit2/rugged currently does not - # offer 'compare and swap' ref updates. Without compare-and-swap we can - # (and have!) accidentally reset the ref to an earlier state, clobbering - # commits. See also https://github.com/libgit2/libgit2/issues/1534. - command = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z) - _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin| - stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00") - end - - return if status.zero? - - raise CommitError.new("Could not update branch #{name.sub('refs/heads/', '')}. Please refresh and try again.") - end - # Makes sure a commit is kept around when Git garbage collection runs. # Git GC will delete commits from the repository that are no longer in any # branches or tags, but we want to keep some of these commits around, for @@ -783,8 +749,7 @@ class Repository user, path, message, branch, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - update_branch_with_hooks( - user, + GitOperationService.new(user, self).with_branch( branch, source_branch: source_branch, source_project: source_project) do |ref| @@ -808,8 +773,7 @@ class Repository user, path, content, message, branch, update, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - update_branch_with_hooks( - user, + GitOperationService.new(user, self).with_branch( branch, source_branch: source_branch, source_project: source_project) do |ref| @@ -839,8 +803,7 @@ class Repository branch:, previous_path:, message:, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - update_branch_with_hooks( - user, + GitOperationService.new(user, self).with_branch( branch, source_branch: source_branch, source_project: source_project) do |ref| @@ -874,8 +837,7 @@ class Repository user, path, message, branch, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - update_branch_with_hooks( - user, + GitOperationService.new(user, self).with_branch( branch, source_branch: source_branch, source_project: source_project) do |ref| @@ -902,8 +864,7 @@ class Repository user:, branch:, message:, actions:, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - update_branch_with_hooks( - user, + GitOperationService.new(user, self).with_branch( branch, source_branch: source_branch, source_project: source_project) do |ref| @@ -964,7 +925,7 @@ class Repository end def user_to_committer(user) - Gitlab::Git::committer_hash(email: user.email, name: user.name) + Gitlab::Git.committer_hash(email: user.email, name: user.name) end def can_be_merged?(source_sha, target_branch) @@ -988,7 +949,8 @@ class Repository merge_index = rugged.merge_commits(our_commit, their_commit) return false if merge_index.conflicts? - update_branch_with_hooks(user, merge_request.target_branch) do + GitOperationService.new(user, self).with_branch( + merge_request.target_branch) do actual_options = options.merge( parents: [our_commit, their_commit], tree: merge_index.write_tree(rugged), @@ -1005,8 +967,7 @@ class Repository return false unless revert_tree_id - update_branch_with_hooks( - user, + GitOperationService.new(user, self).with_branch( base_branch, source_commit: commit) do @@ -1027,8 +988,7 @@ class Repository return false unless cherry_pick_tree_id - update_branch_with_hooks( - user, + GitOperationService.new(user, self).with_branch( base_branch, source_commit: commit) do @@ -1048,8 +1008,8 @@ class Repository end end - def resolve_conflicts(user, branch, params) - update_branch_with_hooks(user, branch) do + def resolve_conflicts(user, branch_name, params) + GitOperationService.new(user, self).with_branch(branch_name) do committer = user_to_committer(user) Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer)) @@ -1140,51 +1100,6 @@ class Repository fetch_ref(path_to_repo, ref, ref_path) end - # Whenever `source_branch` or `source_commit` is passed, if `branch` - # doesn't exist, it would be created from `source_branch` or - # `source_commit`. Should only pass one of them, not both. - # If `source_project` is passed, and the branch doesn't exist, - # it would try to find the source from it instead of current repository. - def update_branch_with_hooks( - current_user, branch, - source_branch: nil, source_commit: nil, source_project: project) - update_autocrlf_option - - target_branch, new_branch_added = - raw_ensure_branch( - branch, - source_branch: source_branch, - source_commit: source_commit, - source_project: source_project - ) - - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch - was_empty = empty? - - # Make commit - newrev = yield(ref) - - unless newrev - raise CommitError.new('Failed to create commit') - end - - if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? - oldrev = Gitlab::Git::BLANK_SHA - else - oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha) - end - - GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do - update_ref!(ref, newrev, oldrev) - - # If repo was empty expire cache - after_create if was_empty - after_create_branch if was_empty || new_branch_added - end - - newrev - end - def ls_files(ref) actual_ref = ref || root_ref raw_repository.ls_files(actual_ref) @@ -1266,47 +1181,4 @@ class Repository def repository_event(event, tags = {}) Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags)) end - - def raw_ensure_branch( - branch_name, source_commit: nil, source_branch: nil, source_project: nil) - old_branch = find_branch(branch_name) - - if source_commit && source_branch - raise ArgumentError, - 'Should only pass either :source_branch or :source_commit, not both' - end - - if old_branch - [old_branch, false] - elsif project != source_project - unless source_branch - raise ArgumentError, - 'Should also pass :source_branch if' + - ' :source_project is different from current project' - end - - fetch_ref( - source_project.repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch}", - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch_name}" - ) - - [find_branch(branch_name), true] - elsif source_commit || source_branch - oldrev = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - target = (source_commit || commit(source_branch)).try(:sha) - - unless target - raise CommitError.new( - "Cannot find branch #{branch_name} nor #{source_commit.try(:sha) || source_branch}") - end - - update_ref!(ref, target, oldrev) - - [find_branch(branch_name), true] - else - [nil, true] # Empty branch - end - end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb new file mode 100644 index 00000000000..88175c6931d --- /dev/null +++ b/app/services/git_operation_service.rb @@ -0,0 +1,168 @@ + +GitOperationService = Struct.new(:user, :repository) do + def add_branch(branch_name, newrev) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + oldrev = Gitlab::Git::BLANK_SHA + + with_hooks_and_update_ref(ref, oldrev, newrev) + end + + def rm_branch(branch) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name + oldrev = branch.dereferenced_target.id + newrev = Gitlab::Git::BLANK_SHA + + with_hooks_and_update_ref(ref, oldrev, newrev) + end + + def add_tag(tag_name, newrev, options = {}) + ref = Gitlab::Git::TAG_REF_PREFIX + tag_name + oldrev = Gitlab::Git::BLANK_SHA + + with_hooks(ref, oldrev, newrev) do |service| + raw_tag = repository.rugged.tags.create(tag_name, newrev, options) + service.newrev = raw_tag.target_id + end + end + + # Whenever `source_branch` or `source_commit` is passed, if `branch` + # doesn't exist, it would be created from `source_branch` or + # `source_commit`. Should only pass one of them, not both. + # If `source_project` is passed, and the branch doesn't exist, + # it would try to find the source from it instead of current repository. + def with_branch( + branch_name, + source_branch: nil, + source_commit: nil, + source_project: repository.project) + + if source_commit && source_branch + raise ArgumentError, 'Should pass only :source_branch or :source_commit' + end + + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + oldrev = Gitlab::Git::BLANK_SHA + + if repository.branch_exists?(branch_name) + oldrev = newrev = repository.commit(branch_name).sha + + elsif repository.project != source_project + unless source_branch + raise ArgumentError, + 'Should also pass :source_branch if' + + ' :source_project is different from current project' + end + + newrev = source_project.repository.commit(source_branch).try(:sha) + + unless newrev + raise Repository::CommitError.new( + "Cannot find branch #{branch_name} nor" \ + " #{source_branch} from" \ + " #{source_project.path_with_namespace}") + end + + elsif source_commit || source_branch + newrev = (source_commit || repository.commit(source_branch)).try(:sha) + + unless newrev + raise Repository::CommitError.new( + "Cannot find branch #{branch_name} nor" \ + " #{source_commit.try(:sha) || source_branch} from" \ + " #{repository.project.path_with_namespace}") + end + + else # we want an orphan empty branch + newrev = Gitlab::Git::BLANK_SHA + end + + commit_with_hooks(ref, oldrev, newrev) do + if repository.project != source_project + repository.fetch_ref( + source_project.repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch}", + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch_name}" + ) + end + + yield(ref) + end + end + + private + + def commit_with_hooks(ref, oldrev, newrev) + with_hooks_and_update_ref(ref, oldrev, newrev) do |service| + was_empty = repository.empty? + + # Make commit + nextrev = yield(ref) + + unless nextrev + raise Repository::CommitError.new('Failed to create commit') + end + + service.newrev = nextrev + + update_ref!(ref, nextrev, newrev) + + # If repo was empty expire cache + repository.after_create if was_empty + repository.after_create_branch if was_empty || + oldrev == Gitlab::Git::BLANK_SHA + + nextrev + end + end + + def with_hooks_and_update_ref(ref, oldrev, newrev) + with_hooks(ref, oldrev, newrev) do |service| + update_ref!(ref, newrev, oldrev) + + yield(service) if block_given? + end + end + + def with_hooks(ref, oldrev, newrev) + update_autocrlf_option + + result = nil + + GitHooksService.new.execute( + user, + repository.path_to_repo, + oldrev, + newrev, + ref) do |service| + + result = yield(service) if block_given? + end + + result + end + + def update_ref!(name, newrev, oldrev) + # We use 'git update-ref' because libgit2/rugged currently does not + # offer 'compare and swap' ref updates. Without compare-and-swap we can + # (and have!) accidentally reset the ref to an earlier state, clobbering + # commits. See also https://github.com/libgit2/libgit2/issues/1534. + command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z] + _, status = Gitlab::Popen.popen( + command, + repository.path_to_repo) do |stdin| + stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00") + end + + unless status.zero? + raise Repository::CommitError.new( + "Could not update branch #{name.sub('refs/heads/', '')}." \ + " Please refresh and try again.") + end + end + + def update_autocrlf_option + if repository.raw_repository.autocrlf != :input + repository.raw_repository.autocrlf = :input + end + end +end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index b797d19161d..3ce3c4dec2a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -667,7 +667,7 @@ describe Repository, models: true do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do - repository.rm_branch(user, 'new_feature') + repository.rm_branch(user, 'feature') end.to raise_error(GitHooksService::PreReceiveError) end @@ -682,7 +682,7 @@ describe Repository, models: true do end end - describe '#update_branch_with_hooks' do + xdescribe '#update_branch_with_hooks' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev @@ -848,7 +848,7 @@ describe Repository, models: true do end it 'sets autocrlf to :input' do - repository.update_autocrlf_option + GitOperationService.new(nil, repository).send(:update_autocrlf_option) expect(repository.raw_repository.autocrlf).to eq(:input) end @@ -863,7 +863,7 @@ describe Repository, models: true do expect(repository.raw_repository).not_to receive(:autocrlf=). with(:input) - repository.update_autocrlf_option + GitOperationService.new(nil, repository).send(:update_autocrlf_option) end end end @@ -1429,14 +1429,14 @@ describe Repository, models: true do describe '#update_ref!' do it 'can create a ref' do - repository.update_ref!('refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA) + GitOperationService.new(nil, repository).send(:update_ref!, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA) expect(repository.find_branch('foobar')).not_to be_nil end it 'raises CommitError when the ref update fails' do expect do - repository.update_ref!('refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA) + GitOperationService.new(nil, repository).send(:update_ref!, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA) end.to raise_error(Repository::CommitError) end end From 444da6f47ed77172471a27386b969e6401d7cf84 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Dec 2016 15:20:50 +0800 Subject: [PATCH 028/174] Fix update_ref! call in the test --- spec/workers/git_garbage_collect_worker_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index e471a68a49a..2b31efbf631 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -107,7 +107,8 @@ describe GitGarbageCollectWorker do tree: old_commit.tree, parents: [old_commit], ) - project.repository.update_ref!( + GitOperationService.new(nil, project.repository).send( + :update_ref!, "refs/heads/#{SecureRandom.hex(6)}", new_commit_sha, Gitlab::Git::BLANK_SHA From 65806ec632f2ea1e2087b7cdc64f13e6db49c88a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Dec 2016 18:47:24 +0800 Subject: [PATCH 029/174] Re-enable tests for update_branch_with_hooks and Add back check if we're losing commits in a merge. --- app/services/git_operation_service.rb | 15 ++++++-- spec/models/repository_spec.rb | 50 ++++++++++++++++++++------- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 88175c6931d..c34d4bde150 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -102,9 +102,20 @@ GitOperationService = Struct.new(:user, :repository) do raise Repository::CommitError.new('Failed to create commit') end - service.newrev = nextrev + branch = + repository.find_branch(ref[Gitlab::Git::BRANCH_REF_PREFIX.size..-1]) - update_ref!(ref, nextrev, newrev) + prevrev = if branch && + !repository.rugged.lookup(nextrev).parent_ids.empty? + repository.rugged.merge_base( + nextrev, branch.dereferenced_target.sha) + else + newrev + end + + update_ref!(ref, nextrev, prevrev) + + service.newrev = nextrev # If repo was empty expire cache repository.after_create if was_empty diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 3ce3c4dec2a..e3ec4c85a74 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -682,33 +682,48 @@ describe Repository, models: true do end end - xdescribe '#update_branch_with_hooks' do + describe '#update_branch_with_hooks' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev context 'when pre hooks were successful' do before do - expect_any_instance_of(GitHooksService).to receive(:execute). - with(user, repository.path_to_repo, old_rev, new_rev, 'refs/heads/feature'). - and_yield.and_return(true) + service = GitHooksService.new + expect(GitHooksService).to receive(:new).and_return(service) + expect(service).to receive(:execute). + with( + user, + repository.path_to_repo, + old_rev, + old_rev, + 'refs/heads/feature'). + and_yield(service).and_return(true) end it 'runs without errors' do expect do - repository.update_branch_with_hooks(user, 'feature') { new_rev } + GitOperationService.new(user, repository).with_branch('feature') do + new_rev + end end.not_to raise_error end it 'ensures the autocrlf Git option is set to :input' do - expect(repository).to receive(:update_autocrlf_option) + service = GitOperationService.new(user, repository) - repository.update_branch_with_hooks(user, 'feature') { new_rev } + expect(service).to receive(:update_autocrlf_option) + + service.with_branch('feature') { new_rev } end context "when the branch wasn't empty" do it 'updates the head' do expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev) - repository.update_branch_with_hooks(user, 'feature') { new_rev } + + GitOperationService.new(user, repository).with_branch('feature') do + new_rev + end + expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev) end end @@ -727,7 +742,11 @@ describe Repository, models: true do branch = 'feature-ff-target' repository.add_branch(user, branch, old_rev) - expect { repository.update_branch_with_hooks(user, branch) { new_rev } }.not_to raise_error + expect do + GitOperationService.new(user, repository).with_branch(branch) do + new_rev + end + end.not_to raise_error end end @@ -742,7 +761,9 @@ describe Repository, models: true do # Updating 'master' to new_rev would lose the commits on 'master' that # are not contained in new_rev. This should not be allowed. expect do - repository.update_branch_with_hooks(user, branch) { new_rev } + GitOperationService.new(user, repository).with_branch(branch) do + new_rev + end end.to raise_error(Repository::CommitError) end end @@ -752,7 +773,9 @@ describe Repository, models: true do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do - repository.update_branch_with_hooks(user, 'feature') { new_rev } + GitOperationService.new(user, repository).with_branch('feature') do + new_rev + end end.to raise_error(GitHooksService::PreReceiveError) end end @@ -770,7 +793,10 @@ describe Repository, models: true do expect(repository).to receive(:expire_branches_cache) expect(repository).to receive(:expire_has_visible_content_cache) - repository.update_branch_with_hooks(user, 'new-feature') { new_rev } + GitOperationService.new(user, repository). + with_branch('new-feature') do + new_rev + end end end From 6ae1a73cfdad4b98176bb99846042d4378119de2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Dec 2016 19:50:08 +0800 Subject: [PATCH 030/174] Pass source_branch properly for cherry-pick/revert Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237/diffs#note_19210818 --- app/models/repository.rb | 12 ++++++++---- app/services/commits/change_service.rb | 9 ++++++++- app/services/git_operation_service.rb | 16 +++++----------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 491d2ab69b2..36cbb0d051e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -962,14 +962,16 @@ class Repository end end - def revert(user, commit, base_branch, revert_tree_id = nil) + def revert( + user, commit, base_branch, revert_tree_id = nil, + source_branch: nil, source_project: project) revert_tree_id ||= check_revert_content(commit, base_branch) return false unless revert_tree_id GitOperationService.new(user, self).with_branch( base_branch, - source_commit: commit) do + source_branch: source_branch, source_project: source_project) do source_sha = find_branch(base_branch).dereferenced_target.sha committer = user_to_committer(user) @@ -983,14 +985,16 @@ class Repository end end - def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) + def cherry_pick( + user, commit, base_branch, cherry_pick_tree_id = nil, + source_branch: nil, source_project: project) cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) return false unless cherry_pick_tree_id GitOperationService.new(user, self).with_branch( base_branch, - source_commit: commit) do + source_branch: source_branch, source_project: source_project) do source_sha = find_branch(base_branch).dereferenced_target.sha committer = user_to_committer(user) diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 04b28cfaed8..5458f7a6790 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -31,7 +31,14 @@ module Commits if tree_id validate_target_branch(into) if @create_merge_request - repository.public_send(action, current_user, @commit, into, tree_id) + repository.public_send( + action, + current_user, + @commit, + into, + tree_id, + source_branch: @target_branch) + success else error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically. diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index c34d4bde150..c9e2c21737a 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -25,21 +25,15 @@ GitOperationService = Struct.new(:user, :repository) do end end - # Whenever `source_branch` or `source_commit` is passed, if `branch` - # doesn't exist, it would be created from `source_branch` or - # `source_commit`. Should only pass one of them, not both. + # Whenever `source_branch` is passed, if `branch` doesn't exist, + # it would be created from `source_branch`. # If `source_project` is passed, and the branch doesn't exist, # it would try to find the source from it instead of current repository. def with_branch( branch_name, source_branch: nil, - source_commit: nil, source_project: repository.project) - if source_commit && source_branch - raise ArgumentError, 'Should pass only :source_branch or :source_commit' - end - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name oldrev = Gitlab::Git::BLANK_SHA @@ -62,13 +56,13 @@ GitOperationService = Struct.new(:user, :repository) do " #{source_project.path_with_namespace}") end - elsif source_commit || source_branch - newrev = (source_commit || repository.commit(source_branch)).try(:sha) + elsif source_branch + newrev = repository.commit(source_branch).try(:sha) unless newrev raise Repository::CommitError.new( "Cannot find branch #{branch_name} nor" \ - " #{source_commit.try(:sha) || source_branch} from" \ + " #{source_branch} from" \ " #{repository.project.path_with_namespace}") end From 5ecd0c81af85476c2328d3836cc68b17ebd5a8a6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Dec 2016 22:37:43 +0800 Subject: [PATCH 031/174] Commit outside the hooks if possible: So we still commit outside the hooks, and only update ref inside the hooks. There are only two exceptions: * Whenever it's adding a tag. We can't add a tag without committing, unfortunately. See !7700 * Whenever source project is in another repository. We'll need to fetch ref otherwise commits can't be made. See the whole discussion starting from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19210942 --- app/models/repository.rb | 16 +++- app/services/git_operation_service.rb | 111 ++++++++++++-------------- spec/models/repository_spec.rb | 2 +- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 36cbb0d051e..9393d6b461e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -973,7 +973,8 @@ class Repository base_branch, source_branch: source_branch, source_project: source_project) do - source_sha = find_branch(base_branch).dereferenced_target.sha + source_sha = source_project.repository.find_source_sha( + source_branch || base_branch) committer = user_to_committer(user) Rugged::Commit.create(rugged, @@ -996,7 +997,8 @@ class Repository base_branch, source_branch: source_branch, source_project: source_project) do - source_sha = find_branch(base_branch).dereferenced_target.sha + source_sha = source_project.repository.find_source_sha( + source_branch || base_branch) committer = user_to_committer(user) Rugged::Commit.create(rugged, @@ -1162,6 +1164,16 @@ class Repository end end + protected + + def find_source_sha(branch_name) + if branch_exists?(branch_name) + find_branch(branch_name).dereferenced_target.sha + else + Gitlab::Git::BLANK_SHA + end + end + private def refs_directory_exists? diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index c9e2c21737a..3a102a9276b 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -34,43 +34,10 @@ GitOperationService = Struct.new(:user, :repository) do source_branch: nil, source_project: repository.project) - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - oldrev = Gitlab::Git::BLANK_SHA + check_with_branch_arguments!(branch_name, source_branch, source_project) - if repository.branch_exists?(branch_name) - oldrev = newrev = repository.commit(branch_name).sha - - elsif repository.project != source_project - unless source_branch - raise ArgumentError, - 'Should also pass :source_branch if' + - ' :source_project is different from current project' - end - - newrev = source_project.repository.commit(source_branch).try(:sha) - - unless newrev - raise Repository::CommitError.new( - "Cannot find branch #{branch_name} nor" \ - " #{source_branch} from" \ - " #{source_project.path_with_namespace}") - end - - elsif source_branch - newrev = repository.commit(source_branch).try(:sha) - - unless newrev - raise Repository::CommitError.new( - "Cannot find branch #{branch_name} nor" \ - " #{source_branch} from" \ - " #{repository.project.path_with_namespace}") - end - - else # we want an orphan empty branch - newrev = Gitlab::Git::BLANK_SHA - end - - commit_with_hooks(ref, oldrev, newrev) do + update_branch_with_hooks( + branch_name, source_branch, source_project) do |ref| if repository.project != source_project repository.fetch_ref( source_project.repository.path_to_repo, @@ -85,39 +52,37 @@ GitOperationService = Struct.new(:user, :repository) do private - def commit_with_hooks(ref, oldrev, newrev) - with_hooks_and_update_ref(ref, oldrev, newrev) do |service| - was_empty = repository.empty? + def update_branch_with_hooks(branch_name, source_branch, source_project) + update_autocrlf_option - # Make commit - nextrev = yield(ref) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + oldrev = Gitlab::Git::BLANK_SHA + was_empty = repository.empty? - unless nextrev - raise Repository::CommitError.new('Failed to create commit') - end + # Make commit + newrev = yield(ref) - branch = - repository.find_branch(ref[Gitlab::Git::BRANCH_REF_PREFIX.size..-1]) + unless newrev + raise Repository::CommitError.new('Failed to create commit') + end - prevrev = if branch && - !repository.rugged.lookup(nextrev).parent_ids.empty? - repository.rugged.merge_base( - nextrev, branch.dereferenced_target.sha) - else - newrev - end - - update_ref!(ref, nextrev, prevrev) - - service.newrev = nextrev + branch = repository.find_branch(branch_name) + oldrev = if repository.rugged.lookup(newrev).parent_ids.empty? || + branch.nil? + Gitlab::Git::BLANK_SHA + else + repository.rugged.merge_base( + newrev, branch.dereferenced_target.sha) + end + with_hooks_and_update_ref(ref, oldrev, newrev) do # If repo was empty expire cache repository.after_create if was_empty repository.after_create_branch if was_empty || oldrev == Gitlab::Git::BLANK_SHA - - nextrev end + + newrev end def with_hooks_and_update_ref(ref, oldrev, newrev) @@ -129,8 +94,6 @@ GitOperationService = Struct.new(:user, :repository) do end def with_hooks(ref, oldrev, newrev) - update_autocrlf_option - result = nil GitHooksService.new.execute( @@ -170,4 +133,30 @@ GitOperationService = Struct.new(:user, :repository) do repository.raw_repository.autocrlf = :input end end + + def check_with_branch_arguments!(branch_name, source_branch, source_project) + return if repository.branch_exists?(branch_name) + + if repository.project != source_project + unless source_branch + raise ArgumentError, + 'Should also pass :source_branch if' + + ' :source_project is different from current project' + end + + unless source_project.repository.commit(source_branch).try(:sha) + raise Repository::CommitError.new( + "Cannot find branch #{branch_name} nor" \ + " #{source_branch} from" \ + " #{source_project.path_with_namespace}") + end + elsif source_branch + unless repository.commit(source_branch).try(:sha) + raise Repository::CommitError.new( + "Cannot find branch #{branch_name} nor" \ + " #{source_branch} from" \ + " #{repository.project.path_with_namespace}") + end + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index e3ec4c85a74..ebd05c946cc 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -695,7 +695,7 @@ describe Repository, models: true do user, repository.path_to_repo, old_rev, - old_rev, + new_rev, 'refs/heads/feature'). and_yield(service).and_return(true) end From 5ba468efde94ed823aaccabf2405e63cecfbf9d6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Dec 2016 23:03:58 +0800 Subject: [PATCH 032/174] Not sure why rubocop prefers this but anyway --- app/models/repository.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 9393d6b461e..038ab5e104a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -974,7 +974,7 @@ class Repository source_branch: source_branch, source_project: source_project) do source_sha = source_project.repository.find_source_sha( - source_branch || base_branch) + source_branch || base_branch) committer = user_to_committer(user) Rugged::Commit.create(rugged, @@ -998,7 +998,7 @@ class Repository source_branch: source_branch, source_project: source_project) do source_sha = source_project.repository.find_source_sha( - source_branch || base_branch) + source_branch || base_branch) committer = user_to_committer(user) Rugged::Commit.create(rugged, From fff3c5262857ee8c8dbf4ba7159032f836eba1bd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Dec 2016 23:33:33 +0800 Subject: [PATCH 033/174] Use multi_action to commit which doesn't need to have the branch existed upfront. That is, `Rugged::Commit.create` rather than `Gitlab::Git::Blob.commit` which the former doesn't need to have the branch but the latter needs. --- app/models/repository.rb | 99 +++++++++++++++------------------------- 1 file changed, 36 insertions(+), 63 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 038ab5e104a..c05cfb271c7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -773,27 +773,17 @@ class Repository user, path, content, message, branch, update, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - GitOperationService.new(user, self).with_branch( - branch, + multi_action( + user: user, + branch: branch, + message: message, + author_email: author_email, + author_name: author_name, source_branch: source_branch, - source_project: source_project) do |ref| - options = { - commit: { - branch: ref, - message: message, - update_ref: false - }, - file: { - content: content, - path: path, - update: update - } - } - - options.merge!(get_committer_and_author(user, email: author_email, name: author_name)) - - Gitlab::Git::Blob.commit(raw_repository, options) - end + source_project: source_project, + actions: [{action: :create, + file_path: path, + content: content}]) end # rubocop:enable Metrics/ParameterLists @@ -803,32 +793,24 @@ class Repository branch:, previous_path:, message:, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - GitOperationService.new(user, self).with_branch( - branch, + action = if previous_path && previous_path != path + :move + else + :update + end + + multi_action( + user: user, + branch: branch, + message: message, + author_email: author_email, + author_name: author_name, source_branch: source_branch, - source_project: source_project) do |ref| - options = { - commit: { - branch: ref, - message: message, - update_ref: false - }, - file: { - content: content, - path: path, - update: true - } - } - - options.merge!(get_committer_and_author(user, email: author_email, name: author_name)) - - if previous_path && previous_path != path - options[:file][:previous_path] = previous_path - Gitlab::Git::Blob.rename(raw_repository, options) - else - Gitlab::Git::Blob.commit(raw_repository, options) - end - end + source_project: source_project, + actions: [{action: action, + file_path: path, + content: content, + previous_path: previous_path}]) end # rubocop:enable Metrics/ParameterLists @@ -837,25 +819,16 @@ class Repository user, path, message, branch, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - GitOperationService.new(user, self).with_branch( - branch, + multi_action( + user: user, + branch: branch, + message: message, + author_email: author_email, + author_name: author_name, source_branch: source_branch, - source_project: source_project) do |ref| - options = { - commit: { - branch: ref, - message: message, - update_ref: false - }, - file: { - path: path - } - } - - options.merge!(get_committer_and_author(user, email: author_email, name: author_name)) - - Gitlab::Git::Blob.remove(raw_repository, options) - end + source_project: source_project, + actions: [{action: :delete, + file_path: path}]) end # rubocop:enable Metrics/ParameterLists From a51f26e5142d6c5f40984cd374f0dea7580a4235 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 01:12:49 +0800 Subject: [PATCH 034/174] Use commit_file for commit_dir --- app/models/repository.rb | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index c05cfb271c7..6246630300c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -749,22 +749,32 @@ class Repository user, path, message, branch, author_email: nil, author_name: nil, source_branch: nil, source_project: project) - GitOperationService.new(user, self).with_branch( - branch, - source_branch: source_branch, - source_project: source_project) do |ref| - options = { - commit: { - branch: ref, - message: message, - update_ref: false - } - } + if branch_exists?(branch) + # tree_entry is private + entry = raw_repository.send(:tree_entry, commit(branch), path) - options.merge!(get_committer_and_author(user, email: author_email, name: author_name)) - - raw_repository.mkdir(path, options) + if entry + if entry[:type] == :blob + raise Gitlab::Git::Repository::InvalidBlobName.new( + "Directory already exists as a file") + else + raise Gitlab::Git::Repository::InvalidBlobName.new( + "Directory already exists") + end + end end + + commit_file( + user, + "#{Gitlab::Git::PathHelper.normalize_path(path)}/.gitkeep", + '', + message, + branch, + false, + author_email: author_email, + author_name: author_name, + source_branch: source_branch, + source_project: source_project) end # rubocop:enable Metrics/ParameterLists From e76173038b03c660a498d9f38b07797b453f1e7f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 01:13:16 +0800 Subject: [PATCH 035/174] Restore the check for update in commit_file --- app/models/repository.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/models/repository.rb b/app/models/repository.rb index 6246630300c..8a94fbf3ecc 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -783,6 +783,14 @@ class Repository user, path, content, message, branch, update, author_email: nil, author_name: nil, source_branch: nil, source_project: project) + if branch_exists?(branch) && update == false + # tree_entry is private + if raw_repository.send(:tree_entry, commit(branch), path) + raise Gitlab::Git::Repository::InvalidBlobName.new( + "Filename already exists; update not allowed") + end + end + multi_action( user: user, branch: branch, From 4b3c18ce5cbd88156423d89f26b0869f45e2225e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 01:13:33 +0800 Subject: [PATCH 036/174] Use branch_name to find the branch rather than ref --- app/models/repository.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 8a94fbf3ecc..4d74ac6f585 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -858,9 +858,9 @@ class Repository GitOperationService.new(user, self).with_branch( branch, source_branch: source_branch, - source_project: source_project) do |ref| + source_project: source_project) do index = rugged.index - branch_commit = find_branch(ref) + branch_commit = find_branch(branch) parents = if branch_commit last_commit = branch_commit.dereferenced_target From e36088dd98c1c00a8884684158d47f479940346e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 01:30:10 +0800 Subject: [PATCH 037/174] We need to normalize the path for all actions --- app/models/repository.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 4d74ac6f585..cf2c08f618e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -766,7 +766,7 @@ class Repository commit_file( user, - "#{Gitlab::Git::PathHelper.normalize_path(path)}/.gitkeep", + "#{path}/.gitkeep", '', message, branch, @@ -871,12 +871,14 @@ class Repository end actions.each do |action| + path = Gitlab::Git::PathHelper.normalize_path(action[:file_path]).to_s + case action[:action] when :create, :update, :move mode = case action[:action] when :update - index.get(action[:file_path])[:mode] + index.get(path)[:mode] when :move index.get(action[:previous_path])[:mode] end @@ -887,9 +889,9 @@ class Repository content = action[:encoding] == 'base64' ? Base64.decode64(action[:content]) : action[:content] oid = rugged.write(content, :blob) - index.add(path: action[:file_path], oid: oid, mode: mode) + index.add(path: path, oid: oid, mode: mode) when :delete - index.remove(action[:file_path]) + index.remove(path) end end From 56e0dcab97f43e14ae88a66031e3cd71cd062b23 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 02:15:09 +0800 Subject: [PATCH 038/174] Spaces around hash braces --- app/models/repository.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index cf2c08f618e..4d350f937a6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -799,9 +799,9 @@ class Repository author_name: author_name, source_branch: source_branch, source_project: source_project, - actions: [{action: :create, - file_path: path, - content: content}]) + actions: [{ action: :create, + file_path: path, + content: content }]) end # rubocop:enable Metrics/ParameterLists @@ -825,10 +825,10 @@ class Repository author_name: author_name, source_branch: source_branch, source_project: source_project, - actions: [{action: action, - file_path: path, - content: content, - previous_path: previous_path}]) + actions: [{ action: action, + file_path: path, + content: content, + previous_path: previous_path }]) end # rubocop:enable Metrics/ParameterLists @@ -845,8 +845,8 @@ class Repository author_name: author_name, source_branch: source_branch, source_project: source_project, - actions: [{action: :delete, - file_path: path}]) + actions: [{ action: :delete, + file_path: path }]) end # rubocop:enable Metrics/ParameterLists From cdb598f3973bb7643d8d7f85f6109d84aea759ec Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 14:15:50 +0800 Subject: [PATCH 039/174] We probably don't need this anymore, not sure why --- app/controllers/concerns/creates_commit.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index e5c40446314..dacb5679dd3 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,10 +4,9 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - source_branch = @ref if @ref && @repository.branch_exists?(@ref) commit_params = @commit_params.merge( source_project: @project, - source_branch: source_branch, + source_branch: @ref, target_branch: @target_branch ) From ae5b935b2888f7721e424cf41e2963e1483d8bb5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 14:16:06 +0800 Subject: [PATCH 040/174] find the commit properly and replicate gitlab_git by checking filename as well --- app/models/repository.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 4d350f937a6..50f347b58c8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -860,7 +860,8 @@ class Repository source_branch: source_branch, source_project: source_project) do index = rugged.index - branch_commit = find_branch(branch) + branch_commit = source_project.repository.find_branch( + source_branch || branch) parents = if branch_commit last_commit = branch_commit.dereferenced_target @@ -873,6 +874,9 @@ class Repository actions.each do |action| path = Gitlab::Git::PathHelper.normalize_path(action[:file_path]).to_s + raise Gitlab::Git::Repository::InvalidBlobName.new("Invalid path") if + path.split('/').include?('..') + case action[:action] when :create, :update, :move mode = From 4f94053c21196b3445c117130e3556463e4ca1d0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 15:22:32 +0800 Subject: [PATCH 041/174] We still need it for empty repo for web UI! We shouldn't pass a non-existing branch to source_branch. Checkout test for: * spec/features/tags/master_views_tags_spec.rb:24 * spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb:13 --- app/controllers/concerns/creates_commit.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index dacb5679dd3..5d11f286e9a 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,9 +4,10 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables + source_branch = @ref if @ref && @repository.find_branch(@ref) commit_params = @commit_params.merge( source_project: @project, - source_branch: @ref, + source_branch: source_branch, target_branch: @target_branch ) From cf677378ee8104e7505cf0670ad45a51613af575 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 15:23:41 +0800 Subject: [PATCH 042/174] Prefer repository.branch_exists? --- app/services/files/base_service.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 6779bd2818a..80e1d1d60f2 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -5,7 +5,7 @@ module Files def execute @source_project = params[:source_project] || @project @source_branch = params[:source_branch] - @target_branch = params[:target_branch] + @target_branch = params[:target_branch] @commit_message = params[:commit_message] @file_path = params[:file_path] @@ -59,12 +59,12 @@ module Files end unless project.empty_repo? - unless @source_project.repository.branch_names.include?(@source_branch) + unless @source_project.repository.branch_exists?(@source_branch) raise_error('You can only create or edit files when you are on a branch') end if different_branch? - if repository.branch_names.include?(@target_branch) + if repository.branch_exists?(@target_branch) raise_error('Branch with such name already exists. You need to switch to this branch in order to make changes') end end From 691f1c496834078ba41209597558259d20790a0b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 15:31:42 +0800 Subject: [PATCH 043/174] Simply give result if result[:status] == :error --- app/services/create_branch_service.rb | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 076f976ed06..1b5e504573a 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,8 +1,9 @@ class CreateBranchService < BaseService def execute(branch_name, ref) - failure = validate_new_branch(branch_name) + result = ValidateNewBranchService.new(project, current_user). + execute(branch_name) - return failure if failure + return result if result[:status] == :error new_branch = repository.add_branch(current_user, branch_name, ref) @@ -18,13 +19,4 @@ class CreateBranchService < BaseService def success(branch) super().merge(branch: branch) end - - private - - def validate_new_branch(branch_name) - result = ValidateNewBranchService.new(project, current_user). - execute(branch_name) - - error(result[:message]) if result[:status] == :error - end end From 3fa3fcd7876262bb63966debd04d16ea219fad73 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 17:08:25 +0800 Subject: [PATCH 044/174] Cleanup parameters, easier to understand and more consistent across different methodst --- app/models/repository.rb | 91 +++++++++++++----------- app/services/commits/change_service.rb | 2 +- app/services/files/create_dir_service.rb | 6 +- app/services/files/create_service.rb | 8 +-- app/services/files/delete_service.rb | 6 +- app/services/files/multi_service.rb | 4 +- app/services/files/update_service.rb | 6 +- app/services/git_operation_service.rb | 16 ++--- spec/models/repository_spec.rb | 86 ++++++++++++++-------- 9 files changed, 130 insertions(+), 95 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 50f347b58c8..73a9e269a65 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -746,12 +746,13 @@ class Repository # rubocop:disable Metrics/ParameterLists def commit_dir( - user, path, message, branch, + user, path, + message:, branch_name:, author_email: nil, author_name: nil, - source_branch: nil, source_project: project) - if branch_exists?(branch) + source_branch_name: nil, source_project: project) + if branch_exists?(branch_name) # tree_entry is private - entry = raw_repository.send(:tree_entry, commit(branch), path) + entry = raw_repository.send(:tree_entry, commit(branch_name), path) if entry if entry[:type] == :blob @@ -768,24 +769,25 @@ class Repository user, "#{path}/.gitkeep", '', - message, - branch, - false, + message: message, + branch_name: branch_name, + update: false, author_email: author_email, author_name: author_name, - source_branch: source_branch, + source_branch_name: source_branch_name, source_project: source_project) end # rubocop:enable Metrics/ParameterLists # rubocop:disable Metrics/ParameterLists def commit_file( - user, path, content, message, branch, update, + user, path, content, + message:, branch_name:, update: true, author_email: nil, author_name: nil, - source_branch: nil, source_project: project) - if branch_exists?(branch) && update == false + source_branch_name: nil, source_project: project) + if branch_exists?(branch_name) && update == false # tree_entry is private - if raw_repository.send(:tree_entry, commit(branch), path) + if raw_repository.send(:tree_entry, commit(branch_name), path) raise Gitlab::Git::Repository::InvalidBlobName.new( "Filename already exists; update not allowed") end @@ -793,11 +795,11 @@ class Repository multi_action( user: user, - branch: branch, message: message, + branch_name: branch_name, author_email: author_email, author_name: author_name, - source_branch: source_branch, + source_branch_name: source_branch_name, source_project: source_project, actions: [{ action: :create, file_path: path, @@ -808,9 +810,9 @@ class Repository # rubocop:disable Metrics/ParameterLists def update_file( user, path, content, - branch:, previous_path:, message:, + message:, branch_name:, previous_path:, author_email: nil, author_name: nil, - source_branch: nil, source_project: project) + source_branch_name: nil, source_project: project) action = if previous_path && previous_path != path :move else @@ -819,11 +821,11 @@ class Repository multi_action( user: user, - branch: branch, message: message, + branch_name: branch_name, author_email: author_email, author_name: author_name, - source_branch: source_branch, + source_branch_name: source_branch_name, source_project: source_project, actions: [{ action: action, file_path: path, @@ -834,16 +836,17 @@ class Repository # rubocop:disable Metrics/ParameterLists def remove_file( - user, path, message, branch, + user, path, + message:, branch_name:, author_email: nil, author_name: nil, - source_branch: nil, source_project: project) + source_branch_name: nil, source_project: project) multi_action( user: user, - branch: branch, message: message, + branch_name: branch_name, author_email: author_email, author_name: author_name, - source_branch: source_branch, + source_branch_name: source_branch_name, source_project: source_project, actions: [{ action: :delete, file_path: path }]) @@ -852,16 +855,16 @@ class Repository # rubocop:disable Metrics/ParameterLists def multi_action( - user:, branch:, message:, actions:, + user:, branch_name:, message:, actions:, author_email: nil, author_name: nil, - source_branch: nil, source_project: project) + source_branch_name: nil, source_project: project) GitOperationService.new(user, self).with_branch( - branch, - source_branch: source_branch, + branch_name, + source_branch_name: source_branch_name, source_project: source_project) do index = rugged.index branch_commit = source_project.repository.find_branch( - source_branch || branch) + source_branch_name || branch_name) parents = if branch_commit last_commit = branch_commit.dereferenced_target @@ -960,18 +963,19 @@ class Repository end def revert( - user, commit, base_branch, revert_tree_id = nil, - source_branch: nil, source_project: project) - revert_tree_id ||= check_revert_content(commit, base_branch) + user, commit, branch_name, revert_tree_id = nil, + source_branch_name: nil, source_project: project) + revert_tree_id ||= check_revert_content(commit, branch_name) return false unless revert_tree_id GitOperationService.new(user, self).with_branch( - base_branch, - source_branch: source_branch, source_project: source_project) do + branch_name, + source_branch_name: source_branch_name, + source_project: source_project) do source_sha = source_project.repository.find_source_sha( - source_branch || base_branch) + source_branch_name || branch_name) committer = user_to_committer(user) Rugged::Commit.create(rugged, @@ -984,18 +988,19 @@ class Repository end def cherry_pick( - user, commit, base_branch, cherry_pick_tree_id = nil, - source_branch: nil, source_project: project) - cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) + user, commit, branch_name, cherry_pick_tree_id = nil, + source_branch_name: nil, source_project: project) + cherry_pick_tree_id ||= check_cherry_pick_content(commit, branch_name) return false unless cherry_pick_tree_id GitOperationService.new(user, self).with_branch( - base_branch, - source_branch: source_branch, source_project: source_project) do + branch_name, + source_branch_name: source_branch_name, + source_project: source_project) do source_sha = source_project.repository.find_source_sha( - source_branch || base_branch) + source_branch_name || branch_name) committer = user_to_committer(user) Rugged::Commit.create(rugged, @@ -1019,8 +1024,8 @@ class Repository end end - def check_revert_content(commit, base_branch) - source_sha = find_branch(base_branch).dereferenced_target.sha + def check_revert_content(commit, branch_name) + source_sha = find_branch(branch_name).dereferenced_target.sha args = [commit.id, source_sha] args << { mainline: 1 } if commit.merge_commit? @@ -1033,8 +1038,8 @@ class Repository tree_id end - def check_cherry_pick_content(commit, base_branch) - source_sha = find_branch(base_branch).dereferenced_target.sha + def check_cherry_pick_content(commit, branch_name) + source_sha = find_branch(branch_name).dereferenced_target.sha args = [commit.id, source_sha] args << 1 if commit.merge_commit? diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 5458f7a6790..d49fcd42a08 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -37,7 +37,7 @@ module Commits @commit, into, tree_id, - source_branch: @target_branch) + source_branch_name: @target_branch) success else diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb index f0bb3333db8..4a2b2e8fcaf 100644 --- a/app/services/files/create_dir_service.rb +++ b/app/services/files/create_dir_service.rb @@ -4,11 +4,11 @@ module Files repository.commit_dir( current_user, @file_path, - @commit_message, - @target_branch, + message: @commit_message, + branch_name: @target_branch, author_email: @author_email, author_name: @author_name, - source_branch: @source_branch) + source_branch_name: @source_branch) end def validate diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 65f7baf56fd..c95cb75f7cb 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -5,12 +5,12 @@ module Files current_user, @file_path, @file_content, - @commit_message, - @target_branch, - false, + message: @commit_message, + branch_name: @target_branch, + update: false, author_email: @author_email, author_name: @author_name, - source_branch: @source_branch) + source_branch_name: @source_branch) end def validate diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index b3f323d0173..45a9a559469 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -4,11 +4,11 @@ module Files repository.remove_file( current_user, @file_path, - @commit_message, - @target_branch, + message: @commit_message, + branch_name: @target_branch, author_email: @author_email, author_name: @author_name, - source_branch: @source_branch) + source_branch_name: @source_branch) end end end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index 6f5f25f88fd..42ed97ca3c0 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -5,12 +5,12 @@ module Files def commit repository.multi_action( user: current_user, - branch: @target_branch, message: @commit_message, + branch_name: @target_branch, actions: params[:actions], author_email: @author_email, author_name: @author_name, - source_branch: @source_branch + source_branch_name: @source_branch ) end diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 67d473d4978..5f671817cdb 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -4,13 +4,13 @@ module Files def commit repository.update_file(current_user, @file_path, @file_content, - branch: @target_branch, - previous_path: @previous_path, message: @commit_message, + branch_name: @target_branch, + previous_path: @previous_path, author_email: @author_email, author_name: @author_name, source_project: @source_project, - source_branch: @source_branch) + source_branch_name: @source_branch) end private diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 3a102a9276b..c8504ecf3cd 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -25,23 +25,23 @@ GitOperationService = Struct.new(:user, :repository) do end end - # Whenever `source_branch` is passed, if `branch` doesn't exist, - # it would be created from `source_branch`. + # Whenever `source_branch_name` is passed, if `branch_name` doesn't exist, + # it would be created from `source_branch_name`. # If `source_project` is passed, and the branch doesn't exist, # it would try to find the source from it instead of current repository. def with_branch( branch_name, - source_branch: nil, + source_branch_name: nil, source_project: repository.project) - check_with_branch_arguments!(branch_name, source_branch, source_project) + check_with_branch_arguments!( + branch_name, source_branch_name, source_project) - update_branch_with_hooks( - branch_name, source_branch, source_project) do |ref| + update_branch_with_hooks(branch_name) do |ref| if repository.project != source_project repository.fetch_ref( source_project.repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch}", + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch_name}", "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch_name}" ) end @@ -52,7 +52,7 @@ GitOperationService = Struct.new(:user, :repository) do private - def update_branch_with_hooks(branch_name, source_branch, source_project) + def update_branch_with_hooks(branch_name) update_autocrlf_option ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index ebd05c946cc..78596346b91 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -249,7 +249,8 @@ describe Repository, models: true do describe "#commit_dir" do it "commits a change that creates a new directory" do expect do - repository.commit_dir(user, 'newdir', 'Create newdir', 'master') + repository.commit_dir(user, 'newdir', + message: 'Create newdir', branch_name: 'master') end.to change { repository.commits('master').count }.by(1) newdir = repository.tree('master', 'newdir') @@ -259,7 +260,10 @@ describe Repository, models: true do context "when an author is specified" do it "uses the given email/name to set the commit's author" do expect do - repository.commit_dir(user, "newdir", "Add newdir", 'master', author_email: author_email, author_name: author_name) + repository.commit_dir(user, 'newdir', + message: 'Add newdir', + branch_name: 'master', + author_email: author_email, author_name: author_name) end.to change { repository.commits('master').count }.by(1) last_commit = repository.commit @@ -274,8 +278,9 @@ describe Repository, models: true do it 'commits change to a file successfully' do expect do repository.commit_file(user, 'CHANGELOG', 'Changelog!', - 'Updates file content', - 'master', true) + message: 'Updates file content', + branch_name: 'master', + update: true) end.to change { repository.commits('master').count }.by(1) blob = repository.blob_at('master', 'CHANGELOG') @@ -286,8 +291,12 @@ describe Repository, models: true do context "when an author is specified" do it "uses the given email/name to set the commit's author" do expect do - repository.commit_file(user, "README", 'README!', 'Add README', - 'master', true, author_email: author_email, author_name: author_name) + repository.commit_file(user, 'README', 'README!', + message: 'Add README', + branch_name: 'master', + update: true, + author_email: author_email, + author_name: author_name) end.to change { repository.commits('master').count }.by(1) last_commit = repository.commit @@ -302,7 +311,7 @@ describe Repository, models: true do it 'updates filename successfully' do expect do repository.update_file(user, 'NEWLICENSE', 'Copyright!', - branch: 'master', + branch_name: 'master', previous_path: 'LICENSE', message: 'Changes filename') end.to change { repository.commits('master').count }.by(1) @@ -315,15 +324,16 @@ describe Repository, models: true do context "when an author is specified" do it "uses the given email/name to set the commit's author" do - repository.commit_file(user, "README", 'README!', 'Add README', 'master', true) + repository.commit_file(user, 'README', 'README!', + message: 'Add README', branch_name: 'master', update: true) expect do - repository.update_file(user, 'README', "Updated README!", - branch: 'master', - previous_path: 'README', - message: 'Update README', - author_email: author_email, - author_name: author_name) + repository.update_file(user, 'README', 'Updated README!', + branch_name: 'master', + previous_path: 'README', + message: 'Update README', + author_email: author_email, + author_name: author_name) end.to change { repository.commits('master').count }.by(1) last_commit = repository.commit @@ -336,10 +346,12 @@ describe Repository, models: true do describe "#remove_file" do it 'removes file successfully' do - repository.commit_file(user, "README", 'README!', 'Add README', 'master', true) + repository.commit_file(user, 'README', 'README!', + message: 'Add README', branch_name: 'master', update: true) expect do - repository.remove_file(user, "README", "Remove README", 'master') + repository.remove_file(user, 'README', + message: 'Remove README', branch_name: 'master') end.to change { repository.commits('master').count }.by(1) expect(repository.blob_at('master', 'README')).to be_nil @@ -347,10 +359,13 @@ describe Repository, models: true do context "when an author is specified" do it "uses the given email/name to set the commit's author" do - repository.commit_file(user, "README", 'README!', 'Add README', 'master', true) + repository.commit_file(user, 'README', 'README!', + message: 'Add README', branch_name: 'master', update: true) expect do - repository.remove_file(user, "README", "Remove README", 'master', author_email: author_email, author_name: author_name) + repository.remove_file(user, 'README', + message: 'Remove README', branch_name: 'master', + author_email: author_email, author_name: author_name) end.to change { repository.commits('master').count }.by(1) last_commit = repository.commit @@ -498,11 +513,14 @@ describe Repository, models: true do describe "#license_blob", caching: true do before do - repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master') + repository.remove_file( + user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master') end it 'handles when HEAD points to non-existent ref' do - repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false) + repository.commit_file( + user, 'LICENSE', 'Copyright!', + message: 'Add LICENSE', branch_name: 'master', update: false) allow(repository).to receive(:file_on_head). and_raise(Rugged::ReferenceError) @@ -511,21 +529,27 @@ describe Repository, models: true do end it 'looks in the root_ref only' do - repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'markdown') - repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'markdown', false) + repository.remove_file(user, 'LICENSE', + message: 'Remove LICENSE', branch_name: 'markdown') + repository.commit_file(user, 'LICENSE', + Licensee::License.new('mit').content, + message: 'Add LICENSE', branch_name: 'markdown', update: false) expect(repository.license_blob).to be_nil end it 'detects license file with no recognizable open-source license content' do - repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false) + repository.commit_file(user, 'LICENSE', 'Copyright!', + message: 'Add LICENSE', branch_name: 'master', update: false) expect(repository.license_blob.name).to eq('LICENSE') end %w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename| it "detects '#{filename}'" do - repository.commit_file(user, filename, Licensee::License.new('mit').content, "Add #{filename}", 'master', false) + repository.commit_file(user, filename, + Licensee::License.new('mit').content, + message: "Add #{filename}", branch_name: 'master', update: false) expect(repository.license_blob.name).to eq(filename) end @@ -534,7 +558,8 @@ describe Repository, models: true do describe '#license_key', caching: true do before do - repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master') + repository.remove_file(user, 'LICENSE', + message: 'Remove LICENSE', branch_name: 'master') end it 'returns nil when no license is detected' do @@ -548,13 +573,16 @@ describe Repository, models: true do end it 'detects license file with no recognizable open-source license content' do - repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false) + repository.commit_file(user, 'LICENSE', 'Copyright!', + message: 'Add LICENSE', branch_name: 'master', update: false) expect(repository.license_key).to be_nil end it 'returns the license key' do - repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false) + repository.commit_file(user, 'LICENSE', + Licensee::License.new('mit').content, + message: 'Add LICENSE', branch_name: 'master', update: false) expect(repository.license_key).to eq('mit') end @@ -815,7 +843,9 @@ describe Repository, models: true do expect(empty_repository).to receive(:expire_has_visible_content_cache) empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!', - 'Updates file content', 'master', false) + message: 'Updates file content', + branch_name: 'master', + update: false) end end end From 23032467d4a1282f69e76bba921bd71c0083f7a8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 17:27:50 +0800 Subject: [PATCH 045/174] source_branch -> source_branch_name --- app/services/git_operation_service.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index c8504ecf3cd..36c8b8ff575 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -134,27 +134,28 @@ GitOperationService = Struct.new(:user, :repository) do end end - def check_with_branch_arguments!(branch_name, source_branch, source_project) + def check_with_branch_arguments!( + branch_name, source_branch_name, source_project) return if repository.branch_exists?(branch_name) if repository.project != source_project - unless source_branch + unless source_branch_name raise ArgumentError, - 'Should also pass :source_branch if' + + 'Should also pass :source_branch_name if' + ' :source_project is different from current project' end - unless source_project.repository.commit(source_branch).try(:sha) + unless source_project.repository.commit(source_branch_name).try(:sha) raise Repository::CommitError.new( "Cannot find branch #{branch_name} nor" \ - " #{source_branch} from" \ + " #{source_branch_name} from" \ " #{source_project.path_with_namespace}") end - elsif source_branch - unless repository.commit(source_branch).try(:sha) + elsif source_branch_name + unless repository.commit(source_branch_name).try(:sha) raise Repository::CommitError.new( "Cannot find branch #{branch_name} nor" \ - " #{source_branch} from" \ + " #{source_branch_name} from" \ " #{repository.project.path_with_namespace}") end end From 8384d0d8d528ffdd60c9ba9e3c0c9f688cb560ef Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 17:57:52 +0800 Subject: [PATCH 046/174] Introduce Repository#with_tmp_ref which we need commits from the other repository. We'll cleanup the tmp ref after we're done with our business. --- .../projects/compare_controller.rb | 3 +- app/models/merge_request_diff.rb | 3 +- app/models/repository.rb | 15 ++++++++++ app/services/compare_service.rb | 30 +++++++++++-------- app/services/git_operation_service.rb | 15 +++++----- app/services/merge_requests/build_service.rb | 5 ++-- app/workers/emails_on_push_worker.rb | 6 ++-- spec/services/compare_service_spec.rb | 6 ++-- 8 files changed, 54 insertions(+), 29 deletions(-) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index bee3d56076c..e2b178314c0 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -37,7 +37,8 @@ class Projects::CompareController < Projects::ApplicationController end def define_diff_vars - @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref) + @compare = CompareService.new(@project, @head_ref). + execute(@project, @start_ref) if @compare @commits = @compare.commits diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index b8f36a2c958..7946d8e123e 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -169,7 +169,8 @@ class MergeRequestDiff < ActiveRecord::Base # When compare merge request versions we want diff A..B instead of A...B # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. - CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight) + CompareService.new(project, head_commit_sha). + execute(project, sha, straight: straight) end def commits_count diff --git a/app/models/repository.rb b/app/models/repository.rb index 73a9e269a65..ced68b9d274 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1099,6 +1099,21 @@ class Repository Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip) end + def with_tmp_ref(source_repository, source_branch_name) + random_string = SecureRandom.hex + + fetch_ref( + source_repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch_name}", + "refs/tmp/#{random_string}/head" + ) + + yield + + ensure + FileUtils.rm_rf("#{path_to_repo}/refs/tmp/#{random_string}") + end + def fetch_ref(source_path, source_ref, target_ref) args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) Gitlab::Popen.popen(args, path_to_repo) diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 5e8fafca98c..4367cb5f615 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,23 +3,29 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch, straight: false) - source_commit = source_project.commit(source_branch) - return unless source_commit + attr_reader :source_project, :source_sha - source_sha = source_commit.sha + def initialize(new_source_project, source_branch) + @source_project = new_source_project + @source_sha = new_source_project.commit(source_branch).try(:sha) + end + + def execute(target_project, target_branch, straight: false) + return unless source_sha # If compare with other project we need to fetch ref first - unless target_project == source_project - random_string = SecureRandom.hex - - target_project.repository.fetch_ref( - source_project.repository.path_to_repo, - "refs/heads/#{source_branch}", - "refs/tmp/#{random_string}/head" - ) + if target_project == source_project + compare(target_project, target_branch, straight) + else + target_project.repository.with_tmp_ref(source_project, source_branch) do + compare(target_project, target_branch, straight) + end end + end + private + + def compare(target_project, target_branch, straight) raw_compare = Gitlab::Git::Compare.new( target_project.repository.raw_repository, target_branch, diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 36c8b8ff575..a7d267cd6b4 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -38,15 +38,14 @@ GitOperationService = Struct.new(:user, :repository) do branch_name, source_branch_name, source_project) update_branch_with_hooks(branch_name) do |ref| - if repository.project != source_project - repository.fetch_ref( - source_project.repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch_name}", - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch_name}" - ) + if repository.project == source_project + yield(ref) + else + repository.with_tmp_ref( + source_project.repository, source_branch_name) do + yield(ref) + end end - - yield(ref) end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index bebfca7537b..a52a94c5ffa 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -16,9 +16,10 @@ module MergeRequests messages = validate_branches(merge_request) return build_failed(merge_request, messages) unless messages.empty? - compare = CompareService.new.execute( + compare = CompareService.new( merge_request.source_project, - merge_request.source_branch, + merge_request.source_branch + ).execute( merge_request.target_project, merge_request.target_branch, ) diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index b9cd49985dc..d4c3f14ec06 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -33,13 +33,15 @@ class EmailsOnPushWorker reverse_compare = false if action == :push - compare = CompareService.new.execute(project, after_sha, project, before_sha) + compare = CompareService.new(project, after_sha). + execute(project, before_sha) diff_refs = compare.diff_refs return false if compare.same if compare.commits.empty? - compare = CompareService.new.execute(project, before_sha, project, after_sha) + compare = CompareService.new(project, before_sha). + execute(project, after_sha) diff_refs = compare.diff_refs reverse_compare = true diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb index 3760f19aaa2..0a7fc58523f 100644 --- a/spec/services/compare_service_spec.rb +++ b/spec/services/compare_service_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' describe CompareService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } - let(:service) { described_class.new } + let(:service) { described_class.new(project, 'feature') } describe '#execute' do context 'compare with base, like feature...fix' do - subject { service.execute(project, 'feature', project, 'fix', straight: false) } + subject { service.execute(project, 'fix', straight: false) } it { expect(subject.diffs.size).to eq(1) } end context 'straight compare, like feature..fix' do - subject { service.execute(project, 'feature', project, 'fix', straight: true) } + subject { service.execute(project, 'fix', straight: true) } it { expect(subject.diffs.size).to eq(3) } end From 07b9b80a8833cf44ba804c9b8dfdf1550785fe83 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 19:11:52 +0800 Subject: [PATCH 047/174] Fix tests to use the new API --- app/services/compare_service.rb | 14 ++++--- .../projects/templates_controller_spec.rb | 3 +- spec/factories/projects.rb | 34 +++++++++++++++- ...project_owner_creates_license_file_spec.rb | 3 +- .../projects/issuable_templates_spec.rb | 40 ++++++++++++++++--- spec/lib/gitlab/git_access_spec.rb | 8 +++- .../gitlab/template/issue_template_spec.rb | 17 ++++---- .../template/merge_request_template_spec.rb | 17 ++++---- .../models/cycle_analytics/production_spec.rb | 8 +++- spec/models/cycle_analytics/staging_spec.rb | 8 ++-- .../merge_requests/resolve_service_spec.rb | 8 +++- spec/support/cycle_analytics_helpers.rb | 8 +++- 12 files changed, 128 insertions(+), 40 deletions(-) diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 4367cb5f615..199a015622e 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,29 +3,31 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - attr_reader :source_project, :source_sha + attr_reader :source_project, :source_branch - def initialize(new_source_project, source_branch) + def initialize(new_source_project, source_branch_name) @source_project = new_source_project - @source_sha = new_source_project.commit(source_branch).try(:sha) + @source_branch = new_source_project.commit(source_branch_name) end def execute(target_project, target_branch, straight: false) + source_sha = source_branch.try(:sha) + return unless source_sha # If compare with other project we need to fetch ref first if target_project == source_project - compare(target_project, target_branch, straight) + compare(source_sha, target_project, target_branch, straight) else target_project.repository.with_tmp_ref(source_project, source_branch) do - compare(target_project, target_branch, straight) + compare(source_sha, target_project, target_branch, straight) end end end private - def compare(target_project, target_branch, straight) + def compare(source_sha, target_project, target_branch, straight) raw_compare = Gitlab::Git::Compare.new( target_project.repository.raw_repository, target_branch, diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 19a152bcb05..78e54e92a56 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -14,7 +14,8 @@ describe Projects::TemplatesController do before do project.add_user(user, Gitlab::Access::MASTER) - project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false) + project.repository.commit_file(user, file_path_1, 'something valid', + message: 'test 3', branch_name: 'master', update: false) end describe '#show' do diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 1166498ddff..aa971e12a2e 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -91,8 +91,40 @@ FactoryGirl.define do factory :project, parent: :empty_project do path { 'gitlabhq' } - after :create do |project| + transient do + create_template nil + end + + after :create do |project, evaluator| TestEnv.copy_repo(project) + + if evaluator.create_template + args = evaluator.create_template + + project.add_user(args[:user], args[:access]) + + project.repository.commit_file( + args[:user], + ".gitlab/#{args[:path]}/bug.md", + 'something valid', + message: 'test 3', + branch_name: 'master', + update: false) + project.repository.commit_file( + args[:user], + ".gitlab/#{args[:path]}/template_test.md", + 'template_test', + message: 'test 1', + branch_name: 'master', + update: false) + project.repository.commit_file( + args[:user], + ".gitlab/#{args[:path]}/feature_proposal.md", + 'feature_proposal', + message: 'test 2', + branch_name: 'master', + update: false) + end end end diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index a521ce50f35..64094af29c0 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -6,7 +6,8 @@ feature 'project owner creates a license file', feature: true, js: true do let(:project_master) { create(:user) } let(:project) { create(:project) } background do - project.repository.remove_file(project_master, 'LICENSE', 'Remove LICENSE', 'master') + project.repository.remove_file(project_master, 'LICENSE', + message: 'Remove LICENSE', branch_name: 'master') project.team << [project_master, :master] login_as(project_master) visit namespace_project_path(project.namespace, project) diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 2f377312ea5..daf08067ed1 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -18,8 +18,20 @@ feature 'issuable templates', feature: true, js: true do let(:description_addition) { ' appending to description' } background do - project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) - project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false) + project.repository.commit_file( + user, + '.gitlab/issue_templates/bug.md', + template_content, + message: 'added issue template', + branch_name: 'master', + update: false) + project.repository.commit_file( + user, + '.gitlab/issue_templates/test.md', + longtemplate_content, + message: 'added issue template', + branch_name: 'master', + update: false) visit edit_namespace_project_issue_path project.namespace, project, issue fill_in :'issue[title]', with: 'test issue title' end @@ -68,7 +80,13 @@ feature 'issuable templates', feature: true, js: true do let(:issue) { create(:issue, author: user, assignee: user, project: project) } background do - project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) + project.repository.commit_file( + user, + '.gitlab/issue_templates/bug.md', + template_content, + message: 'added issue template', + branch_name: 'master', + update: false) visit edit_namespace_project_issue_path project.namespace, project, issue fill_in :'issue[title]', with: 'test issue title' fill_in :'issue[description]', with: prior_description @@ -87,7 +105,13 @@ feature 'issuable templates', feature: true, js: true do let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) } background do - project.repository.commit_file(user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false) + project.repository.commit_file( + user, + '.gitlab/merge_request_templates/feature-proposal.md', + template_content, + message: 'added merge request template', + branch_name: 'master', + update: false) visit edit_namespace_project_merge_request_path project.namespace, project, merge_request fill_in :'merge_request[title]', with: 'test merge request title' end @@ -112,7 +136,13 @@ feature 'issuable templates', feature: true, js: true do fork_project.team << [fork_user, :master] create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project) login_as fork_user - project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false) + project.repository.commit_file( + fork_user, + '.gitlab/merge_request_templates/feature-proposal.md', + template_content, + message: 'added merge request template', + branch_name: 'master', + update: false) visit edit_namespace_project_merge_request_path project.namespace, project, merge_request fill_in :'merge_request[title]', with: 'test merge request title' end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index f1d0a190002..a48287b7a75 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -209,7 +209,13 @@ describe Gitlab::GitAccess, lib: true do stub_git_hooks project.repository.add_branch(user, unprotected_branch, 'feature') target_branch = project.repository.lookup('feature') - source_branch = project.repository.commit_file(user, FFaker::InternetSE.login_user_name, FFaker::HipsterIpsum.paragraph, FFaker::HipsterIpsum.sentence, unprotected_branch, false) + source_branch = project.repository.commit_file( + user, + FFaker::InternetSE.login_user_name, + FFaker::HipsterIpsum.paragraph, + message: FFaker::HipsterIpsum.sentence, + branch_name: unprotected_branch, + update: false) rugged = project.repository.rugged author = { email: "email@example.com", time: Time.now, name: "Example Git User" } diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb index d2d334e6413..984236b74ce 100644 --- a/spec/lib/gitlab/template/issue_template_spec.rb +++ b/spec/lib/gitlab/template/issue_template_spec.rb @@ -4,16 +4,15 @@ describe Gitlab::Template::IssueTemplate do subject { described_class } let(:user) { create(:user) } - let(:project) { create(:project) } - let(:file_path_1) { '.gitlab/issue_templates/bug.md' } - let(:file_path_2) { '.gitlab/issue_templates/template_test.md' } - let(:file_path_3) { '.gitlab/issue_templates/feature_proposal.md' } - before do - project.add_user(user, Gitlab::Access::MASTER) - project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false) - project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false) - project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false) + let(:project) do + create(:project, + create_template: { + user: user, + access: Gitlab::Access::MASTER, + path: 'issue_templates' + } + ) end describe '.all' do diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb index ddf68c4cf78..e98a8beccdd 100644 --- a/spec/lib/gitlab/template/merge_request_template_spec.rb +++ b/spec/lib/gitlab/template/merge_request_template_spec.rb @@ -4,16 +4,15 @@ describe Gitlab::Template::MergeRequestTemplate do subject { described_class } let(:user) { create(:user) } - let(:project) { create(:project) } - let(:file_path_1) { '.gitlab/merge_request_templates/bug.md' } - let(:file_path_2) { '.gitlab/merge_request_templates/template_test.md' } - let(:file_path_3) { '.gitlab/merge_request_templates/feature_proposal.md' } - before do - project.add_user(user, Gitlab::Access::MASTER) - project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false) - project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false) - project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false) + let(:project) do + create(:project, + create_template: { + user: user, + access: Gitlab::Access::MASTER, + path: 'merge_request_templates' + } + ) end describe '.all' do diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 21b9c6e7150..97568c47903 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -21,7 +21,13 @@ describe 'CycleAnalytics#production', feature: true do ["production deploy happens after merge request is merged (along with other changes)", lambda do |context, data| # Make other changes on master - sha = context.project.repository.commit_file(context.user, context.random_git_name, "content", "commit message", 'master', false) + sha = context.project.repository.commit_file( + context.user, + context.random_git_name, + 'content', + message: 'commit message', + branch_name: 'master', + update: false) context.project.repository.commit(sha) context.deploy_master diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index dad653964b7..27c826110fb 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -28,10 +28,10 @@ describe 'CycleAnalytics#staging', feature: true do sha = context.project.repository.commit_file( context.user, context.random_git_name, - "content", - "commit message", - 'master', - false) + 'content', + message: 'commit message', + branch_name: 'master', + update: false) context.project.repository.commit(sha) context.deploy_master diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb index 388abb6a0df..a0e51681725 100644 --- a/spec/services/merge_requests/resolve_service_spec.rb +++ b/spec/services/merge_requests/resolve_service_spec.rb @@ -66,7 +66,13 @@ describe MergeRequests::ResolveService do context 'when the source project is a fork and does not contain the HEAD of the target branch' do let!(:target_head) do - project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false) + project.repository.commit_file( + user, + 'new-file-in-target', + '', + message: 'Add new file in target', + branch_name: 'conflict-start', + update: false) end before do diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb index 75c95d70951..6ed55289ed9 100644 --- a/spec/support/cycle_analytics_helpers.rb +++ b/spec/support/cycle_analytics_helpers.rb @@ -35,7 +35,13 @@ module CycleAnalyticsHelpers project.repository.add_branch(user, source_branch, 'master') end - sha = project.repository.commit_file(user, random_git_name, "content", "commit message", source_branch, false) + sha = project.repository.commit_file( + user, + random_git_name, + 'content', + message: 'commit message', + branch_name: source_branch, + update: false) project.repository.commit(sha) opts = { From 9c6563f64a3a770cccb9fcf3eb609416c2466080 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 19:46:51 +0800 Subject: [PATCH 048/174] rubocop prefers lisp style --- spec/lib/gitlab/template/issue_template_spec.rb | 4 +--- spec/lib/gitlab/template/merge_request_template_spec.rb | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb index 984236b74ce..4275fda5c74 100644 --- a/spec/lib/gitlab/template/issue_template_spec.rb +++ b/spec/lib/gitlab/template/issue_template_spec.rb @@ -10,9 +10,7 @@ describe Gitlab::Template::IssueTemplate do create_template: { user: user, access: Gitlab::Access::MASTER, - path: 'issue_templates' - } - ) + path: 'issue_templates' }) end describe '.all' do diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb index e98a8beccdd..708bb084eef 100644 --- a/spec/lib/gitlab/template/merge_request_template_spec.rb +++ b/spec/lib/gitlab/template/merge_request_template_spec.rb @@ -10,9 +10,7 @@ describe Gitlab::Template::MergeRequestTemplate do create_template: { user: user, access: Gitlab::Access::MASTER, - path: 'merge_request_templates' - } - ) + path: 'merge_request_templates' }) end describe '.all' do From e7599eb0b76f7ef199e8719377a212f53aaa17f8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Dec 2016 19:49:20 +0800 Subject: [PATCH 049/174] Should pass repository rather than project --- app/services/compare_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 199a015622e..fcbfe68f1a3 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -19,7 +19,8 @@ class CompareService if target_project == source_project compare(source_sha, target_project, target_branch, straight) else - target_project.repository.with_tmp_ref(source_project, source_branch) do + target_project.repository.with_tmp_ref( + source_project.repository, source_branch) do compare(source_sha, target_project, target_branch, straight) end end From 56f697466d288c3bff16cc270e5cb2c50c9c14c8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 9 Dec 2016 20:05:35 +0800 Subject: [PATCH 050/174] Introduce git_action and normalize previous_path Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19747456 --- app/models/repository.rb | 72 +++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 1684aa97567..9d554991ab4 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -862,32 +862,8 @@ class Repository [] end - actions.each do |action| - path = Gitlab::Git::PathHelper.normalize_path(action[:file_path]).to_s - - raise Gitlab::Git::Repository::InvalidBlobName.new("Invalid path") if - path.split('/').include?('..') - - case action[:action] - when :create, :update, :move - mode = - case action[:action] - when :update - index.get(path)[:mode] - when :move - index.get(action[:previous_path])[:mode] - end - mode ||= 0o100644 - - index.remove(action[:previous_path]) if action[:action] == :move - - content = action[:encoding] == 'base64' ? Base64.decode64(action[:content]) : action[:content] - oid = rugged.write(content, :blob) - - index.add(path: path, oid: oid, mode: mode) - when :delete - index.remove(path) - end + actions.each do |act| + git_action(index, act) end options = { @@ -1181,6 +1157,50 @@ class Repository private + def git_action(index, action) + path = normalize_path(action[:file_path]) + + if action[:action] == :move + previous_path = normalize_path(action[:previous_path]) + end + + case action[:action] + when :create, :update, :move + mode = + case action[:action] + when :update + index.get(path)[:mode] + when :move + index.get(previous_path)[:mode] + end + mode ||= 0o100644 + + index.remove(previous_path) if action[:action] == :move + + content = if action[:encoding] == 'base64' + Base64.decode64(action[:content]) + else + action[:content] + end + + oid = rugged.write(content, :blob) + + index.add(path: path, oid: oid, mode: mode) + when :delete + index.remove(path) + end + end + + def normalize_path(path) + pathname = Gitlab::Git::PathHelper.normalize_path(path) + + if pathname.each_filename.include?('..') + raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path') + end + + pathname.to_s + end + def refs_directory_exists? return false unless path_with_namespace From 5a115671b9d7c22daf8160d7284786d0f8b216cb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 9 Dec 2016 20:06:19 +0800 Subject: [PATCH 051/174] Use rugged.references.delete to delete reference Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19746468 --- app/models/repository.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 9d554991ab4..389d52f8c0f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1064,18 +1064,18 @@ class Repository end def with_tmp_ref(source_repository, source_branch_name) - random_string = SecureRandom.hex + tmp_ref = "refs/tmp/#{SecureRandom.hex}/head" fetch_ref( source_repository.path_to_repo, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch_name}", - "refs/tmp/#{random_string}/head" + tmp_ref ) yield ensure - FileUtils.rm_rf("#{path_to_repo}/refs/tmp/#{random_string}") + rugged.references.delete(tmp_ref) end def fetch_ref(source_path, source_ref, target_ref) From bb9d30590d4ca5b25d5020234916ce961acf15b6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 10 Dec 2016 00:40:23 +0800 Subject: [PATCH 052/174] Pass source_commit so that we save a few lookups --- app/models/repository.rb | 55 ++++++++++----------------- app/services/git_operation_service.rb | 25 ++++++------ spec/models/repository_spec.rb | 28 +++++++++++--- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 389d52f8c0f..4034a49ae63 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -849,15 +849,12 @@ class Repository GitOperationService.new(user, self).with_branch( branch_name, source_branch_name: source_branch_name, - source_project: source_project) do + source_project: source_project) do |source_commit| index = rugged.index - branch_commit = source_project.repository.find_branch( - source_branch_name || branch_name) - parents = if branch_commit - last_commit = branch_commit.dereferenced_target - index.read_tree(last_commit.raw_commit.tree) - [last_commit.sha] + parents = if source_commit + index.read_tree(source_commit.raw_commit.tree) + [source_commit.sha] else [] end @@ -904,17 +901,17 @@ class Repository end def merge(user, merge_request, options = {}) - our_commit = rugged.branches[merge_request.target_branch].target - their_commit = rugged.lookup(merge_request.diff_head_sha) - - raise "Invalid merge target" if our_commit.nil? - raise "Invalid merge source" if their_commit.nil? - - merge_index = rugged.merge_commits(our_commit, their_commit) - return false if merge_index.conflicts? - GitOperationService.new(user, self).with_branch( - merge_request.target_branch) do + merge_request.target_branch) do |source_commit| + our_commit = source_commit.raw_commit + their_commit = rugged.lookup(merge_request.diff_head_sha) + + raise 'Invalid merge target' unless our_commit + raise 'Invalid merge source' unless their_commit + + merge_index = rugged.merge_commits(our_commit, their_commit) + break if merge_index.conflicts? + actual_options = options.merge( parents: [our_commit, their_commit], tree: merge_index.write_tree(rugged), @@ -924,6 +921,8 @@ class Repository merge_request.update(in_progress_merge_commit_sha: commit_id) commit_id end + rescue Repository::CommitError # when merge_index.conflicts? + false end def revert( @@ -936,10 +935,8 @@ class Repository GitOperationService.new(user, self).with_branch( branch_name, source_branch_name: source_branch_name, - source_project: source_project) do + source_project: source_project) do |source_commit| - source_sha = source_project.repository.find_source_sha( - source_branch_name || branch_name) committer = user_to_committer(user) Rugged::Commit.create(rugged, @@ -947,7 +944,7 @@ class Repository author: committer, committer: committer, tree: revert_tree_id, - parents: [rugged.lookup(source_sha)]) + parents: [source_commit.sha]) end end @@ -961,10 +958,8 @@ class Repository GitOperationService.new(user, self).with_branch( branch_name, source_branch_name: source_branch_name, - source_project: source_project) do + source_project: source_project) do |source_commit| - source_sha = source_project.repository.find_source_sha( - source_branch_name || branch_name) committer = user_to_committer(user) Rugged::Commit.create(rugged, @@ -976,7 +971,7 @@ class Repository }, committer: committer, tree: cherry_pick_tree_id, - parents: [rugged.lookup(source_sha)]) + parents: [source_commit.sha]) end end @@ -1145,16 +1140,6 @@ class Repository end end - protected - - def find_source_sha(branch_name) - if branch_exists?(branch_name) - find_branch(branch_name).dereferenced_target.sha - else - Gitlab::Git::BLANK_SHA - end - end - private def git_action(index, action) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index a7d267cd6b4..62a9eda3eba 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -37,13 +37,16 @@ GitOperationService = Struct.new(:user, :repository) do check_with_branch_arguments!( branch_name, source_branch_name, source_project) - update_branch_with_hooks(branch_name) do |ref| + source_commit = source_project.repository.find_branch( + source_branch_name || branch_name).try(:dereferenced_target) + + update_branch_with_hooks(branch_name) do if repository.project == source_project - yield(ref) + yield(source_commit) else repository.with_tmp_ref( source_project.repository, source_branch_name) do - yield(ref) + yield(source_commit) end end end @@ -54,31 +57,29 @@ GitOperationService = Struct.new(:user, :repository) do def update_branch_with_hooks(branch_name) update_autocrlf_option - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - oldrev = Gitlab::Git::BLANK_SHA was_empty = repository.empty? # Make commit - newrev = yield(ref) + newrev = yield unless newrev raise Repository::CommitError.new('Failed to create commit') end branch = repository.find_branch(branch_name) - oldrev = if repository.rugged.lookup(newrev).parent_ids.empty? || - branch.nil? - Gitlab::Git::BLANK_SHA + oldrev = if branch + # This could verify we're not losing commits + repository.rugged.merge_base(newrev, branch.target) else - repository.rugged.merge_base( - newrev, branch.dereferenced_target.sha) + Gitlab::Git::BLANK_SHA end + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name with_hooks_and_update_ref(ref, oldrev, newrev) do # If repo was empty expire cache repository.after_create if was_empty repository.after_create_branch if was_empty || - oldrev == Gitlab::Git::BLANK_SHA + Gitlab::Git.blank_ref?(oldrev) end newrev diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 9cc13a9a25b..a61d7f0c76d 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -257,6 +257,24 @@ describe Repository, models: true do expect(newdir.path).to eq('newdir') end + context "when committing to another project" do + let(:forked_project) { create(:project) } + + it "creates a fork and commit to the forked project" do + expect do + repository.commit_dir(user, 'newdir', + message: 'Create newdir', branch_name: 'patch', + source_branch_name: 'master', source_project: forked_project) + end.to change { repository.commits('master').count }.by(0) + + expect(repository.branch_exists?('patch')).to be_truthy + expect(forked_project.repository.branch_exists?('patch')).to be_falsy + + newdir = repository.tree('patch', 'newdir') + expect(newdir.path).to eq('newdir') + end + end + context "when an author is specified" do it "uses the given email/name to set the commit's author" do expect do @@ -758,9 +776,9 @@ describe Repository, models: true do end context 'when the update adds more than one commit' do - it 'runs without errors' do - old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' + let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' } + it 'runs without errors' do # old_rev is an ancestor of new_rev expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev) @@ -779,10 +797,10 @@ describe Repository, models: true do end context 'when the update would remove commits from the target branch' do - it 'raises an exception' do - branch = 'master' - old_rev = repository.find_branch(branch).dereferenced_target.sha + let(:branch) { 'master' } + let(:old_rev) { repository.find_branch(branch).dereferenced_target.sha } + it 'raises an exception' do # The 'master' branch is NOT an ancestor of new_rev. expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev) From 3e01385bca92dc8c0df3aa4032cc58d708dc0ff5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 10 Dec 2016 01:23:49 +0800 Subject: [PATCH 053/174] Should pass branch name, not commit object! --- app/services/compare_service.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index fcbfe68f1a3..31c371c4b34 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,15 +3,16 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - attr_reader :source_project, :source_branch + attr_reader :source_project, :source_branch_name - def initialize(new_source_project, source_branch_name) + def initialize(new_source_project, new_source_branch_name) @source_project = new_source_project - @source_branch = new_source_project.commit(source_branch_name) + @source_branch_name = new_source_branch_name end def execute(target_project, target_branch, straight: false) - source_sha = source_branch.try(:sha) + source_sha = source_project.repository. + commit(source_branch_name).try(:sha) return unless source_sha @@ -20,7 +21,7 @@ class CompareService compare(source_sha, target_project, target_branch, straight) else target_project.repository.with_tmp_ref( - source_project.repository, source_branch) do + source_project.repository, source_branch_name) do compare(source_sha, target_project, target_branch, straight) end end From c0dfa0c609805558b30f11046eedadfb8a14886d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 12 Dec 2016 23:07:52 +0800 Subject: [PATCH 054/174] Not sure why, but apparently SHA works better It's very weird that source_commit.raw_commit and rugged.branches[merge_request.target_branch].target should be completely the same. I checked with == and other values which proved that both should be the same, but still tests cannot pass for: spec/services/merge_requests/refresh_service_spec.rb I decided to give it up. We could just use SHA and that works fine anyway. --- app/models/repository.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 44f66b89600..7a7236993a8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -903,8 +903,8 @@ class Repository def merge(user, merge_request, options = {}) GitOperationService.new(user, self).with_branch( merge_request.target_branch) do |source_commit| - our_commit = source_commit.raw_commit - their_commit = rugged.lookup(merge_request.diff_head_sha) + our_commit = source_commit.sha + their_commit = merge_request.diff_head_sha raise 'Invalid merge target' unless our_commit raise 'Invalid merge source' unless their_commit From 26af4b5a61d5cdaffa7769336f40cd0861f6b1d4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 01:21:48 +0800 Subject: [PATCH 055/174] Also check blob path from source branch Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19747244 --- app/models/repository.rb | 55 +++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 7a7236993a8..2e706b770b2 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -738,19 +738,11 @@ class Repository message:, branch_name:, author_email: nil, author_name: nil, source_branch_name: nil, source_project: project) - if branch_exists?(branch_name) - # tree_entry is private - entry = raw_repository.send(:tree_entry, commit(branch_name), path) + check_tree_entry_for_dir(branch_name, path) - if entry - if entry[:type] == :blob - raise Gitlab::Git::Repository::InvalidBlobName.new( - "Directory already exists as a file") - else - raise Gitlab::Git::Repository::InvalidBlobName.new( - "Directory already exists") - end - end + if source_branch_name + source_project.repository. + check_tree_entry_for_dir(source_branch_name, path) end commit_file( @@ -773,11 +765,16 @@ class Repository message:, branch_name:, update: true, author_email: nil, author_name: nil, source_branch_name: nil, source_project: project) - if branch_exists?(branch_name) && update == false - # tree_entry is private - if raw_repository.send(:tree_entry, commit(branch_name), path) - raise Gitlab::Git::Repository::InvalidBlobName.new( - "Filename already exists; update not allowed") + unless update + error_message = "Filename already exists; update not allowed" + + if tree_entry_at(branch_name, path) + raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) + end + + if source_branch_name && + source_project.repository.tree_entry_at(source_branch_name, path) + raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) end end @@ -1140,6 +1137,30 @@ class Repository end end + protected + + def tree_entry_at(branch_name, path) + branch_exists?(branch_name) && + # tree_entry is private + raw_repository.send(:tree_entry, commit(branch_name), path) + end + + def check_tree_entry_for_dir(branch_name, path) + return unless branch_exists?(branch_name) + + entry = tree_entry_at(branch_name, path) + + return unless entry + + if entry[:type] == :blob + raise Gitlab::Git::Repository::InvalidBlobName.new( + "Directory already exists as a file") + else + raise Gitlab::Git::Repository::InvalidBlobName.new( + "Directory already exists") + end + end + private def git_action(index, action) From dc4b3dd0ae5d6e0b55ba6723e5deff6eee127409 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 01:47:17 +0800 Subject: [PATCH 056/174] Fix source_project and also pass source_project Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19747556 --- app/controllers/concerns/creates_commit.rb | 4 ++-- app/services/commits/change_service.rb | 1 + app/services/files/create_dir_service.rb | 1 + app/services/files/create_service.rb | 1 + app/services/files/delete_service.rb | 1 + app/services/files/multi_service.rb | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index d2fcb2efd6a..a94077c2bd4 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -6,12 +6,12 @@ module CreatesCommit source_branch = @ref if @ref && @repository.find_branch(@ref) commit_params = @commit_params.merge( - source_project: @project, + source_project: @tree_edit_project, source_branch: source_branch, target_branch: @target_branch ) - result = service.new(@tree_edit_project, current_user, commit_params).execute + result = service.new(@project, current_user, commit_params).execute if result[:status] == :success update_flash_notice(success_notice) diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 99d459908e7..9c630f5bbf1 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -37,6 +37,7 @@ module Commits @commit, into, tree_id, + source_project: @source_project, source_branch_name: @target_branch) success diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb index 4a2b2e8fcaf..ee4e130a38f 100644 --- a/app/services/files/create_dir_service.rb +++ b/app/services/files/create_dir_service.rb @@ -8,6 +8,7 @@ module Files branch_name: @target_branch, author_email: @author_email, author_name: @author_name, + source_project: @source_project, source_branch_name: @source_branch) end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index c95cb75f7cb..853c471666d 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -10,6 +10,7 @@ module Files update: false, author_email: @author_email, author_name: @author_name, + source_project: @source_project, source_branch_name: @source_branch) end diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index 45a9a559469..cfe532d49b3 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -8,6 +8,7 @@ module Files branch_name: @target_branch, author_email: @author_email, author_name: @author_name, + source_project: @source_project, source_branch_name: @source_branch) end end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index 42ed97ca3c0..f77e5d91103 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -10,6 +10,7 @@ module Files actions: params[:actions], author_email: @author_email, author_name: @author_name, + source_project: @source_project, source_branch_name: @source_branch ) end From 46d752ce218d833ff947bd4503de56300471a8cb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 02:03:04 +0800 Subject: [PATCH 057/174] Use a regular class for GitOperationService Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19747793 --- app/services/git_operation_service.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 62a9eda3eba..9a052f952cf 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -1,5 +1,12 @@ -GitOperationService = Struct.new(:user, :repository) do +class GitOperationService + attr_reader :user, :repository + + def initialize(new_user, new_repository) + @user = new_user + @repository = new_repository + end + def add_branch(branch_name, newrev) ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name oldrev = Gitlab::Git::BLANK_SHA From d03c605bd4a128d45179dd05f117a78aab7af6be Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 02:29:35 +0800 Subject: [PATCH 058/174] Unify parameters and callback after hooks Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19747856 --- app/services/git_operation_service.rb | 34 +++++++++---------- lib/gitlab/git.rb | 2 +- spec/models/repository_spec.rb | 7 ++-- .../git_garbage_collect_worker_spec.rb | 2 +- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 9a052f952cf..68b28231595 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -11,7 +11,7 @@ class GitOperationService ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name oldrev = Gitlab::Git::BLANK_SHA - with_hooks_and_update_ref(ref, oldrev, newrev) + update_ref_in_hooks(ref, newrev, oldrev) end def rm_branch(branch) @@ -19,14 +19,14 @@ class GitOperationService oldrev = branch.dereferenced_target.id newrev = Gitlab::Git::BLANK_SHA - with_hooks_and_update_ref(ref, oldrev, newrev) + update_ref_in_hooks(ref, newrev, oldrev) end def add_tag(tag_name, newrev, options = {}) ref = Gitlab::Git::TAG_REF_PREFIX + tag_name oldrev = Gitlab::Git::BLANK_SHA - with_hooks(ref, oldrev, newrev) do |service| + with_hooks(ref, newrev, oldrev) do |service| raw_tag = repository.rugged.tags.create(tag_name, newrev, options) service.newrev = raw_tag.target_id end @@ -82,25 +82,23 @@ class GitOperationService end ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - with_hooks_and_update_ref(ref, oldrev, newrev) do - # If repo was empty expire cache - repository.after_create if was_empty - repository.after_create_branch if was_empty || - Gitlab::Git.blank_ref?(oldrev) - end + update_ref_in_hooks(ref, newrev, oldrev) + + # If repo was empty expire cache + repository.after_create if was_empty + repository.after_create_branch if was_empty || + Gitlab::Git.blank_ref?(oldrev) newrev end - def with_hooks_and_update_ref(ref, oldrev, newrev) - with_hooks(ref, oldrev, newrev) do |service| - update_ref!(ref, newrev, oldrev) - - yield(service) if block_given? + def update_ref_in_hooks(ref, newrev, oldrev) + with_hooks(ref, newrev, oldrev) do + update_ref(ref, newrev, oldrev) end end - def with_hooks(ref, oldrev, newrev) + def with_hooks(ref, newrev, oldrev) result = nil GitHooksService.new.execute( @@ -116,7 +114,7 @@ class GitOperationService result end - def update_ref!(name, newrev, oldrev) + def update_ref(ref, newrev, oldrev) # We use 'git update-ref' because libgit2/rugged currently does not # offer 'compare and swap' ref updates. Without compare-and-swap we can # (and have!) accidentally reset the ref to an earlier state, clobbering @@ -125,12 +123,12 @@ class GitOperationService _, status = Gitlab::Popen.popen( command, repository.path_to_repo) do |stdin| - stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00") + stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00") end unless status.zero? raise Repository::CommitError.new( - "Could not update branch #{name.sub('refs/heads/', '')}." \ + "Could not update branch #{Gitlab::Git.branch_name(ref)}." \ " Please refresh and try again.") end end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 3cd515e4a3a..d3df3f1bca1 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -6,7 +6,7 @@ module Gitlab class << self def ref_name(ref) - ref.gsub(/\Arefs\/(tags|heads)\//, '') + ref.sub(/\Arefs\/(tags|heads)\//, '') end def branch_name(ref) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index a61d7f0c76d..65e96351033 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -829,7 +829,6 @@ describe Repository, models: true do context 'when target branch is different from source branch' do before do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, '']) - allow(repository).to receive(:update_ref!) end it 'expires branch cache' do @@ -1474,16 +1473,16 @@ describe Repository, models: true do end end - describe '#update_ref!' do + describe '#update_ref' do it 'can create a ref' do - GitOperationService.new(nil, repository).send(:update_ref!, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA) + GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA) expect(repository.find_branch('foobar')).not_to be_nil end it 'raises CommitError when the ref update fails' do expect do - GitOperationService.new(nil, repository).send(:update_ref!, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA) + GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA) end.to raise_error(Repository::CommitError) end end diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 2b31efbf631..5ef8cf1105b 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -108,7 +108,7 @@ describe GitGarbageCollectWorker do parents: [old_commit], ) GitOperationService.new(nil, project.repository).send( - :update_ref!, + :update_ref, "refs/heads/#{SecureRandom.hex(6)}", new_commit_sha, Gitlab::Git::BLANK_SHA From 944a8fa4d204ce7e9967f372a61657e75b4e88a0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 03:00:16 +0800 Subject: [PATCH 059/174] Use branch_exists? to check branches Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19747922 --- app/services/git_operation_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 68b28231595..b00fbcf9a79 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -150,14 +150,14 @@ class GitOperationService ' :source_project is different from current project' end - unless source_project.repository.commit(source_branch_name).try(:sha) + unless source_project.repository.branch_exists?(source_branch_name) raise Repository::CommitError.new( "Cannot find branch #{branch_name} nor" \ " #{source_branch_name} from" \ " #{source_project.path_with_namespace}") end elsif source_branch_name - unless repository.commit(source_branch_name).try(:sha) + unless repository.branch_exists?(source_branch_name) raise Repository::CommitError.new( "Cannot find branch #{branch_name} nor" \ " #{source_branch_name} from" \ From 56d131dcd52cba98e0eee253cab8bbf0b3b706df Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 03:03:20 +0800 Subject: [PATCH 060/174] Use ArgumentError error instead because it's a bug if it happens. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19747933 --- app/services/git_operation_service.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index b00fbcf9a79..0d1bd05e552 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -151,17 +151,17 @@ class GitOperationService end unless source_project.repository.branch_exists?(source_branch_name) - raise Repository::CommitError.new( + raise ArgumentError, "Cannot find branch #{branch_name} nor" \ " #{source_branch_name} from" \ - " #{source_project.path_with_namespace}") + " #{source_project.path_with_namespace}" end elsif source_branch_name unless repository.branch_exists?(source_branch_name) - raise Repository::CommitError.new( + raise ArgumentError, "Cannot find branch #{branch_name} nor" \ " #{source_branch_name} from" \ - " #{repository.project.path_with_namespace}") + " #{repository.project.path_with_namespace}" end end end From 99b556976370bfe0c052d15b6a8f0642256173fd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 03:33:43 +0800 Subject: [PATCH 061/174] Try to use those @mr variables for full correctness --- app/controllers/concerns/creates_commit.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index a94077c2bd4..f0e6fc4b3e8 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,14 +4,16 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - source_branch = @ref if @ref && @repository.find_branch(@ref) + source_branch = @ref if @ref && + @mr_source_project.repository.branch_exists?(@ref) commit_params = @commit_params.merge( - source_project: @tree_edit_project, + source_project: @mr_source_project, source_branch: source_branch, - target_branch: @target_branch + target_branch: @mr_target_branch ) - result = service.new(@project, current_user, commit_params).execute + result = service.new( + @mr_target_project, current_user, commit_params).execute if result[:status] == :success update_flash_notice(success_notice) From 14c4db2ae4efa1187476f11569df1b77c9c055fa Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 4 Jan 2017 22:31:06 +0800 Subject: [PATCH 062/174] Add a comment to explain why newrev should be updated Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_20301332 --- app/services/git_operation_service.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 0d1bd05e552..5acfdb0f9e2 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -1,4 +1,3 @@ - class GitOperationService attr_reader :user, :repository @@ -28,6 +27,9 @@ class GitOperationService with_hooks(ref, newrev, oldrev) do |service| raw_tag = repository.rugged.tags.create(tag_name, newrev, options) + + # If raw_tag is an annotated tag, we'll need to update newrev to point + # to the new revision. service.newrev = raw_tag.target_id end end From c1a75c3c0b59af7a9e6af3ff834adf56256aa2ce Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 4 Jan 2017 22:32:36 +0800 Subject: [PATCH 063/174] Prefer leading dots over trailing dots Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_20601323 --- app/controllers/projects/compare_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 325987199fa..d359920c91e 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -37,8 +37,8 @@ class Projects::CompareController < Projects::ApplicationController end def define_diff_vars - @compare = CompareService.new(@project, @head_ref). - execute(@project, @start_ref) + @compare = CompareService.new(@project, @head_ref) + .execute(@project, @start_ref) if @compare @commits = @compare.commits From ecac2f1122f093455e7b9e5d054157241dcd5cff Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 4 Jan 2017 22:50:01 +0800 Subject: [PATCH 064/174] Update the comment: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_20876648 --- app/services/git_operation_service.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 5acfdb0f9e2..f36c0b082e4 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -26,10 +26,12 @@ class GitOperationService oldrev = Gitlab::Git::BLANK_SHA with_hooks(ref, newrev, oldrev) do |service| + # We want to pass the OID of the tag object to the hooks. For an + # annotated tag we don't know that OID until after the tag object + # (raw_tag) is created in the repository. That is why we have to + # update the value after creating the tag object. Only the + # "post-receive" hook will receive the correct value in this case. raw_tag = repository.rugged.tags.create(tag_name, newrev, options) - - # If raw_tag is an annotated tag, we'll need to update newrev to point - # to the new revision. service.newrev = raw_tag.target_id end end From 05d742a047cca3ded10e6e3a545e211a3592c89c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 5 Jan 2017 01:10:35 +0800 Subject: [PATCH 065/174] Indent the way rubocop likes --- app/controllers/concerns/creates_commit.rb | 4 ++-- app/models/repository.rb | 2 +- app/services/git_operation_service.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 58de0917049..9d41c559b0b 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,8 +4,8 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - source_branch = @ref if @ref && - @mr_source_project.repository.branch_exists?(@ref) + source_branch = @ref if + @ref && @mr_source_project.repository.branch_exists?(@ref) commit_params = @commit_params.merge( source_project: @mr_source_project, source_branch: source_branch, diff --git a/app/models/repository.rb b/app/models/repository.rb index b01437acd8e..46995bdcb33 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -781,7 +781,7 @@ class Repository end if source_branch_name && - source_project.repository.tree_entry_at(source_branch_name, path) + source_project.repository.tree_entry_at(source_branch_name, path) raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index f36c0b082e4..00c85112873 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -90,8 +90,8 @@ class GitOperationService # If repo was empty expire cache repository.after_create if was_empty - repository.after_create_branch if was_empty || - Gitlab::Git.blank_ref?(oldrev) + repository.after_create_branch if + was_empty || Gitlab::Git.blank_ref?(oldrev) newrev end From 99ac0935271b1e99f4512e496104219045f1018e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 5 Jan 2017 01:52:21 +0800 Subject: [PATCH 066/174] Introduce Repository#with_repo_branch_commit We merge repository checks inside it so we don't have to check it on the call site, and we could also load the commit for the caller. This greatly reduce code duplication. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_20572919 --- app/models/repository.rb | 25 ++++++++++++++++--------- app/services/compare_service.rb | 18 ++++++------------ app/services/git_operation_service.rb | 18 ++++++------------ 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 46995bdcb33..b1a789492d3 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1063,19 +1063,26 @@ class Repository Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip) end - def with_tmp_ref(source_repository, source_branch_name) - tmp_ref = "refs/tmp/#{SecureRandom.hex}/head" + def with_repo_branch_commit(source_repository, source_branch_name) + branch_name_or_sha = + if source_repository == self + source_branch_name + else + tmp_ref = "refs/tmp/#{SecureRandom.hex}/head" - fetch_ref( - source_repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch_name}", - tmp_ref - ) + fetch_ref( + source_repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch_name}", + tmp_ref + ) - yield + source_repository.commit(source_branch_name).sha + end + + yield(commit(branch_name_or_sha)) ensure - rugged.references.delete(tmp_ref) + rugged.references.delete(tmp_ref) if tmp_ref end def fetch_ref(source_path, source_ref, target_ref) diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 31c371c4b34..d3d613661a6 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -11,19 +11,13 @@ class CompareService end def execute(target_project, target_branch, straight: false) - source_sha = source_project.repository. - commit(source_branch_name).try(:sha) - - return unless source_sha - # If compare with other project we need to fetch ref first - if target_project == source_project - compare(source_sha, target_project, target_branch, straight) - else - target_project.repository.with_tmp_ref( - source_project.repository, source_branch_name) do - compare(source_sha, target_project, target_branch, straight) - end + target_project.repository.with_repo_branch_commit( + source_project.repository, + source_branch_name) do |commit| + break unless commit + + compare(commit.sha, target_project, target_branch, straight) end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 00c85112873..ed9822cfee6 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -43,23 +43,17 @@ class GitOperationService def with_branch( branch_name, source_branch_name: nil, - source_project: repository.project) + source_project: repository.project, + &block) check_with_branch_arguments!( branch_name, source_branch_name, source_project) - source_commit = source_project.repository.find_branch( - source_branch_name || branch_name).try(:dereferenced_target) - update_branch_with_hooks(branch_name) do - if repository.project == source_project - yield(source_commit) - else - repository.with_tmp_ref( - source_project.repository, source_branch_name) do - yield(source_commit) - end - end + repository.with_repo_branch_commit( + source_project.repository, + source_branch_name || branch_name, + &block) end end From e01c692a35d817c09416356b549f473f63d78dc8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 5 Jan 2017 02:53:45 +0800 Subject: [PATCH 067/174] Remove tag with git hooks --- app/models/repository.rb | 19 +++++++++++-------- app/services/delete_tag_service.rb | 2 +- app/services/git_operation_service.rb | 12 +++++++++++- spec/models/repository_spec.rb | 5 +++-- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index b1a789492d3..e834936aa93 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -196,16 +196,14 @@ class Repository true end - # TODO: why we don't pass user here? - def rm_tag(tag_name) + def rm_tag(user, tag_name) before_remove_tag + tag = find_tag(tag_name) - begin - rugged.tags.delete(tag_name) - true - rescue Rugged::ReferenceError - false - end + GitOperationService.new(user, self).rm_tag(tag) + + after_remove_tag + true end def ref_names @@ -401,6 +399,11 @@ class Repository repository_event(:remove_tag) end + # Runs code after removing a tag. + def after_remove_tag + expire_tags_cache + end + def before_import expire_content_cache end diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index a44dee14a0f..9d4bffb93e9 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -7,7 +7,7 @@ class DeleteTagService < BaseService return error('No such tag', 404) end - if repository.rm_tag(tag_name) + if repository.rm_tag(current_user, tag_name) release = project.releases.find_by(tag: tag_name) release.destroy if release diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index ed9822cfee6..3b7f702e3ab 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -15,7 +15,7 @@ class GitOperationService def rm_branch(branch) ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name - oldrev = branch.dereferenced_target.id + oldrev = branch.target newrev = Gitlab::Git::BLANK_SHA update_ref_in_hooks(ref, newrev, oldrev) @@ -36,6 +36,16 @@ class GitOperationService end end + def rm_tag(tag) + ref = Gitlab::Git::TAG_REF_PREFIX + tag.name + oldrev = tag.target + newrev = Gitlab::Git::BLANK_SHA + + update_ref_in_hooks(ref, newrev, oldrev) do + repository.rugged.tags.delete(tag_name) + end + end + # Whenever `source_branch_name` is passed, if `branch_name` doesn't exist, # it would be created from `source_branch_name`. # If `source_project` is passed, and the branch doesn't exist, diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index b1fee342b57..0f43c5c019a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1419,9 +1419,10 @@ describe Repository, models: true do describe '#rm_tag' do it 'removes a tag' do expect(repository).to receive(:before_remove_tag) - expect(repository.rugged.tags).to receive(:delete).with('v1.1.0') - repository.rm_tag('v1.1.0') + repository.rm_tag(create(:user), 'v1.1.0') + + expect(repository.find_tag('v1.1.0')).to be_nil end end From 9244c81bc5f1c74966cb3ecb7b099fe7fd33689f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 5 Jan 2017 03:01:16 +0800 Subject: [PATCH 068/174] I think I am really confused, should be @tree_edit_project Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_20571990 --- app/controllers/concerns/creates_commit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 9d41c559b0b..a2e1d7c4653 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -13,7 +13,7 @@ module CreatesCommit ) result = service.new( - @mr_target_project, current_user, commit_params).execute + @tree_edit_project, current_user, commit_params).execute if result[:status] == :success update_flash_notice(success_notice) From 0b3b56b34d959322cced8a317138945c685015b8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 5 Jan 2017 22:58:04 +0800 Subject: [PATCH 069/174] Merge request terms are reversed for GitOperationService This is seriously confusing but a target branch in merge request, is actually the source branch for GitOperationService, which is the base branch. The source branch in a merge request, is the target branch for GitOperationService, which is where we want to make commits. Perhaps we should rename source branch in GitOperationService to base branch, and target branch to committing branch. --- app/controllers/concerns/creates_commit.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index a2e1d7c4653..025fca088bf 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -5,15 +5,15 @@ module CreatesCommit set_commit_variables source_branch = @ref if - @ref && @mr_source_project.repository.branch_exists?(@ref) + @ref && @mr_target_project.repository.branch_exists?(@ref) commit_params = @commit_params.merge( - source_project: @mr_source_project, + source_project: @mr_target_project, source_branch: source_branch, - target_branch: @mr_target_branch + target_branch: @mr_source_branch ) result = service.new( - @tree_edit_project, current_user, commit_params).execute + @mr_source_project, current_user, commit_params).execute if result[:status] == :success update_flash_notice(success_notice) From 5e12b3d841b0da1a2c6047de53a033107bbb5c32 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 5 Jan 2017 23:49:11 +0800 Subject: [PATCH 070/174] Prefer leading dots over trailing dots --- app/services/create_branch_service.rb | 4 ++-- app/workers/emails_on_push_worker.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 1b5e504573a..77459d8779d 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,7 +1,7 @@ class CreateBranchService < BaseService def execute(branch_name, ref) - result = ValidateNewBranchService.new(project, current_user). - execute(branch_name) + result = ValidateNewBranchService.new(project, current_user) + .execute(branch_name) return result if result[:status] == :error diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index d4c3f14ec06..f5ccc84c160 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -33,15 +33,15 @@ class EmailsOnPushWorker reverse_compare = false if action == :push - compare = CompareService.new(project, after_sha). - execute(project, before_sha) + compare = CompareService.new(project, after_sha) + .execute(project, before_sha) diff_refs = compare.diff_refs return false if compare.same if compare.commits.empty? - compare = CompareService.new(project, before_sha). - execute(project, after_sha) + compare = CompareService.new(project, before_sha) + .execute(project, after_sha) diff_refs = compare.diff_refs reverse_compare = true From ae86a1b9d3c9ca4ce592fa89085acd059ffc09a0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 02:11:27 +0800 Subject: [PATCH 071/174] Just trust set_commit_variables to set everything! Removing those weird setup in assign_change_commit_vars fixed all the failures in the tests. I still cannot say why but clearly we need to have better names. It's so confusing right now. We should seriously stop fiddling those instance variables. --- app/controllers/concerns/creates_commit.rb | 4 +--- app/controllers/projects/commit_controller.rb | 8 +++----- app/services/commits/change_service.rb | 3 ++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 025fca088bf..eafaed8a3d0 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,11 +4,9 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - source_branch = @ref if - @ref && @mr_target_project.repository.branch_exists?(@ref) commit_params = @commit_params.merge( source_project: @mr_target_project, - source_branch: source_branch, + source_branch: @mr_target_branch, target_branch: @mr_source_branch ) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 791ed88db30..44f34006049 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -40,7 +40,7 @@ class Projects::CommitController < Projects::ApplicationController end def revert - assign_change_commit_vars(@commit.revert_branch_name) + assign_change_commit_vars return render_404 if @target_branch.blank? @@ -49,7 +49,7 @@ class Projects::CommitController < Projects::ApplicationController end def cherry_pick - assign_change_commit_vars(@commit.cherry_pick_branch_name) + assign_change_commit_vars return render_404 if @target_branch.blank? @@ -110,11 +110,9 @@ class Projects::CommitController < Projects::ApplicationController @ci_pipelines = project.pipelines.where(sha: commit.sha) end - def assign_change_commit_vars(mr_source_branch) + def assign_change_commit_vars @commit = project.commit(params[:id]) @target_branch = params[:target_branch] - @mr_source_branch = mr_source_branch - @mr_target_branch = @target_branch @commit_params = { commit: @commit, create_merge_request: params[:create_merge_request].present? || different_project? diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 9b241aa8b04..60bd59a5d9f 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -5,6 +5,7 @@ module Commits def execute @source_project = params[:source_project] || @project + @source_branch = params[:source_branch] @target_branch = params[:target_branch] @commit = params[:commit] @create_merge_request = params[:create_merge_request].present? @@ -38,7 +39,7 @@ module Commits into, tree_id, source_project: @source_project, - source_branch_name: @target_branch) + source_branch_name: @source_branch) success else From a30f278bdee399346f199ada0e33f5c2d233d861 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 04:18:51 +0800 Subject: [PATCH 072/174] Fix for initial commit and remove unneeded args --- app/controllers/concerns/creates_commit.rb | 12 +++++++++--- app/services/commits/change_service.rb | 11 +++++++++-- lib/api/commits.rb | 2 -- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index eafaed8a3d0..f5f9cdeaec5 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,9 +4,10 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables + source_branch = @mr_target_branch unless initial_commit? commit_params = @commit_params.merge( source_project: @mr_target_project, - source_branch: @mr_target_branch, + source_branch: source_branch, target_branch: @mr_source_branch ) @@ -113,7 +114,7 @@ module CreatesCommit else # Merge request to this project @mr_target_project = @project - @mr_target_branch ||= @ref + @mr_target_branch = @ref || @target_branch end else # Edit file in fork @@ -121,7 +122,12 @@ module CreatesCommit # Merge request from fork to this project @mr_source_project = @tree_edit_project @mr_target_project = @project - @mr_target_branch ||= @ref + @mr_target_branch = @ref || @target_branch end end + + def initial_commit? + @mr_target_branch.nil? || + !@mr_target_project.repository.branch_exists?(@mr_target_branch) + end end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 60bd59a5d9f..4a5d8029413 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -26,8 +26,15 @@ module Commits def commit_change(action) raise NotImplementedError unless repository.respond_to?(action) - into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch - tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch) + if @create_merge_request + into = @commit.public_send("#{action}_branch_name") + tree_branch = @source_branch + else + into = tree_branch = @target_branch + end + + tree_id = repository.public_send( + "check_#{action}_content", @commit, tree_branch) if tree_id validate_target_branch(into) if @create_merge_request diff --git a/lib/api/commits.rb b/lib/api/commits.rb index cf2489dbb67..2c1da0902c9 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -140,8 +140,6 @@ module API commit_params = { commit: commit, create_merge_request: false, - source_project: user_project, - source_branch: commit.cherry_pick_branch_name, target_branch: params[:branch] } From dea589d635d4c41fbf0db721b132ea466c34cb4a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 04:41:17 +0800 Subject: [PATCH 073/174] Prefer leading dots over trailing dots --- app/models/merge_request_diff.rb | 4 ++-- app/services/commits/change_service.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 7946d8e123e..00c2a3695af 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -169,8 +169,8 @@ class MergeRequestDiff < ActiveRecord::Base # When compare merge request versions we want diff A..B instead of A...B # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. - CompareService.new(project, head_commit_sha). - execute(project, sha, straight: straight) + CompareService.new(project, head_commit_sha) + .execute(project, sha, straight: straight) end def commits_count diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 4a5d8029413..8d1dfbcea7d 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -70,8 +70,8 @@ module Commits # Temporary branch exists and contains the change commit return if repository.find_branch(new_branch) - result = ValidateNewBranchService.new(@project, current_user). - execute(new_branch) + result = ValidateNewBranchService.new(@project, current_user) + .execute(new_branch) if result[:status] == :error raise ChangeError, "There was an error creating the source branch: #{result[:message]}" From 593228ffe3b2e4ff82c4d63e5d5c59b835f70085 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 20:59:38 +0800 Subject: [PATCH 074/174] Don't set invalid @mr_source_branch when create_merge_request? --- app/controllers/concerns/creates_commit.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index f5f9cdeaec5..258791bb5cd 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -91,16 +91,13 @@ module CreatesCommit @mr_source_project != @mr_target_project end - def different_branch? - @mr_source_branch != @mr_target_branch || different_project? - end - def create_merge_request? - params[:create_merge_request].present? && different_branch? + params[:create_merge_request].present? end + # TODO: We should really clean this up def set_commit_variables - @mr_source_branch ||= @target_branch + @mr_source_branch = @target_branch unless create_merge_request? if can?(current_user, :push_code, @project) # Edit file in this project From a4b97b2cb61c03d08e25cf2cd7fcbb3f21611350 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 22:05:30 +0800 Subject: [PATCH 075/174] Rename source to base to avoid confusion from MR --- app/controllers/concerns/creates_commit.rb | 6 +- app/models/repository.rb | 78 +++++++++++----------- app/services/commits/change_service.rb | 10 +-- app/services/compare_service.rb | 12 ++-- app/services/files/base_service.rb | 10 +-- app/services/files/create_dir_service.rb | 4 +- app/services/files/create_service.rb | 6 +- app/services/files/delete_service.rb | 4 +- app/services/files/multi_service.rb | 6 +- app/services/files/update_service.rb | 6 +- app/services/git_operation_service.rb | 40 +++++------ 11 files changed, 91 insertions(+), 91 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 258791bb5cd..c503f8bf696 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,10 +4,10 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - source_branch = @mr_target_branch unless initial_commit? + base_branch = @mr_target_branch unless initial_commit? commit_params = @commit_params.merge( - source_project: @mr_target_project, - source_branch: source_branch, + base_project: @mr_target_project, + base_branch: base_branch, target_branch: @mr_source_branch ) diff --git a/app/models/repository.rb b/app/models/repository.rb index e834936aa93..a335c629a78 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -748,12 +748,12 @@ class Repository user, path, message:, branch_name:, author_email: nil, author_name: nil, - source_branch_name: nil, source_project: project) + base_branch_name: nil, base_project: project) check_tree_entry_for_dir(branch_name, path) - if source_branch_name - source_project.repository. - check_tree_entry_for_dir(source_branch_name, path) + if base_branch_name + base_project.repository. + check_tree_entry_for_dir(base_branch_name, path) end commit_file( @@ -765,8 +765,8 @@ class Repository update: false, author_email: author_email, author_name: author_name, - source_branch_name: source_branch_name, - source_project: source_project) + base_branch_name: base_branch_name, + base_project: base_project) end # rubocop:enable Metrics/ParameterLists @@ -775,7 +775,7 @@ class Repository user, path, content, message:, branch_name:, update: true, author_email: nil, author_name: nil, - source_branch_name: nil, source_project: project) + base_branch_name: nil, base_project: project) unless update error_message = "Filename already exists; update not allowed" @@ -783,8 +783,8 @@ class Repository raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) end - if source_branch_name && - source_project.repository.tree_entry_at(source_branch_name, path) + if base_branch_name && + base_project.repository.tree_entry_at(base_branch_name, path) raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) end end @@ -795,8 +795,8 @@ class Repository branch_name: branch_name, author_email: author_email, author_name: author_name, - source_branch_name: source_branch_name, - source_project: source_project, + base_branch_name: base_branch_name, + base_project: base_project, actions: [{ action: :create, file_path: path, content: content }]) @@ -808,7 +808,7 @@ class Repository user, path, content, message:, branch_name:, previous_path:, author_email: nil, author_name: nil, - source_branch_name: nil, source_project: project) + base_branch_name: nil, base_project: project) action = if previous_path && previous_path != path :move else @@ -821,8 +821,8 @@ class Repository branch_name: branch_name, author_email: author_email, author_name: author_name, - source_branch_name: source_branch_name, - source_project: source_project, + base_branch_name: base_branch_name, + base_project: base_project, actions: [{ action: action, file_path: path, content: content, @@ -835,15 +835,15 @@ class Repository user, path, message:, branch_name:, author_email: nil, author_name: nil, - source_branch_name: nil, source_project: project) + base_branch_name: nil, base_project: project) multi_action( user: user, message: message, branch_name: branch_name, author_email: author_email, author_name: author_name, - source_branch_name: source_branch_name, - source_project: source_project, + base_branch_name: base_branch_name, + base_project: base_project, actions: [{ action: :delete, file_path: path }]) end @@ -853,16 +853,16 @@ class Repository def multi_action( user:, branch_name:, message:, actions:, author_email: nil, author_name: nil, - source_branch_name: nil, source_project: project) + base_branch_name: nil, base_project: project) GitOperationService.new(user, self).with_branch( branch_name, - source_branch_name: source_branch_name, - source_project: source_project) do |source_commit| + base_branch_name: base_branch_name, + base_project: base_project) do |base_commit| index = rugged.index - parents = if source_commit - index.read_tree(source_commit.raw_commit.tree) - [source_commit.sha] + parents = if base_commit + index.read_tree(base_commit.raw_commit.tree) + [base_commit.sha] else [] end @@ -910,8 +910,8 @@ class Repository def merge(user, merge_request, options = {}) GitOperationService.new(user, self).with_branch( - merge_request.target_branch) do |source_commit| - our_commit = source_commit.sha + merge_request.target_branch) do |base_commit| + our_commit = base_commit.sha their_commit = merge_request.diff_head_sha raise 'Invalid merge target' unless our_commit @@ -935,15 +935,15 @@ class Repository def revert( user, commit, branch_name, revert_tree_id = nil, - source_branch_name: nil, source_project: project) + base_branch_name: nil, base_project: project) revert_tree_id ||= check_revert_content(commit, branch_name) return false unless revert_tree_id GitOperationService.new(user, self).with_branch( branch_name, - source_branch_name: source_branch_name, - source_project: source_project) do |source_commit| + base_branch_name: base_branch_name, + base_project: base_project) do |base_commit| committer = user_to_committer(user) @@ -952,21 +952,21 @@ class Repository author: committer, committer: committer, tree: revert_tree_id, - parents: [source_commit.sha]) + parents: [base_commit.sha]) end end def cherry_pick( user, commit, branch_name, cherry_pick_tree_id = nil, - source_branch_name: nil, source_project: project) + base_branch_name: nil, base_project: project) cherry_pick_tree_id ||= check_cherry_pick_content(commit, branch_name) return false unless cherry_pick_tree_id GitOperationService.new(user, self).with_branch( branch_name, - source_branch_name: source_branch_name, - source_project: source_project) do |source_commit| + base_branch_name: base_branch_name, + base_project: base_project) do |base_commit| committer = user_to_committer(user) @@ -979,7 +979,7 @@ class Repository }, committer: committer, tree: cherry_pick_tree_id, - parents: [source_commit.sha]) + parents: [base_commit.sha]) end end @@ -1066,20 +1066,20 @@ class Repository Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip) end - def with_repo_branch_commit(source_repository, source_branch_name) + def with_repo_branch_commit(base_repository, base_branch_name) branch_name_or_sha = - if source_repository == self - source_branch_name + if base_repository == self + base_branch_name else tmp_ref = "refs/tmp/#{SecureRandom.hex}/head" fetch_ref( - source_repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_branch_name}", + base_repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{base_branch_name}", tmp_ref ) - source_repository.commit(source_branch_name).sha + base_repository.commit(base_branch_name).sha end yield(commit(branch_name_or_sha)) diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 8d1dfbcea7d..1faa052e0ca 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -4,8 +4,8 @@ module Commits class ChangeError < StandardError; end def execute - @source_project = params[:source_project] || @project - @source_branch = params[:source_branch] + @base_project = params[:base_project] || @project + @base_branch = params[:base_branch] @target_branch = params[:target_branch] @commit = params[:commit] @create_merge_request = params[:create_merge_request].present? @@ -28,7 +28,7 @@ module Commits if @create_merge_request into = @commit.public_send("#{action}_branch_name") - tree_branch = @source_branch + tree_branch = @base_branch else into = tree_branch = @target_branch end @@ -45,8 +45,8 @@ module Commits @commit, into, tree_id, - source_project: @source_project, - source_branch_name: @source_branch) + base_project: @base_project, + base_branch_name: @base_branch) success else diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index d3d613661a6..2e4f8ee9dc8 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,18 +3,18 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - attr_reader :source_project, :source_branch_name + attr_reader :base_project, :base_branch_name - def initialize(new_source_project, new_source_branch_name) - @source_project = new_source_project - @source_branch_name = new_source_branch_name + def initialize(new_base_project, new_base_branch_name) + @base_project = new_base_project + @base_branch_name = new_base_branch_name end def execute(target_project, target_branch, straight: false) # If compare with other project we need to fetch ref first target_project.repository.with_repo_branch_commit( - source_project.repository, - source_branch_name) do |commit| + base_project.repository, + base_branch_name) do |commit| break unless commit compare(commit.sha, target_project, target_branch, straight) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 80e1d1d60f2..89f7dcbaa87 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -3,8 +3,8 @@ module Files class ValidationError < StandardError; end def execute - @source_project = params[:source_project] || @project - @source_branch = params[:source_branch] + @base_project = params[:base_project] || @project + @base_branch = params[:base_branch] @target_branch = params[:target_branch] @commit_message = params[:commit_message] @@ -22,7 +22,7 @@ module Files # Validate parameters validate - # Create new branch if it different from source_branch + # Create new branch if it different from base_branch validate_target_branch if different_branch? result = commit @@ -38,7 +38,7 @@ module Files private def different_branch? - @source_branch != @target_branch || @source_project != @project + @base_branch != @target_branch || @base_project != @project end def file_has_changed? @@ -59,7 +59,7 @@ module Files end unless project.empty_repo? - unless @source_project.repository.branch_exists?(@source_branch) + unless @base_project.repository.branch_exists?(@base_branch) raise_error('You can only create or edit files when you are on a branch') end diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb index ee4e130a38f..53b6d456e0d 100644 --- a/app/services/files/create_dir_service.rb +++ b/app/services/files/create_dir_service.rb @@ -8,8 +8,8 @@ module Files branch_name: @target_branch, author_email: @author_email, author_name: @author_name, - source_project: @source_project, - source_branch_name: @source_branch) + base_project: @base_project, + base_branch_name: @base_branch) end def validate diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 853c471666d..270dc6471aa 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -10,8 +10,8 @@ module Files update: false, author_email: @author_email, author_name: @author_name, - source_project: @source_project, - source_branch_name: @source_branch) + base_project: @base_project, + base_branch_name: @base_branch) end def validate @@ -34,7 +34,7 @@ module Files unless project.empty_repo? @file_path.slice!(0) if @file_path.start_with?('/') - blob = repository.blob_at_branch(@source_branch, @file_path) + blob = repository.blob_at_branch(@base_branch, @file_path) if blob raise_error('Your changes could not be committed because a file with the same name already exists') diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index cfe532d49b3..d5341b9e197 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -8,8 +8,8 @@ module Files branch_name: @target_branch, author_email: @author_email, author_name: @author_name, - source_project: @source_project, - source_branch_name: @source_branch) + base_project: @base_project, + base_branch_name: @base_branch) end end end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index f77e5d91103..ca13b887e06 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -10,8 +10,8 @@ module Files actions: params[:actions], author_email: @author_email, author_name: @author_name, - source_project: @source_project, - source_branch_name: @source_branch + base_project: @base_project, + base_branch_name: @base_branch ) end @@ -63,7 +63,7 @@ module Files end def last_commit - Gitlab::Git::Commit.last_for_path(repository, @source_branch, @file_path) + Gitlab::Git::Commit.last_for_path(repository, @base_branch, @file_path) end def regex_check(file) diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 5f671817cdb..f546b169550 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -9,8 +9,8 @@ module Files previous_path: @previous_path, author_email: @author_email, author_name: @author_name, - source_project: @source_project, - source_branch_name: @source_branch) + base_project: @base_project, + base_branch_name: @base_branch) end private @@ -25,7 +25,7 @@ module Files def last_commit @last_commit ||= Gitlab::Git::Commit. - last_for_path(@source_project.repository, @source_branch, @file_path) + last_for_path(@base_project.repository, @base_branch, @file_path) end end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 3b7f702e3ab..ec23407544c 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -46,23 +46,23 @@ class GitOperationService end end - # Whenever `source_branch_name` is passed, if `branch_name` doesn't exist, - # it would be created from `source_branch_name`. - # If `source_project` is passed, and the branch doesn't exist, - # it would try to find the source from it instead of current repository. + # Whenever `base_branch_name` is passed, if `branch_name` doesn't exist, + # it would be created from `base_branch_name`. + # If `base_project` is passed, and the branch doesn't exist, + # it would try to find the base from it instead of current repository. def with_branch( branch_name, - source_branch_name: nil, - source_project: repository.project, + base_branch_name: nil, + base_project: repository.project, &block) check_with_branch_arguments!( - branch_name, source_branch_name, source_project) + branch_name, base_branch_name, base_project) update_branch_with_hooks(branch_name) do repository.with_repo_branch_commit( - source_project.repository, - source_branch_name || branch_name, + base_project.repository, + base_branch_name || branch_name, &block) end end @@ -148,27 +148,27 @@ class GitOperationService end def check_with_branch_arguments!( - branch_name, source_branch_name, source_project) + branch_name, base_branch_name, base_project) return if repository.branch_exists?(branch_name) - if repository.project != source_project - unless source_branch_name + if repository.project != base_project + unless base_branch_name raise ArgumentError, - 'Should also pass :source_branch_name if' + - ' :source_project is different from current project' + 'Should also pass :base_branch_name if' + + ' :base_project is different from current project' end - unless source_project.repository.branch_exists?(source_branch_name) + unless base_project.repository.branch_exists?(base_branch_name) raise ArgumentError, "Cannot find branch #{branch_name} nor" \ - " #{source_branch_name} from" \ - " #{source_project.path_with_namespace}" + " #{base_branch_name} from" \ + " #{base_project.path_with_namespace}" end - elsif source_branch_name - unless repository.branch_exists?(source_branch_name) + elsif base_branch_name + unless repository.branch_exists?(base_branch_name) raise ArgumentError, "Cannot find branch #{branch_name} nor" \ - " #{source_branch_name} from" \ + " #{base_branch_name} from" \ " #{repository.project.path_with_namespace}" end end From 358501df2d3229f68be700d2fc57cd3c3e7e5042 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 22:20:02 +0800 Subject: [PATCH 076/174] Properly fix the edge case! --- app/controllers/concerns/creates_commit.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index c503f8bf696..516b1cac6ef 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -97,8 +97,6 @@ module CreatesCommit # TODO: We should really clean this up def set_commit_variables - @mr_source_branch = @target_branch unless create_merge_request? - if can?(current_user, :push_code, @project) # Edit file in this project @tree_edit_project = @project @@ -121,10 +119,25 @@ module CreatesCommit @mr_target_project = @project @mr_target_branch = @ref || @target_branch end + + @mr_source_branch = guess_mr_source_branch end def initial_commit? @mr_target_branch.nil? || !@mr_target_project.repository.branch_exists?(@mr_target_branch) end + + def guess_mr_source_branch + # XXX: Happens when viewing a commit without a branch. In this case, + # @target_branch would be the default branch for @mr_source_project, + # however we want a generated new branch here. Thus we can't use + # @target_branch, but should pass nil to indicate that we want a new + # branch instead of @target_branch. + return if + create_merge_request? && + @mr_source_project.repository.branch_exists?(@target_branch) + + @target_branch + end end From e3c36850a618ee2f7f9087b681e62d8a50e7b1b1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 22:49:23 +0800 Subject: [PATCH 077/174] Detect if we really want a new merge request properly --- app/controllers/concerns/creates_commit.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 516b1cac6ef..646d922cb24 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -92,7 +92,9 @@ module CreatesCommit end def create_merge_request? - params[:create_merge_request].present? + # XXX: Even if the field is set, if we're checking the same branch + # as the target branch, we don't want to create a merge request. + params[:create_merge_request].present? && @ref != @target_branch end # TODO: We should really clean this up @@ -136,7 +138,8 @@ module CreatesCommit # branch instead of @target_branch. return if create_merge_request? && - @mr_source_project.repository.branch_exists?(@target_branch) + # XXX: Don't understand why rubocop prefers this indention + @mr_source_project.repository.branch_exists?(@target_branch) @target_branch end From ccc73c455ba0b95b531c69414a6a1f47667f16b5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 23:29:13 +0800 Subject: [PATCH 078/174] Rename from base to start because base could mean merge base --- app/controllers/concerns/creates_commit.rb | 6 +- app/models/repository.rb | 78 +++++++++++----------- app/services/commits/change_service.rb | 10 +-- app/services/compare_service.rb | 12 ++-- app/services/files/base_service.rb | 10 +-- app/services/files/create_dir_service.rb | 4 +- app/services/files/create_service.rb | 6 +- app/services/files/delete_service.rb | 4 +- app/services/files/multi_service.rb | 6 +- app/services/files/update_service.rb | 6 +- app/services/git_operation_service.rb | 40 +++++------ 11 files changed, 91 insertions(+), 91 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 646d922cb24..2ece99aebc0 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -4,10 +4,10 @@ module CreatesCommit def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) set_commit_variables - base_branch = @mr_target_branch unless initial_commit? + start_branch = @mr_target_branch unless initial_commit? commit_params = @commit_params.merge( - base_project: @mr_target_project, - base_branch: base_branch, + start_project: @mr_target_project, + start_branch: start_branch, target_branch: @mr_source_branch ) diff --git a/app/models/repository.rb b/app/models/repository.rb index a335c629a78..f8bdfb602a9 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -748,12 +748,12 @@ class Repository user, path, message:, branch_name:, author_email: nil, author_name: nil, - base_branch_name: nil, base_project: project) + start_branch_name: nil, start_project: project) check_tree_entry_for_dir(branch_name, path) - if base_branch_name - base_project.repository. - check_tree_entry_for_dir(base_branch_name, path) + if start_branch_name + start_project.repository. + check_tree_entry_for_dir(start_branch_name, path) end commit_file( @@ -765,8 +765,8 @@ class Repository update: false, author_email: author_email, author_name: author_name, - base_branch_name: base_branch_name, - base_project: base_project) + start_branch_name: start_branch_name, + start_project: start_project) end # rubocop:enable Metrics/ParameterLists @@ -775,7 +775,7 @@ class Repository user, path, content, message:, branch_name:, update: true, author_email: nil, author_name: nil, - base_branch_name: nil, base_project: project) + start_branch_name: nil, start_project: project) unless update error_message = "Filename already exists; update not allowed" @@ -783,8 +783,8 @@ class Repository raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) end - if base_branch_name && - base_project.repository.tree_entry_at(base_branch_name, path) + if start_branch_name && + start_project.repository.tree_entry_at(start_branch_name, path) raise Gitlab::Git::Repository::InvalidBlobName.new(error_message) end end @@ -795,8 +795,8 @@ class Repository branch_name: branch_name, author_email: author_email, author_name: author_name, - base_branch_name: base_branch_name, - base_project: base_project, + start_branch_name: start_branch_name, + start_project: start_project, actions: [{ action: :create, file_path: path, content: content }]) @@ -808,7 +808,7 @@ class Repository user, path, content, message:, branch_name:, previous_path:, author_email: nil, author_name: nil, - base_branch_name: nil, base_project: project) + start_branch_name: nil, start_project: project) action = if previous_path && previous_path != path :move else @@ -821,8 +821,8 @@ class Repository branch_name: branch_name, author_email: author_email, author_name: author_name, - base_branch_name: base_branch_name, - base_project: base_project, + start_branch_name: start_branch_name, + start_project: start_project, actions: [{ action: action, file_path: path, content: content, @@ -835,15 +835,15 @@ class Repository user, path, message:, branch_name:, author_email: nil, author_name: nil, - base_branch_name: nil, base_project: project) + start_branch_name: nil, start_project: project) multi_action( user: user, message: message, branch_name: branch_name, author_email: author_email, author_name: author_name, - base_branch_name: base_branch_name, - base_project: base_project, + start_branch_name: start_branch_name, + start_project: start_project, actions: [{ action: :delete, file_path: path }]) end @@ -853,16 +853,16 @@ class Repository def multi_action( user:, branch_name:, message:, actions:, author_email: nil, author_name: nil, - base_branch_name: nil, base_project: project) + start_branch_name: nil, start_project: project) GitOperationService.new(user, self).with_branch( branch_name, - base_branch_name: base_branch_name, - base_project: base_project) do |base_commit| + start_branch_name: start_branch_name, + start_project: start_project) do |start_commit| index = rugged.index - parents = if base_commit - index.read_tree(base_commit.raw_commit.tree) - [base_commit.sha] + parents = if start_commit + index.read_tree(start_commit.raw_commit.tree) + [start_commit.sha] else [] end @@ -910,8 +910,8 @@ class Repository def merge(user, merge_request, options = {}) GitOperationService.new(user, self).with_branch( - merge_request.target_branch) do |base_commit| - our_commit = base_commit.sha + merge_request.target_branch) do |start_commit| + our_commit = start_commit.sha their_commit = merge_request.diff_head_sha raise 'Invalid merge target' unless our_commit @@ -935,15 +935,15 @@ class Repository def revert( user, commit, branch_name, revert_tree_id = nil, - base_branch_name: nil, base_project: project) + start_branch_name: nil, start_project: project) revert_tree_id ||= check_revert_content(commit, branch_name) return false unless revert_tree_id GitOperationService.new(user, self).with_branch( branch_name, - base_branch_name: base_branch_name, - base_project: base_project) do |base_commit| + start_branch_name: start_branch_name, + start_project: start_project) do |start_commit| committer = user_to_committer(user) @@ -952,21 +952,21 @@ class Repository author: committer, committer: committer, tree: revert_tree_id, - parents: [base_commit.sha]) + parents: [start_commit.sha]) end end def cherry_pick( user, commit, branch_name, cherry_pick_tree_id = nil, - base_branch_name: nil, base_project: project) + start_branch_name: nil, start_project: project) cherry_pick_tree_id ||= check_cherry_pick_content(commit, branch_name) return false unless cherry_pick_tree_id GitOperationService.new(user, self).with_branch( branch_name, - base_branch_name: base_branch_name, - base_project: base_project) do |base_commit| + start_branch_name: start_branch_name, + start_project: start_project) do |start_commit| committer = user_to_committer(user) @@ -979,7 +979,7 @@ class Repository }, committer: committer, tree: cherry_pick_tree_id, - parents: [base_commit.sha]) + parents: [start_commit.sha]) end end @@ -1066,20 +1066,20 @@ class Repository Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip) end - def with_repo_branch_commit(base_repository, base_branch_name) + def with_repo_branch_commit(start_repository, start_branch_name) branch_name_or_sha = - if base_repository == self - base_branch_name + if start_repository == self + start_branch_name else tmp_ref = "refs/tmp/#{SecureRandom.hex}/head" fetch_ref( - base_repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{base_branch_name}", + start_repository.path_to_repo, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", tmp_ref ) - base_repository.commit(base_branch_name).sha + start_repository.commit(start_branch_name).sha end yield(commit(branch_name_or_sha)) diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 1faa052e0ca..25e22f14e60 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -4,8 +4,8 @@ module Commits class ChangeError < StandardError; end def execute - @base_project = params[:base_project] || @project - @base_branch = params[:base_branch] + @start_project = params[:start_project] || @project + @start_branch = params[:start_branch] @target_branch = params[:target_branch] @commit = params[:commit] @create_merge_request = params[:create_merge_request].present? @@ -28,7 +28,7 @@ module Commits if @create_merge_request into = @commit.public_send("#{action}_branch_name") - tree_branch = @base_branch + tree_branch = @start_branch else into = tree_branch = @target_branch end @@ -45,8 +45,8 @@ module Commits @commit, into, tree_id, - base_project: @base_project, - base_branch_name: @base_branch) + start_project: @start_project, + start_branch_name: @start_branch) success else diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 2e4f8ee9dc8..ab4c02a97a0 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,18 +3,18 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - attr_reader :base_project, :base_branch_name + attr_reader :start_project, :start_branch_name - def initialize(new_base_project, new_base_branch_name) - @base_project = new_base_project - @base_branch_name = new_base_branch_name + def initialize(new_start_project, new_start_branch_name) + @start_project = new_start_project + @start_branch_name = new_start_branch_name end def execute(target_project, target_branch, straight: false) # If compare with other project we need to fetch ref first target_project.repository.with_repo_branch_commit( - base_project.repository, - base_branch_name) do |commit| + start_project.repository, + start_branch_name) do |commit| break unless commit compare(commit.sha, target_project, target_branch, straight) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 89f7dcbaa87..0a25f56d24c 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -3,8 +3,8 @@ module Files class ValidationError < StandardError; end def execute - @base_project = params[:base_project] || @project - @base_branch = params[:base_branch] + @start_project = params[:start_project] || @project + @start_branch = params[:start_branch] @target_branch = params[:target_branch] @commit_message = params[:commit_message] @@ -22,7 +22,7 @@ module Files # Validate parameters validate - # Create new branch if it different from base_branch + # Create new branch if it different from start_branch validate_target_branch if different_branch? result = commit @@ -38,7 +38,7 @@ module Files private def different_branch? - @base_branch != @target_branch || @base_project != @project + @start_branch != @target_branch || @start_project != @project end def file_has_changed? @@ -59,7 +59,7 @@ module Files end unless project.empty_repo? - unless @base_project.repository.branch_exists?(@base_branch) + unless @start_project.repository.branch_exists?(@start_branch) raise_error('You can only create or edit files when you are on a branch') end diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb index 53b6d456e0d..858de5f0538 100644 --- a/app/services/files/create_dir_service.rb +++ b/app/services/files/create_dir_service.rb @@ -8,8 +8,8 @@ module Files branch_name: @target_branch, author_email: @author_email, author_name: @author_name, - base_project: @base_project, - base_branch_name: @base_branch) + start_project: @start_project, + start_branch_name: @start_branch) end def validate diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 270dc6471aa..88dd7bbaedb 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -10,8 +10,8 @@ module Files update: false, author_email: @author_email, author_name: @author_name, - base_project: @base_project, - base_branch_name: @base_branch) + start_project: @start_project, + start_branch_name: @start_branch) end def validate @@ -34,7 +34,7 @@ module Files unless project.empty_repo? @file_path.slice!(0) if @file_path.start_with?('/') - blob = repository.blob_at_branch(@base_branch, @file_path) + blob = repository.blob_at_branch(@start_branch, @file_path) if blob raise_error('Your changes could not be committed because a file with the same name already exists') diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index d5341b9e197..50f0ffcac9f 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -8,8 +8,8 @@ module Files branch_name: @target_branch, author_email: @author_email, author_name: @author_name, - base_project: @base_project, - base_branch_name: @base_branch) + start_project: @start_project, + start_branch_name: @start_branch) end end end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index ca13b887e06..6ba868df04d 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -10,8 +10,8 @@ module Files actions: params[:actions], author_email: @author_email, author_name: @author_name, - base_project: @base_project, - base_branch_name: @base_branch + start_project: @start_project, + start_branch_name: @start_branch ) end @@ -63,7 +63,7 @@ module Files end def last_commit - Gitlab::Git::Commit.last_for_path(repository, @base_branch, @file_path) + Gitlab::Git::Commit.last_for_path(repository, @start_branch, @file_path) end def regex_check(file) diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index f546b169550..a71fe61a4b6 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -9,8 +9,8 @@ module Files previous_path: @previous_path, author_email: @author_email, author_name: @author_name, - base_project: @base_project, - base_branch_name: @base_branch) + start_project: @start_project, + start_branch_name: @start_branch) end private @@ -25,7 +25,7 @@ module Files def last_commit @last_commit ||= Gitlab::Git::Commit. - last_for_path(@base_project.repository, @base_branch, @file_path) + last_for_path(@start_project.repository, @start_branch, @file_path) end end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index ec23407544c..2b2ba0870a4 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -46,23 +46,23 @@ class GitOperationService end end - # Whenever `base_branch_name` is passed, if `branch_name` doesn't exist, - # it would be created from `base_branch_name`. - # If `base_project` is passed, and the branch doesn't exist, - # it would try to find the base from it instead of current repository. + # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist, + # it would be created from `start_branch_name`. + # If `start_project` is passed, and the branch doesn't exist, + # it would try to find the commits from it instead of current repository. def with_branch( branch_name, - base_branch_name: nil, - base_project: repository.project, + start_branch_name: nil, + start_project: repository.project, &block) check_with_branch_arguments!( - branch_name, base_branch_name, base_project) + branch_name, start_branch_name, start_project) update_branch_with_hooks(branch_name) do repository.with_repo_branch_commit( - base_project.repository, - base_branch_name || branch_name, + start_project.repository, + start_branch_name || branch_name, &block) end end @@ -148,27 +148,27 @@ class GitOperationService end def check_with_branch_arguments!( - branch_name, base_branch_name, base_project) + branch_name, start_branch_name, start_project) return if repository.branch_exists?(branch_name) - if repository.project != base_project - unless base_branch_name + if repository.project != start_project + unless start_branch_name raise ArgumentError, - 'Should also pass :base_branch_name if' + - ' :base_project is different from current project' + 'Should also pass :start_branch_name if' + + ' :start_project is different from current project' end - unless base_project.repository.branch_exists?(base_branch_name) + unless start_project.repository.branch_exists?(start_branch_name) raise ArgumentError, "Cannot find branch #{branch_name} nor" \ - " #{base_branch_name} from" \ - " #{base_project.path_with_namespace}" + " #{start_branch_name} from" \ + " #{start_project.path_with_namespace}" end - elsif base_branch_name - unless repository.branch_exists?(base_branch_name) + elsif start_branch_name + unless repository.branch_exists?(start_branch_name) raise ArgumentError, "Cannot find branch #{branch_name} nor" \ - " #{base_branch_name} from" \ + " #{start_branch_name} from" \ " #{repository.project.path_with_namespace}" end end From a6394540327cd3919e5189a35a21b57800a104fc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 6 Jan 2017 23:50:08 +0800 Subject: [PATCH 079/174] Fix renaming --- lib/api/commits.rb | 2 +- lib/api/files.rb | 2 +- spec/features/projects/files/editing_a_file_spec.rb | 2 +- spec/lib/gitlab/diff/position_tracer_spec.rb | 6 +++--- spec/models/repository_spec.rb | 2 +- spec/services/files/update_service_spec.rb | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 2c1da0902c9..031759cdcdf 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -55,7 +55,7 @@ module API authorize! :push_code, user_project attrs = declared_params - attrs[:source_branch] = attrs[:branch_name] + attrs[:start_branch] = attrs[:branch_name] attrs[:target_branch] = attrs[:branch_name] attrs[:actions].map! do |action| action[:action] = action[:action].to_sym diff --git a/lib/api/files.rb b/lib/api/files.rb index 2e79e22e649..c58472de578 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -5,7 +5,7 @@ module API def commit_params(attrs) { file_path: attrs[:file_path], - source_branch: attrs[:branch_name], + start_branch: attrs[:branch_name], target_branch: attrs[:branch_name], commit_message: attrs[:commit_message], file_content: attrs[:content], diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb index fe047e00409..36a80d7575d 100644 --- a/spec/features/projects/files/editing_a_file_spec.rb +++ b/spec/features/projects/files/editing_a_file_spec.rb @@ -7,7 +7,7 @@ feature 'User wants to edit a file', feature: true do let(:user) { create(:user) } let(:commit_params) do { - source_branch: project.default_branch, + start_branch: project.default_branch, target_branch: project.default_branch, commit_message: "Committing First Update", file_path: ".gitignore", diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index c268f84c759..f77ab016e9b 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -99,7 +99,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do Files::CreateService.new( project, current_user, - source_branch: branch_name, + start_branch: branch_name, target_branch: branch_name, commit_message: "Create file", file_path: file_name, @@ -112,7 +112,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do Files::UpdateService.new( project, current_user, - source_branch: branch_name, + start_branch: branch_name, target_branch: branch_name, commit_message: "Update file", file_path: file_name, @@ -125,7 +125,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do Files::DeleteService.new( project, current_user, - source_branch: branch_name, + start_branch: branch_name, target_branch: branch_name, commit_message: "Delete file", file_path: file_name diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 0f43c5c019a..36564a3f9e0 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -280,7 +280,7 @@ describe Repository, models: true do expect do repository.commit_dir(user, 'newdir', message: 'Create newdir', branch_name: 'patch', - source_branch_name: 'master', source_project: forked_project) + start_branch_name: 'master', start_project: forked_project) end.to change { repository.commits('master').count }.by(0) expect(repository.branch_exists?('patch')).to be_truthy diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb index 6fadee9304b..35e6e139238 100644 --- a/spec/services/files/update_service_spec.rb +++ b/spec/services/files/update_service_spec.rb @@ -17,8 +17,8 @@ describe Files::UpdateService do file_content: new_contents, file_content_encoding: "text", last_commit_sha: last_commit_sha, - source_project: project, - source_branch: project.default_branch, + start_project: project, + start_branch: project.default_branch, target_branch: target_branch } end From 09f1a9e320306677d0a94c8f8d7b58c3024c3ed7 Mon Sep 17 00:00:00 2001 From: Allison Whilden Date: Sat, 21 Jan 2017 17:00:55 -0800 Subject: [PATCH 080/174] [ci skip] UX guide: Update animation guidance to 100ms --- doc/development/ux_guide/animation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md index 903e54bf9dc..5dae4bcc905 100644 --- a/doc/development/ux_guide/animation.md +++ b/doc/development/ux_guide/animation.md @@ -19,7 +19,7 @@ Easing specifies the rate of change of a parameter over time (see [easings.net]( ### Hover -Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `200ms linear` transition for a color hover effect. +Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `100ms - 150ms linear` transition for a color hover effect. View the [interactive example](http://codepen.io/awhildy/full/GNyEvM/) here. From bc9c245b87375abafd9050648bf020b879172a79 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 10 Jan 2017 19:43:58 +0100 Subject: [PATCH 081/174] Chat Commands have presenters This improves the styling and readability of the code. This is supported by both Mattermost and Slack. --- .../chat_slash_commands_service.rb | 22 +-- lib/gitlab/chat_commands/base_command.rb | 4 - lib/gitlab/chat_commands/command.rb | 22 +-- lib/gitlab/chat_commands/deploy.rb | 24 +-- lib/gitlab/chat_commands/issue_create.rb | 18 +- lib/gitlab/chat_commands/issue_search.rb | 10 +- lib/gitlab/chat_commands/issue_show.rb | 8 +- lib/gitlab/chat_commands/presenter.rb | 131 -------------- lib/gitlab/chat_commands/presenters/access.rb | 22 +++ lib/gitlab/chat_commands/presenters/base.rb | 73 ++++++++ lib/gitlab/chat_commands/presenters/deploy.rb | 24 +++ .../chat_commands/presenters/issuable.rb | 33 ++++ .../chat_commands/presenters/list_issues.rb | 32 ++++ .../chat_commands/presenters/show_issue.rb | 38 +++++ lib/mattermost/client.rb | 41 ----- lib/mattermost/command.rb | 10 -- lib/mattermost/error.rb | 3 - lib/mattermost/session.rb | 160 ------------------ lib/mattermost/team.rb | 7 - spec/lib/gitlab/chat_commands/command_spec.rb | 60 +------ spec/lib/gitlab/chat_commands/deploy_spec.rb | 24 +-- .../gitlab/chat_commands/issue_create_spec.rb | 12 +- .../gitlab/chat_commands/issue_search_spec.rb | 12 +- .../gitlab/chat_commands/issue_show_spec.rb | 25 ++- .../chat_commands/presenters/access_spec.rb | 49 ++++++ .../chat_commands/presenters/deploy_spec.rb | 47 +++++ .../presenters/list_issues_spec.rb | 24 +++ .../presenters/show_issue_spec.rb | 27 +++ spec/lib/mattermost/client_spec.rb | 24 --- spec/lib/mattermost/command_spec.rb | 61 ------- spec/lib/mattermost/session_spec.rb | 123 -------------- spec/lib/mattermost/team_spec.rb | 66 -------- 32 files changed, 477 insertions(+), 759 deletions(-) delete mode 100644 lib/gitlab/chat_commands/presenter.rb create mode 100644 lib/gitlab/chat_commands/presenters/access.rb create mode 100644 lib/gitlab/chat_commands/presenters/base.rb create mode 100644 lib/gitlab/chat_commands/presenters/deploy.rb create mode 100644 lib/gitlab/chat_commands/presenters/issuable.rb create mode 100644 lib/gitlab/chat_commands/presenters/list_issues.rb create mode 100644 lib/gitlab/chat_commands/presenters/show_issue.rb delete mode 100644 lib/mattermost/client.rb delete mode 100644 lib/mattermost/command.rb delete mode 100644 lib/mattermost/error.rb delete mode 100644 lib/mattermost/session.rb delete mode 100644 lib/mattermost/team.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/access_spec.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb delete mode 100644 spec/lib/mattermost/client_spec.rb delete mode 100644 spec/lib/mattermost/command_spec.rb delete mode 100644 spec/lib/mattermost/session_spec.rb delete mode 100644 spec/lib/mattermost/team_spec.rb diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index 2bcff541cc0..608754f3035 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -28,20 +28,24 @@ class ChatSlashCommandsService < Service end def trigger(params) - return unless valid_token?(params[:token]) + return access_presenter unless valid_token?(params[:token]) user = find_chat_user(params) - unless user - url = authorize_chat_name_url(params) - return presenter.authorize_chat_name(url) - end - Gitlab::ChatCommands::Command.new(project, user, - params).execute + if user + Gitlab::ChatCommands::Command.new(project, user, params).execute + else + url = authorize_chat_name_url(params) + access_presenter(url).authorize + end end private + def access_presenter(url = nil) + Gitlab::ChatCommands::Presenters::Access.new(url) + end + def find_chat_user(params) ChatNames::FindUserService.new(self, params).execute end @@ -49,8 +53,4 @@ class ChatSlashCommandsService < Service def authorize_chat_name_url(params) ChatNames::AuthorizeUserService.new(self, params).execute end - - def presenter - Gitlab::ChatCommands::Presenter.new - end end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index 4fe53ce93a9..25da8474e95 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -42,10 +42,6 @@ module Gitlab def find_by_iid(iid) collection.find_by(iid: iid) end - - def presenter - Gitlab::ChatCommands::Presenter.new - end end end end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 145086755e4..ac7ee868402 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -13,9 +13,9 @@ module Gitlab if command if command.allowed?(project, current_user) - present command.new(project, current_user, params).execute(match) + command.new(project, current_user, params).execute(match) else - access_denied + Gitlab::ChatCommands::Presenters::Access.new.access_denied end else help(help_messages) @@ -25,7 +25,7 @@ module Gitlab def match_command match = nil service = available_commands.find do |klass| - match = klass.match(command) + match = klass.match(params[:text]) end [service, match] @@ -42,22 +42,6 @@ module Gitlab klass.available?(project) end end - - def command - params[:text] - end - - def help(messages) - presenter.help(messages, params[:command]) - end - - def access_denied - presenter.access_denied - end - - def present(resource) - presenter.present(resource) - end end end end diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb index 7127d2f6d04..458d90f84e8 100644 --- a/lib/gitlab/chat_commands/deploy.rb +++ b/lib/gitlab/chat_commands/deploy.rb @@ -1,8 +1,6 @@ module Gitlab module ChatCommands class Deploy < BaseCommand - include Gitlab::Routing.url_helpers - def self.match(text) /\Adeploy\s+(?\S+.*)\s+to+\s+(?\S+.*)\z/.match(text) end @@ -24,35 +22,29 @@ module Gitlab to = match[:to] actions = find_actions(from, to) - return unless actions.present? - if actions.one? - play!(from, to, actions.first) + if actions.none? + Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions + elsif actions.one? + action = play!(from, to, actions.first) + Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to) else - Result.new(:error, 'Too many actions defined') + Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions end end private def play!(from, to, action) - new_action = action.play(current_user) - - Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.") + action.play(current_user) end def find_actions(from, to) environment = project.environments.find_by(name: from) - return unless environment + return [] unless environment environment.actions_for(to).select(&:starts_environment?) end - - def url(subject) - polymorphic_url( - [subject.project.namespace.becomes(Namespace), subject.project, subject] - ) - end end end end diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index cefb6775db8..a06f13b0f72 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -2,7 +2,7 @@ module Gitlab module ChatCommands class IssueCreate < IssueCommand def self.match(text) - # we can not match \n with the dot by passing the m modifier as than + # we can not match \n with the dot by passing the m modifier as than # the title and description are not seperated /\Aissue\s+(new|create)\s+(?[^\n]*)\n*(?<description>(.|\n)*)/.match(text) end @@ -19,8 +19,24 @@ module Gitlab title = match[:title] description = match[:description].to_s.rstrip + issue = create_issue(title: title, description: description) + + if issue.errors.any? + presenter(issue).display_errors + else + presenter(issue).present + end + end + + private + + def create_issue(title:, description:) Issues::CreateService.new(project, current_user, title: title, description: description).execute end + + def presenter(issue) + Gitlab::ChatCommands::Presenters::ShowIssue.new(issue) + end end end end diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb index 51bf80c800b..e2d3a0f466a 100644 --- a/lib/gitlab/chat_commands/issue_search.rb +++ b/lib/gitlab/chat_commands/issue_search.rb @@ -10,7 +10,15 @@ module Gitlab end def execute(match) - collection.search(match[:query]).limit(QUERY_LIMIT) + issues = collection.search(match[:query]).limit(QUERY_LIMIT) + + if issues.none? + Presenters::Access.new(issues).not_found + elsif issues.one? + Presenters::ShowIssue.new(issues.first).present + else + Presenters::ListIssues.new(issues).present + end end end end diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb index 2a45d49cf6b..9f3e1b9a64b 100644 --- a/lib/gitlab/chat_commands/issue_show.rb +++ b/lib/gitlab/chat_commands/issue_show.rb @@ -10,7 +10,13 @@ module Gitlab end def execute(match) - find_by_iid(match[:iid]) + issue = find_by_iid(match[:iid]) + + if issue + Gitlab::ChatCommands::Presenters::ShowIssue.new(issue).present + else + Gitlab::ChatCommands::Presenters::Access.new.not_found + end end end end diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb deleted file mode 100644 index 8930a21f406..00000000000 --- a/lib/gitlab/chat_commands/presenter.rb +++ /dev/null @@ -1,131 +0,0 @@ -module Gitlab - module ChatCommands - class Presenter - include Gitlab::Routing - - def authorize_chat_name(url) - message = if url - ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." - else - ":sweat_smile: Couldn't identify you, nor can I autorize you!" - end - - ephemeral_response(message) - end - - def help(commands, trigger) - if commands.none? - ephemeral_response("No commands configured") - else - commands.map! { |command| "#{trigger} #{command}" } - message = header_with_list("Available commands", commands) - - ephemeral_response(message) - end - end - - def present(subject) - return not_found unless subject - - if subject.is_a?(Gitlab::ChatCommands::Result) - show_result(subject) - elsif subject.respond_to?(:count) - if subject.none? - not_found - elsif subject.one? - single_resource(subject.first) - else - multiple_resources(subject) - end - else - single_resource(subject) - end - end - - def access_denied - ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") - end - - private - - def show_result(result) - case result.type - when :success - in_channel_response(result.message) - else - ephemeral_response(result.message) - end - end - - def not_found - ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") - end - - def single_resource(resource) - return error(resource) if resource.errors.any? || !resource.persisted? - - message = "#{title(resource)}:" - message << "\n\n#{resource.description}" if resource.try(:description) - - in_channel_response(message) - end - - def multiple_resources(resources) - titles = resources.map { |resource| title(resource) } - - message = header_with_list("Multiple results were found:", titles) - - ephemeral_response(message) - end - - def error(resource) - message = header_with_list("The action was not successful, because:", resource.errors.messages) - - ephemeral_response(message) - end - - def title(resource) - reference = resource.try(:to_reference) || resource.try(:id) - title = resource.try(:title) || resource.try(:name) - - "[#{reference} #{title}](#{url(resource)})" - end - - def header_with_list(header, items) - message = [header] - - items.each do |item| - message << "- #{item}" - end - - message.join("\n") - end - - def url(resource) - url_for( - [ - resource.project.namespace.becomes(Namespace), - resource.project, - resource - ] - ) - end - - def ephemeral_response(message) - { - response_type: :ephemeral, - text: message, - status: 200 - } - end - - def in_channel_response(message) - { - response_type: :in_channel, - text: message, - status: 200 - } - end - end - end -end diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb new file mode 100644 index 00000000000..6d18d745608 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/access.rb @@ -0,0 +1,22 @@ +module Gitlab::ChatCommands::Presenters + class Access < Gitlab::ChatCommands::Presenters::Base + def access_denied + ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).") + end + + def not_found + ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") + end + + def authorize + message = + if @resource + ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})." + else + ":sweat_smile: Couldn't identify you, nor can I autorize you!" + end + + ephemeral_response(text: message) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb new file mode 100644 index 00000000000..0897025d85f --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/base.rb @@ -0,0 +1,73 @@ +module Gitlab::ChatCommands::Presenters + class Base + include Gitlab::Routing.url_helpers + + def initialize(resource = nil) + @resource = resource + end + + def display_errors + message = header_with_list("The action was not successful, because:", @resource.errors.full_messages) + + ephemeral_response(text: message) + end + + private + + def header_with_list(header, items) + message = [header] + + items.each do |item| + message << "- #{item}" + end + + message.join("\n") + end + + def ephemeral_response(message) + response = { + response_type: :ephemeral, + status: 200 + }.merge(message) + + format_response(response) + end + + def in_channel_response(message) + response = { + response_type: :in_channel, + status: 200 + }.merge(message) + + format_response(response) + end + + def format_response(response) + response[:text] = format(response[:text]) if response.has_key?(:text) + + if response.has_key?(:attachments) + response[:attachments].each do |attachment| + attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext] + attachment[:text] = format(attachment[:text]) if attachment[:text] + end + end + + response + end + + # Convert Markdown to slacks format + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def resource_url + url_for( + [ + @resource.project.namespace.becomes(Namespace), + @resource.project, + @resource + ] + ) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb new file mode 100644 index 00000000000..4f6333812ff --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/deploy.rb @@ -0,0 +1,24 @@ +module Gitlab::ChatCommands::Presenters + class Deploy < Gitlab::ChatCommands::Presenters::Base + def present(from, to) + message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})." + in_channel_response(text: message) + end + + def no_actions + ephemeral_response(text: "No action found to be executed") + end + + def too_many_actions + ephemeral_response(text: "Too many actions defined") + end + + private + + def resource_url + polymorphic_url( + [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] + ) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb new file mode 100644 index 00000000000..9623387f188 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/issuable.rb @@ -0,0 +1,33 @@ +module Gitlab::ChatCommands::Presenters + class Issuable < Gitlab::ChatCommands::Presenters::Base + private + + def project + @resource.project + end + + def author + @resource.author + end + + def fields + [ + { + title: "Assignee", + value: @resource.assignee ? @resource.assignee.name : "_None_", + short: true + }, + { + title: "Milestone", + value: @resource.milestone ? @resource.milestone.title : "_None_", + short: true + }, + { + title: "Labels", + value: @resource.labels.any? ? @resource.label_names : "_None_", + short: true + } + ] + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/list_issues.rb b/lib/gitlab/chat_commands/presenters/list_issues.rb new file mode 100644 index 00000000000..5a7b3fca5c2 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/list_issues.rb @@ -0,0 +1,32 @@ +module Gitlab::ChatCommands::Presenters + class ListIssues < Gitlab::ChatCommands::Presenters::Base + def present + ephemeral_response(text: "Here are the issues I found:", attachments: attachments) + end + + private + + def attachments + @resource.map do |issue| + state = issue.open? ? "Open" : "Closed" + + { + fallback: "Issue #{issue.to_reference}: #{issue.title}", + color: "#d22852", + text: "[#{issue.to_reference}](#{url_for([namespace, project, issue])}) · #{issue.title} (#{state})", + mrkdwn_in: [ + "text" + ] + } + end + end + + def project + @project ||= @resource.first.project + end + + def namespace + @namespace ||= project.namespace.becomes(Namespace) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/show_issue.rb b/lib/gitlab/chat_commands/presenters/show_issue.rb new file mode 100644 index 00000000000..2a89c30b972 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/show_issue.rb @@ -0,0 +1,38 @@ +module Gitlab::ChatCommands::Presenters + class ShowIssue < Gitlab::ChatCommands::Presenters::Issuable + def present + in_channel_response(show_issue) + end + + private + + def show_issue + { + attachments: [ + { + title: @resource.title, + title_link: resource_url, + author_name: author.name, + author_icon: author.avatar_url, + fallback: "#{@resource.to_reference}: #{@resource.title}", + text: text, + fields: fields, + mrkdwn_in: [ + :title, + :text + ] + } + ] + } + end + + def text + message = "" + message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero? + message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero? + message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero? + + message + end + end +end diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb deleted file mode 100644 index ec2903b7ec6..00000000000 --- a/lib/mattermost/client.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Mattermost - class ClientError < Mattermost::Error; end - - class Client - attr_reader :user - - def initialize(user) - @user = user - end - - private - - def with_session(&blk) - Mattermost::Session.new(user).with_session(&blk) - end - - def json_get(path, options = {}) - with_session do |session| - json_response session.get(path, options) - end - end - - def json_post(path, options = {}) - with_session do |session| - json_response session.post(path, options) - end - end - - def json_response(response) - json_response = JSON.parse(response.body) - - unless response.success? - raise Mattermost::ClientError.new(json_response['message'] || 'Undefined error') - end - - json_response - rescue JSON::JSONError - raise Mattermost::ClientError.new('Cannot parse response') - end - end -end diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb deleted file mode 100644 index d1e4bb0eccf..00000000000 --- a/lib/mattermost/command.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Mattermost - class Command < Client - def create(params) - response = json_post("/api/v3/teams/#{params[:team_id]}/commands/create", - body: params.to_json) - - response['token'] - end - end -end diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb deleted file mode 100644 index 014df175be0..00000000000 --- a/lib/mattermost/error.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Mattermost - class Error < StandardError; end -end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb deleted file mode 100644 index 377cb7b1021..00000000000 --- a/lib/mattermost/session.rb +++ /dev/null @@ -1,160 +0,0 @@ -module Mattermost - class NoSessionError < Mattermost::Error - def message - 'No session could be set up, is Mattermost configured with Single Sign On?' - end - end - - class ConnectionError < Mattermost::Error; end - - # This class' prime objective is to obtain a session token on a Mattermost - # instance with SSO configured where this GitLab instance is the provider. - # - # The process depends on OAuth, but skips a step in the authentication cycle. - # For example, usually a user would click the 'login in GitLab' button on - # Mattermost, which would yield a 302 status code and redirects you to GitLab - # to approve the use of your account on Mattermost. Which would trigger a - # callback so Mattermost knows this request is approved and gets the required - # data to create the user account etc. - # - # This class however skips the button click, and also the approval phase to - # speed up the process and keep it without manual action and get a session - # going. - class Session - include Doorkeeper::Helpers::Controller - include HTTParty - - LEASE_TIMEOUT = 60 - - base_uri Settings.mattermost.host - - attr_accessor :current_resource_owner, :token - - def initialize(current_user) - @current_resource_owner = current_user - end - - def with_session - with_lease do - raise Mattermost::NoSessionError unless create - - begin - yield self - rescue Errno::ECONNREFUSED - raise Mattermost::NoSessionError - ensure - destroy - end - end - end - - # Next methods are needed for Doorkeeper - def pre_auth - @pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new( - Doorkeeper.configuration, server.client_via_uid, params) - end - - def authorization - @authorization ||= strategy.request - end - - def strategy - @strategy ||= server.authorization_request(pre_auth.response_type) - end - - def request - @request ||= OpenStruct.new(parameters: params) - end - - def params - Rack::Utils.parse_query(oauth_uri.query).symbolize_keys - end - - def get(path, options = {}) - handle_exceptions do - self.class.get(path, options.merge(headers: @headers)) - end - end - - def post(path, options = {}) - handle_exceptions do - self.class.post(path, options.merge(headers: @headers)) - end - end - - private - - def create - return unless oauth_uri - return unless token_uri - - @token = request_token - @headers = { - Authorization: "Bearer #{@token}" - } - - @token - end - - def destroy - post('/api/v3/users/logout') - end - - def oauth_uri - return @oauth_uri if defined?(@oauth_uri) - - @oauth_uri = nil - - response = get("/api/v3/oauth/gitlab/login", follow_redirects: false) - return unless 300 <= response.code && response.code < 400 - - redirect_uri = response.headers['location'] - return unless redirect_uri - - @oauth_uri = URI.parse(redirect_uri) - end - - def token_uri - @token_uri ||= - if oauth_uri - authorization.authorize.redirect_uri if pre_auth.authorizable? - end - end - - def request_token - response = get(token_uri, follow_redirects: false) - - if 200 <= response.code && response.code < 400 - response.headers['token'] - end - end - - def with_lease - lease_uuid = lease_try_obtain - raise NoSessionError unless lease_uuid - - begin - yield - ensure - Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid) - end - end - - def lease_key - "mattermost:session" - end - - def lease_try_obtain - lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) - lease.try_obtain - end - - def handle_exceptions - yield - rescue HTTParty::Error => e - raise Mattermost::ConnectionError.new(e.message) - rescue Errno::ECONNREFUSED - raise Mattermost::ConnectionError.new(e.message) - end - end -end diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb deleted file mode 100644 index 784eca6ab5a..00000000000 --- a/lib/mattermost/team.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Mattermost - class Team < Client - def all - json_get('/api/v3/teams/all') - end - end -end diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index a2d84977f58..b634df52b68 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -5,19 +5,7 @@ describe Gitlab::ChatCommands::Command, service: true do let(:user) { create(:user) } describe '#execute' do - subject do - described_class.new(project, user, params).execute - end - - context 'when no command is available' do - let(:params) { { text: 'issue show 1' } } - let(:project) { create(:project, has_external_issue_tracker: true) } - - it 'displays 404 messages' do - expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('404 not found') - end - end + subject { described_class.new(project, user, params).execute } context 'when an unknown command is triggered' do let(:params) { { command: '/gitlab', text: "unknown command 123" } } @@ -34,47 +22,7 @@ describe Gitlab::ChatCommands::Command, service: true do it 'rejects the actions' do expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('Whoops! That action is not allowed') - end - end - - context 'issue is successfully created' do - let(:params) { { text: "issue create my new issue" } } - - before do - project.team << [user, :master] - end - - it 'presents the issue' do - expect(subject[:text]).to match("my new issue") - end - - it 'shows a link to the new issue' do - expect(subject[:text]).to match(/\/issues\/\d+/) - end - end - - context 'searching for an issue' do - let(:params) { { text: 'issue search find me' } } - let!(:issue) { create(:issue, project: project, title: 'find me') } - - before do - project.team << [user, :master] - end - - context 'a single issue is found' do - it 'presents the issue' do - expect(subject[:text]).to match(issue.title) - end - end - - context 'multiple issues found' do - let!(:issue2) { create(:issue, project: project, title: "someone find me") } - - it 'shows a link to the new issue' do - expect(subject[:text]).to match(issue.title) - expect(subject[:text]).to match(issue2.title) - end + expect(subject[:text]).to start_with('Whoops! This action is not allowed') end end @@ -90,7 +38,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'and user can not create deployment' do it 'returns action' do expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('Whoops! That action is not allowed') + expect(subject[:text]).to start_with('Whoops! This action is not allowed') end end @@ -100,7 +48,7 @@ describe Gitlab::ChatCommands::Command, service: true do end it 'returns action' do - expect(subject[:text]).to include('Deployment from staging to production started.') + expect(subject[:text]).to include('Deployment started from staging to production') expect(subject[:response_type]).to be(:in_channel) end diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb index bd8099c92da..b3358a32161 100644 --- a/spec/lib/gitlab/chat_commands/deploy_spec.rb +++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb @@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do end context 'if no environment is defined' do - it 'returns nil' do - expect(subject).to be_nil + it 'does not execute an action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("No action found to be executed") end end @@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do let!(:deployment) { create(:deployment, environment: staging, deployable: build) } context 'without actions' do - it 'returns nil' do - expect(subject).to be_nil + it 'does not execute an action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("No action found to be executed") end end @@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do end it 'returns success result' do - expect(subject.type).to eq(:success) - expect(subject.message).to include('Deployment from staging to production started') + expect(subject[:response_type]).to be(:in_channel) + expect(subject[:text]).to start_with('Deployment started from staging to production') end context 'when duplicate action exists' do @@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do end it 'returns error' do - expect(subject.type).to eq(:error) - expect(subject.message).to include('Too many actions defined') + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq('Too many actions defined') end end @@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do name: 'teardown', environment: 'production') end - it 'returns success result' do - expect(subject.type).to eq(:success) - expect(subject.message).to include('Deployment from staging to production started') + it 'returns the success message' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject[:text]).to start_with('Deployment started from staging to production') end end end diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_create_spec.rb index 6c71e79ff6d..0f84b19a5a4 100644 --- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_create_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do it 'creates the issue' do expect { subject }.to change { project.issues.count }.by(1) - expect(subject.title).to eq('bird is the word') + expect(subject[:response_type]).to be(:in_channel) end end @@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do expect { subject }.to change { project.issues.count }.by(1) end end + + context 'issue cannot be created' do + let!(:issue) { create(:issue, project: project, title: 'bird is the word') } + let(:regex_match) { described_class.match("issue create #{'a' * 512}}") } + + it 'displays the errors' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match("- Title is too long") + end + end end describe '.match' do diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb index 24c06a967fa..04d10ad52a1 100644 --- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe Gitlab::ChatCommands::IssueSearch, service: true do describe '#execute' do - let!(:issue) { create(:issue, title: 'find me') } + let!(:issue) { create(:issue, project: project, title: 'find me') } let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } - let(:project) { issue.project } + let(:project) { create(:empty_project) } let(:user) { issue.author } let(:regex_match) { described_class.match("issue search find") } @@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do context 'when the user has no access' do it 'only returns the open issues' do - expect(subject).not_to include(confidential) + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match("not found") end end @@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do end it 'returns all results' do - expect(subject).to include(confidential, issue) + expect(subject).to have_key(:attachments) + expect(subject[:text]).to match("Here are the issues I found:") end end context 'without hits on the query' do it 'returns an empty collection' do - expect(subject).to be_empty + expect(subject[:text]).to match("not found") end end end diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb index 2eab73e49e5..89932c395c6 100644 --- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::ChatCommands::IssueShow, service: true do describe '#execute' do - let(:issue) { create(:issue) } - let(:project) { issue.project } + let(:issue) { create(:issue, project: project) } + let(:project) { create(:empty_project) } let(:user) { issue.author } let(:regex_match) { described_class.match("issue show #{issue.iid}") } @@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do end context 'the issue exists' do + let(:title) { subject[:attachments].first[:title] } + it 'returns the issue' do - expect(subject.iid).to be issue.iid + expect(subject[:response_type]).to be(:in_channel) + expect(title).to eq(issue.title) end context 'when its reference is given' do let(:regex_match) { described_class.match("issue show #{issue.to_reference}") } it 'shows the issue' do - expect(subject.iid).to be issue.iid + expect(subject[:response_type]).to be(:in_channel) + expect(title).to eq(issue.title) end end end @@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do context 'the issue does not exist' do let(:regex_match) { described_class.match("issue show 2343242") } - it "returns nil" do - expect(subject).to be_nil + it "returns not found" do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match("not found") end end end - describe 'self.match' do + describe '.match' do it 'matches the iid' do match = described_class.match("issue show 123") expect(match[:iid]).to eq("123") end + + it 'accepts a reference' do + match = described_class.match("issue show #{Issue.reference_prefix}123") + + expect(match[:iid]).to eq("123") + end end end diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb new file mode 100644 index 00000000000..ae41d75ab0c --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::Access do + describe '#access_denied' do + subject { described_class.new.access_denied } + + it { is_expected.to be_a(Hash) } + + it 'displays an error message' do + expect(subject[:text]).to match("is not allowed") + expect(subject[:response_type]).to be(:ephemeral) + end + end + + describe '#not_found' do + subject { described_class.new.not_found } + + it { is_expected.to be_a(Hash) } + + it 'tells the user the resource was not found' do + expect(subject[:text]).to match("not found!") + expect(subject[:response_type]).to be(:ephemeral) + end + end + + describe '#authorize' do + context 'with an authorization URL' do + subject { described_class.new('http://authorize.me').authorize } + + it { is_expected.to be_a(Hash) } + + it 'tells the user to authorize' do + expect(subject[:text]).to match("connect your GitLab account") + expect(subject[:response_type]).to be(:ephemeral) + end + end + + context 'without authorization url' do + subject { described_class.new.authorize } + + it { is_expected.to be_a(Hash) } + + it 'tells the user to authorize' do + expect(subject[:text]).to match("Couldn't identify you") + expect(subject[:response_type]).to be(:ephemeral) + end + end + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb new file mode 100644 index 00000000000..1c48c727e30 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::Deploy do + let(:build) { create(:ci_build) } + + describe '#present' do + subject { described_class.new(build).present('staging', 'prod') } + + it { is_expected.to have_key(:text) } + it { is_expected.to have_key(:response_type) } + it { is_expected.to have_key(:status) } + it { is_expected.not_to have_key(:attachments) } + + it 'messages the channel of the deploy' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject[:text]).to start_with("Deployment started from staging to prod") + end + end + + describe '#no_actions' do + subject { described_class.new(nil).no_actions } + + it { is_expected.to have_key(:text) } + it { is_expected.to have_key(:response_type) } + it { is_expected.to have_key(:status) } + it { is_expected.not_to have_key(:attachments) } + + it 'tells the user there is no action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("No action found to be executed") + end + end + + describe '#too_many_actions' do + subject { described_class.new(nil).too_many_actions } + + it { is_expected.to have_key(:text) } + it { is_expected.to have_key(:response_type) } + it { is_expected.to have_key(:status) } + it { is_expected.not_to have_key(:attachments) } + + it 'tells the user there is no action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("Too many actions defined") + end + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb new file mode 100644 index 00000000000..1852395fc97 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::ListIssues do + let(:project) { create(:empty_project) } + let(:message) { subject[:text] } + let(:issue) { project.issues.first } + + before { create_list(:issue, 2, project: project) } + + subject { described_class.new(project.issues).present } + + it do + is_expected.to have_key(:text) + is_expected.to have_key(:status) + is_expected.to have_key(:response_type) + is_expected.to have_key(:attachments) + end + + it 'shows a list of results' do + expect(subject[:response_type]).to be(:ephemeral) + + expect(message).to start_with("Here are the issues I found") + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb new file mode 100644 index 00000000000..13a318fe680 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::ShowIssue do + let(:project) { create(:empty_project) } + let(:issue) { create(:issue, project: project) } + let(:attachment) { subject[:attachments].first } + + subject { described_class.new(issue).present } + + it { is_expected.to be_a(Hash) } + + it 'shows the issue' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject).to have_key(:attachments) + expect(attachment[:title]).to eq(issue.title) + end + + context 'with upvotes' do + before do + create(:award_emoji, :upvote, awardable: issue) + end + + it 'shows the upvote count' do + expect(attachment[:text]).to start_with(":+1: 1") + end + end +end diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb deleted file mode 100644 index dc11a414717..00000000000 --- a/spec/lib/mattermost/client_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Client do - let(:user) { build(:user) } - - subject { described_class.new(user) } - - context 'JSON parse error' do - before do - Struct.new("Request", :body, :success?) - end - - it 'yields an error on malformed JSON' do - bad_json = Struct::Request.new("I'm not json", true) - expect { subject.send(:json_response, bad_json) }.to raise_error(Mattermost::ClientError) - end - - it 'shows a client error if the request was unsuccessful' do - bad_request = Struct::Request.new("true", false) - - expect { subject.send(:json_response, bad_request) }.to raise_error(Mattermost::ClientError) - end - end -end diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb deleted file mode 100644 index 5ccf1100898..00000000000 --- a/spec/lib/mattermost/command_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Command do - let(:params) { { 'token' => 'token', team_id: 'abc' } } - - before do - Mattermost::Session.base_uri('http://mattermost.example.com') - - allow_any_instance_of(Mattermost::Client).to receive(:with_session). - and_yield(Mattermost::Session.new(nil)) - end - - describe '#create' do - let(:params) do - { team_id: 'abc', - trigger: 'gitlab' - } - end - - subject { described_class.new(nil).create(params) } - - context 'for valid trigger word' do - before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). - with(body: { - team_id: 'abc', - trigger: 'gitlab' }.to_json). - to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: { token: 'token' }.to_json - ) - end - - it 'returns a token' do - is_expected.to eq('token') - end - end - - context 'for error message' do - before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). - to_return( - status: 500, - headers: { 'Content-Type' => 'application/json' }, - body: { - id: 'api.command.duplicate_trigger.app_error', - message: 'This trigger word is already in use. Please choose another word.', - detailed_error: '', - request_id: 'obc374man7bx5r3dbc1q5qhf3r', - status_code: 500 - }.to_json - ) - end - - it 'raises an error with message' do - expect { subject }.to raise_error(Mattermost::Error, 'This trigger word is already in use. Please choose another word.') - end - end - end -end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb deleted file mode 100644 index 74d12e37181..00000000000 --- a/spec/lib/mattermost/session_spec.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Session, type: :request do - let(:user) { create(:user) } - - let(:gitlab_url) { "http://gitlab.com" } - let(:mattermost_url) { "http://mattermost.com" } - - subject { described_class.new(user) } - - # Needed for doorkeeper to function - it { is_expected.to respond_to(:current_resource_owner) } - it { is_expected.to respond_to(:request) } - it { is_expected.to respond_to(:authorization) } - it { is_expected.to respond_to(:strategy) } - - before do - described_class.base_uri(mattermost_url) - end - - describe '#with session' do - let(:location) { 'http://location.tld' } - let!(:stub) do - WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login"). - to_return(headers: { 'location' => location }, status: 307) - end - - context 'without oauth uri' do - it 'makes a request to the oauth uri' do - expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) - end - end - - context 'with oauth_uri' do - let!(:doorkeeper) do - Doorkeeper::Application.create( - name: "GitLab Mattermost", - redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete", - scopes: "") - end - - context 'without token_uri' do - it 'can not create a session' do - expect do - subject.with_session - end.to raise_error(Mattermost::NoSessionError) - end - end - - context 'with token_uri' do - let(:state) { "state" } - let(:params) do - { response_type: "code", - client_id: doorkeeper.uid, - redirect_uri: "#{mattermost_url}/signup/gitlab/complete", - state: state } - end - let(:location) do - "#{gitlab_url}/oauth/authorize?#{URI.encode_www_form(params)}" - end - - before do - WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete"). - with(query: hash_including({ 'state' => state })). - to_return do |request| - post "/oauth/token", - client_id: doorkeeper.uid, - client_secret: doorkeeper.secret, - redirect_uri: params[:redirect_uri], - grant_type: 'authorization_code', - code: request.uri.query_values['code'] - - if response.status == 200 - { headers: { 'token' => 'thisworksnow' }, status: 202 } - end - end - - WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout"). - to_return(headers: { Authorization: 'token thisworksnow' }, status: 200) - end - - it 'can setup a session' do - subject.with_session do |session| - end - - expect(subject.token).not_to be_nil - end - - it 'returns the value of the block' do - result = subject.with_session do |session| - "value" - end - - expect(result).to eq("value") - end - end - end - - context 'with lease' do - before do - allow(subject).to receive(:lease_try_obtain).and_return('aldkfjsldfk') - end - - it 'tries to obtain a lease' do - expect(subject).to receive(:lease_try_obtain) - expect(Gitlab::ExclusiveLease).to receive(:cancel) - - # Cannot setup a session, but we should still cancel the lease - expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) - end - end - - context 'without lease' do - before do - allow(subject).to receive(:lease_try_obtain).and_return(nil) - end - - it 'returns a NoSessionError error' do - expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) - end - end - end -end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb deleted file mode 100644 index 2d14be6bcc2..00000000000 --- a/spec/lib/mattermost/team_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Team do - before do - Mattermost::Session.base_uri('http://mattermost.example.com') - - allow_any_instance_of(Mattermost::Client).to receive(:with_session). - and_yield(Mattermost::Session.new(nil)) - end - - describe '#all' do - subject { described_class.new(nil).all } - - context 'for valid request' do - let(:response) do - [{ - "id" => "xiyro8huptfhdndadpz8r3wnbo", - "create_at" => 1482174222155, - "update_at" => 1482174222155, - "delete_at" => 0, - "display_name" => "chatops", - "name" => "chatops", - "email" => "admin@example.com", - "type" => "O", - "company_name" => "", - "allowed_domains" => "", - "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", - "allow_open_invite" => false }] - end - - before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). - to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: response.to_json - ) - end - - it 'returns a token' do - is_expected.to eq(response) - end - end - - context 'for error message' do - before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). - to_return( - status: 500, - headers: { 'Content-Type' => 'application/json' }, - body: { - id: 'api.team.list.app_error', - message: 'Cannot list teams.', - detailed_error: '', - request_id: 'obc374man7bx5r3dbc1q5qhf3r', - status_code: 500 - }.to_json - ) - end - - it 'raises an error with message' do - expect { subject }.to raise_error(Mattermost::Error, 'Cannot list teams.') - end - end - end -end From 19c55a47b77f6c63db39a45946dc47f3c95fc744 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Wed, 11 Jan 2017 08:54:44 -0500 Subject: [PATCH 082/174] Revert removing of some files --- lib/mattermost/client.rb | 41 ++++++++++ lib/mattermost/command.rb | 10 +++ lib/mattermost/error.rb | 3 + lib/mattermost/session.rb | 160 ++++++++++++++++++++++++++++++++++++++ lib/mattermost/team.rb | 7 ++ 5 files changed, 221 insertions(+) create mode 100644 lib/mattermost/client.rb create mode 100644 lib/mattermost/command.rb create mode 100644 lib/mattermost/error.rb create mode 100644 lib/mattermost/session.rb create mode 100644 lib/mattermost/team.rb diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb new file mode 100644 index 00000000000..ec2903b7ec6 --- /dev/null +++ b/lib/mattermost/client.rb @@ -0,0 +1,41 @@ +module Mattermost + class ClientError < Mattermost::Error; end + + class Client + attr_reader :user + + def initialize(user) + @user = user + end + + private + + def with_session(&blk) + Mattermost::Session.new(user).with_session(&blk) + end + + def json_get(path, options = {}) + with_session do |session| + json_response session.get(path, options) + end + end + + def json_post(path, options = {}) + with_session do |session| + json_response session.post(path, options) + end + end + + def json_response(response) + json_response = JSON.parse(response.body) + + unless response.success? + raise Mattermost::ClientError.new(json_response['message'] || 'Undefined error') + end + + json_response + rescue JSON::JSONError + raise Mattermost::ClientError.new('Cannot parse response') + end + end +end diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb new file mode 100644 index 00000000000..d1e4bb0eccf --- /dev/null +++ b/lib/mattermost/command.rb @@ -0,0 +1,10 @@ +module Mattermost + class Command < Client + def create(params) + response = json_post("/api/v3/teams/#{params[:team_id]}/commands/create", + body: params.to_json) + + response['token'] + end + end +end diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb new file mode 100644 index 00000000000..014df175be0 --- /dev/null +++ b/lib/mattermost/error.rb @@ -0,0 +1,3 @@ +module Mattermost + class Error < StandardError; end +end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb new file mode 100644 index 00000000000..377cb7b1021 --- /dev/null +++ b/lib/mattermost/session.rb @@ -0,0 +1,160 @@ +module Mattermost + class NoSessionError < Mattermost::Error + def message + 'No session could be set up, is Mattermost configured with Single Sign On?' + end + end + + class ConnectionError < Mattermost::Error; end + + # This class' prime objective is to obtain a session token on a Mattermost + # instance with SSO configured where this GitLab instance is the provider. + # + # The process depends on OAuth, but skips a step in the authentication cycle. + # For example, usually a user would click the 'login in GitLab' button on + # Mattermost, which would yield a 302 status code and redirects you to GitLab + # to approve the use of your account on Mattermost. Which would trigger a + # callback so Mattermost knows this request is approved and gets the required + # data to create the user account etc. + # + # This class however skips the button click, and also the approval phase to + # speed up the process and keep it without manual action and get a session + # going. + class Session + include Doorkeeper::Helpers::Controller + include HTTParty + + LEASE_TIMEOUT = 60 + + base_uri Settings.mattermost.host + + attr_accessor :current_resource_owner, :token + + def initialize(current_user) + @current_resource_owner = current_user + end + + def with_session + with_lease do + raise Mattermost::NoSessionError unless create + + begin + yield self + rescue Errno::ECONNREFUSED + raise Mattermost::NoSessionError + ensure + destroy + end + end + end + + # Next methods are needed for Doorkeeper + def pre_auth + @pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new( + Doorkeeper.configuration, server.client_via_uid, params) + end + + def authorization + @authorization ||= strategy.request + end + + def strategy + @strategy ||= server.authorization_request(pre_auth.response_type) + end + + def request + @request ||= OpenStruct.new(parameters: params) + end + + def params + Rack::Utils.parse_query(oauth_uri.query).symbolize_keys + end + + def get(path, options = {}) + handle_exceptions do + self.class.get(path, options.merge(headers: @headers)) + end + end + + def post(path, options = {}) + handle_exceptions do + self.class.post(path, options.merge(headers: @headers)) + end + end + + private + + def create + return unless oauth_uri + return unless token_uri + + @token = request_token + @headers = { + Authorization: "Bearer #{@token}" + } + + @token + end + + def destroy + post('/api/v3/users/logout') + end + + def oauth_uri + return @oauth_uri if defined?(@oauth_uri) + + @oauth_uri = nil + + response = get("/api/v3/oauth/gitlab/login", follow_redirects: false) + return unless 300 <= response.code && response.code < 400 + + redirect_uri = response.headers['location'] + return unless redirect_uri + + @oauth_uri = URI.parse(redirect_uri) + end + + def token_uri + @token_uri ||= + if oauth_uri + authorization.authorize.redirect_uri if pre_auth.authorizable? + end + end + + def request_token + response = get(token_uri, follow_redirects: false) + + if 200 <= response.code && response.code < 400 + response.headers['token'] + end + end + + def with_lease + lease_uuid = lease_try_obtain + raise NoSessionError unless lease_uuid + + begin + yield + ensure + Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid) + end + end + + def lease_key + "mattermost:session" + end + + def lease_try_obtain + lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) + lease.try_obtain + end + + def handle_exceptions + yield + rescue HTTParty::Error => e + raise Mattermost::ConnectionError.new(e.message) + rescue Errno::ECONNREFUSED + raise Mattermost::ConnectionError.new(e.message) + end + end +end diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb new file mode 100644 index 00000000000..784eca6ab5a --- /dev/null +++ b/lib/mattermost/team.rb @@ -0,0 +1,7 @@ +module Mattermost + class Team < Client + def all + json_get('/api/v3/teams/all') + end + end +end From e96ddef3deaac499bcdd67800839e59f46ae67b5 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Wed, 11 Jan 2017 09:04:49 -0500 Subject: [PATCH 083/174] Add help command --- lib/gitlab/chat_commands/command.rb | 13 ++++------ lib/gitlab/chat_commands/help.rb | 28 +++++++++++++++++++++ lib/gitlab/chat_commands/presenters/help.rb | 20 +++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 lib/gitlab/chat_commands/help.rb create mode 100644 lib/gitlab/chat_commands/presenters/help.rb diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index ac7ee868402..4e5031a8a26 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -18,25 +18,22 @@ module Gitlab Gitlab::ChatCommands::Presenters::Access.new.access_denied end else - help(help_messages) + Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands) end end def match_command match = nil - service = available_commands.find do |klass| - match = klass.match(params[:text]) - end + service = + available_commands.find do |klass| + match = klass.match(params[:text]) + end [service, match] end private - def help_messages - available_commands.map(&:help_message) - end - def available_commands COMMANDS.select do |klass| klass.available?(project) diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb new file mode 100644 index 00000000000..e76733f5445 --- /dev/null +++ b/lib/gitlab/chat_commands/help.rb @@ -0,0 +1,28 @@ +module Gitlab + module ChatCommands + class Help < BaseCommand + # This class has to be used last, as it always matches. It has to match + # because other commands were not triggered and we want to show the help + # command + def self.match(_text) + true + end + + def self.help_message + 'help' + end + + def self.allowed?(_project, _user) + true + end + + def execute(commands) + Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger) + end + + def trigger + params[:command] + end + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb new file mode 100644 index 00000000000..133b707231f --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/help.rb @@ -0,0 +1,20 @@ +module Gitlab::ChatCommands::Presenters + class Help < Gitlab::ChatCommands::Presenters::Base + def present(trigger) + message = + if @resource.none? + "No commands available :thinking_face:" + else + header_with_list("Available commands", full_commands(trigger)) + end + + ephemeral_response(text: message) + end + + private + + def full_commands(trigger) + @resource.map { |command| "#{trigger} #{command.help_message}" } + end + end +end From e9d163865bc6f987e7fa277536c6bc3950fbf1e0 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 12 Jan 2017 09:04:21 -0500 Subject: [PATCH 084/174] Fix tests --- .../project_services/chat_slash_commands_service.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index 608754f3035..5eb1bd86e9d 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -28,7 +28,7 @@ class ChatSlashCommandsService < Service end def trigger(params) - return access_presenter unless valid_token?(params[:token]) + return unless valid_token?(params[:token]) user = find_chat_user(params) @@ -36,16 +36,12 @@ class ChatSlashCommandsService < Service Gitlab::ChatCommands::Command.new(project, user, params).execute else url = authorize_chat_name_url(params) - access_presenter(url).authorize + Gitlab::ChatCommands::Presenters::Access.new(url).authorize end end private - def access_presenter(url = nil) - Gitlab::ChatCommands::Presenters::Access.new(url) - end - def find_chat_user(params) ChatNames::FindUserService.new(self, params).execute end From 52ca0d2c9ecb7e7d4d0130f19bf0fb7b772a357d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 19 Jan 2017 09:22:09 +0100 Subject: [PATCH 085/174] Incorporate feedback --- .../unreleased/zj-format-chat-messages.yml | 4 + lib/gitlab/chat_commands/issue_create.rb | 2 +- lib/gitlab/chat_commands/presenters/access.rb | 38 +++--- lib/gitlab/chat_commands/presenters/base.rb | 114 ++++++++-------- lib/gitlab/chat_commands/presenters/deploy.rb | 39 +++--- lib/gitlab/chat_commands/presenters/help.rb | 31 +++-- .../chat_commands/presenters/issuable.rb | 66 ++++++---- .../chat_commands/presenters/list_issues.rb | 61 +++++---- .../chat_commands/presenters/new_issue.rb | 42 ++++++ .../chat_commands/presenters/show_issue.rb | 72 ++++++---- .../gitlab/chat_commands/issue_search_spec.rb | 2 +- .../gitlab/chat_commands/issue_show_spec.rb | 4 +- .../chat_commands/presenters/deploy_spec.rb | 2 +- .../presenters/list_issues_spec.rb | 5 +- .../presenters/show_issue_spec.rb | 4 +- spec/lib/mattermost/client_spec.rb | 24 ++++ spec/lib/mattermost/command_spec.rb | 61 +++++++++ spec/lib/mattermost/session_spec.rb | 123 ++++++++++++++++++ spec/lib/mattermost/team_spec.rb | 66 ++++++++++ 19 files changed, 568 insertions(+), 192 deletions(-) create mode 100644 changelogs/unreleased/zj-format-chat-messages.yml create mode 100644 lib/gitlab/chat_commands/presenters/new_issue.rb create mode 100644 spec/lib/mattermost/client_spec.rb create mode 100644 spec/lib/mattermost/command_spec.rb create mode 100644 spec/lib/mattermost/session_spec.rb create mode 100644 spec/lib/mattermost/team_spec.rb diff --git a/changelogs/unreleased/zj-format-chat-messages.yml b/changelogs/unreleased/zj-format-chat-messages.yml new file mode 100644 index 00000000000..2494884f5c9 --- /dev/null +++ b/changelogs/unreleased/zj-format-chat-messages.yml @@ -0,0 +1,4 @@ +--- +title: Reformat messages ChatOps +merge_request: 8528 +author: diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index a06f13b0f72..3f3d7de8b2e 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -35,7 +35,7 @@ module Gitlab end def presenter(issue) - Gitlab::ChatCommands::Presenters::ShowIssue.new(issue) + Gitlab::ChatCommands::Presenters::NewIssue.new(issue) end end end diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb index 6d18d745608..b66ef48d6a8 100644 --- a/lib/gitlab/chat_commands/presenters/access.rb +++ b/lib/gitlab/chat_commands/presenters/access.rb @@ -1,22 +1,26 @@ -module Gitlab::ChatCommands::Presenters - class Access < Gitlab::ChatCommands::Presenters::Base - def access_denied - ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).") - end - - def not_found - ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") - end - - def authorize - message = - if @resource - ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})." - else - ":sweat_smile: Couldn't identify you, nor can I autorize you!" +module Gitlab + module ChatCommands + module Presenters + class Access < Presenters::Base + def access_denied + ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).") end - ephemeral_response(text: message) + def not_found + ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") + end + + def authorize + message = + if @resource + ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})." + else + ":sweat_smile: Couldn't identify you, nor can I autorize you!" + end + + ephemeral_response(text: message) + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb index 0897025d85f..2700a5a2ad5 100644 --- a/lib/gitlab/chat_commands/presenters/base.rb +++ b/lib/gitlab/chat_commands/presenters/base.rb @@ -1,73 +1,77 @@ -module Gitlab::ChatCommands::Presenters - class Base - include Gitlab::Routing.url_helpers +module Gitlab + module ChatCommands + module Presenters + class Base + include Gitlab::Routing.url_helpers - def initialize(resource = nil) - @resource = resource - end + def initialize(resource = nil) + @resource = resource + end - def display_errors - message = header_with_list("The action was not successful, because:", @resource.errors.full_messages) + def display_errors + message = header_with_list("The action was not successful, because:", @resource.errors.full_messages) - ephemeral_response(text: message) - end + ephemeral_response(text: message) + end - private + private - def header_with_list(header, items) - message = [header] + def header_with_list(header, items) + message = [header] - items.each do |item| - message << "- #{item}" - end + items.each do |item| + message << "- #{item}" + end - message.join("\n") - end + message.join("\n") + end - def ephemeral_response(message) - response = { - response_type: :ephemeral, - status: 200 - }.merge(message) + def ephemeral_response(message) + response = { + response_type: :ephemeral, + status: 200 + }.merge(message) - format_response(response) - end + format_response(response) + end - def in_channel_response(message) - response = { - response_type: :in_channel, - status: 200 - }.merge(message) + def in_channel_response(message) + response = { + response_type: :in_channel, + status: 200 + }.merge(message) - format_response(response) - end + format_response(response) + end - def format_response(response) - response[:text] = format(response[:text]) if response.has_key?(:text) + def format_response(response) + response[:text] = format(response[:text]) if response.has_key?(:text) - if response.has_key?(:attachments) - response[:attachments].each do |attachment| - attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext] - attachment[:text] = format(attachment[:text]) if attachment[:text] + if response.has_key?(:attachments) + response[:attachments].each do |attachment| + attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext] + attachment[:text] = format(attachment[:text]) if attachment[:text] + end + end + + response + end + + # Convert Markdown to slacks format + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def resource_url + url_for( + [ + @resource.project.namespace.becomes(Namespace), + @resource.project, + @resource + ] + ) end end - - response - end - - # Convert Markdown to slacks format - def format(string) - Slack::Notifier::LinkFormatter.format(string) - end - - def resource_url - url_for( - [ - @resource.project.namespace.becomes(Namespace), - @resource.project, - @resource - ] - ) end end end diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb index 4f6333812ff..b1cfaac15af 100644 --- a/lib/gitlab/chat_commands/presenters/deploy.rb +++ b/lib/gitlab/chat_commands/presenters/deploy.rb @@ -1,24 +1,29 @@ -module Gitlab::ChatCommands::Presenters - class Deploy < Gitlab::ChatCommands::Presenters::Base - def present(from, to) - message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})." - in_channel_response(text: message) - end +module Gitlab + module ChatCommands + module Presenters + class Deploy < Presenters::Base + def present(from, to) + message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})." - def no_actions - ephemeral_response(text: "No action found to be executed") - end + in_channel_response(text: message) + end - def too_many_actions - ephemeral_response(text: "Too many actions defined") - end + def no_actions + ephemeral_response(text: "No action found to be executed") + end - private + def too_many_actions + ephemeral_response(text: "Too many actions defined") + end - def resource_url - polymorphic_url( - [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] - ) + private + + def resource_url + polymorphic_url( + [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] + ) + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb index 133b707231f..c7a67467b7e 100644 --- a/lib/gitlab/chat_commands/presenters/help.rb +++ b/lib/gitlab/chat_commands/presenters/help.rb @@ -1,20 +1,25 @@ -module Gitlab::ChatCommands::Presenters - class Help < Gitlab::ChatCommands::Presenters::Base - def present(trigger) - message = - if @resource.none? - "No commands available :thinking_face:" - else - header_with_list("Available commands", full_commands(trigger)) +module Gitlab + module ChatCommands + module Presenters + class Help < Presenters::Base + def present(trigger) + ephemeral_response(text: help_message(trigger)) end - ephemeral_response(text: message) - end + private - private + def help_message(trigger) + if @resource.none? + "No commands available :thinking_face:" + else + header_with_list("Available commands", full_commands(trigger)) + end + end - def full_commands(trigger) - @resource.map { |command| "#{trigger} #{command.help_message}" } + def full_commands(trigger) + @resource.map { |command| "#{trigger} #{command.help_message}" } + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb index 9623387f188..2cb6b1525fc 100644 --- a/lib/gitlab/chat_commands/presenters/issuable.rb +++ b/lib/gitlab/chat_commands/presenters/issuable.rb @@ -1,33 +1,45 @@ -module Gitlab::ChatCommands::Presenters - class Issuable < Gitlab::ChatCommands::Presenters::Base - private +module Gitlab + module ChatCommands + module Presenters + class Issuable < Presenters::Base + private - def project - @resource.project - end + def color(issuable) + issuable.open? ? '#38ae67' : '#d22852' + end - def author - @resource.author - end + def status_text(issuable) + issuable.open? ? 'Open' : 'Closed' + end - def fields - [ - { - title: "Assignee", - value: @resource.assignee ? @resource.assignee.name : "_None_", - short: true - }, - { - title: "Milestone", - value: @resource.milestone ? @resource.milestone.title : "_None_", - short: true - }, - { - title: "Labels", - value: @resource.labels.any? ? @resource.label_names : "_None_", - short: true - } - ] + def project + @resource.project + end + + def author + @resource.author + end + + def fields + [ + { + title: "Assignee", + value: @resource.assignee ? @resource.assignee.name : "_None_", + short: true + }, + { + title: "Milestone", + value: @resource.milestone ? @resource.milestone.title : "_None_", + short: true + }, + { + title: "Labels", + value: @resource.labels.any? ? @resource.label_names : "_None_", + short: true + } + ] + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/list_issues.rb b/lib/gitlab/chat_commands/presenters/list_issues.rb index 5a7b3fca5c2..2458b9356b7 100644 --- a/lib/gitlab/chat_commands/presenters/list_issues.rb +++ b/lib/gitlab/chat_commands/presenters/list_issues.rb @@ -1,32 +1,43 @@ -module Gitlab::ChatCommands::Presenters - class ListIssues < Gitlab::ChatCommands::Presenters::Base - def present - ephemeral_response(text: "Here are the issues I found:", attachments: attachments) - end +module Gitlab + module ChatCommands + module Presenters + class ListIssues < Presenters::Issuable + def present + text = if @resource.count >= 5 + "Here are the first 5 issues I found:" + else + "Here are the #{@resource.count} issues I found:" + end - private + ephemeral_response(text: text, attachments: attachments) + end - def attachments - @resource.map do |issue| - state = issue.open? ? "Open" : "Closed" + private - { - fallback: "Issue #{issue.to_reference}: #{issue.title}", - color: "#d22852", - text: "[#{issue.to_reference}](#{url_for([namespace, project, issue])}) · #{issue.title} (#{state})", - mrkdwn_in: [ - "text" - ] - } + def attachments + @resource.map do |issue| + url = "[#{issue.to_reference}](#{url_for([namespace, project, issue])})" + + { + color: color(issue), + fallback: "#{issue.to_reference} #{issue.title}", + text: "#{url} · #{issue.title} (#{status_text(issue)})", + + mrkdwn_in: [ + "text" + ] + } + end + end + + def project + @project ||= @resource.first.project + end + + def namespace + @namespace ||= project.namespace.becomes(Namespace) + end end end - - def project - @project ||= @resource.first.project - end - - def namespace - @namespace ||= project.namespace.becomes(Namespace) - end end end diff --git a/lib/gitlab/chat_commands/presenters/new_issue.rb b/lib/gitlab/chat_commands/presenters/new_issue.rb new file mode 100644 index 00000000000..c7c6febb56e --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/new_issue.rb @@ -0,0 +1,42 @@ +module Gitlab + module ChatCommands + module Presenters + class NewIssue < Presenters::Issuable + def present + in_channel_response(show_issue) + end + + private + + def show_issue + { + attachments: [ + { + title: "#{@resource.title} · #{@resource.to_reference}", + title_link: resource_url, + author_name: author.name, + author_icon: author.avatar_url, + fallback: "New issue #{@resource.to_reference}: #{@resource.title}", + pretext: pretext, + color: color(@resource), + fields: fields, + mrkdwn_in: [ + :title, + :text + ] + } + ] + } + end + + def pretext + "I opened an issue on behalf on #{author_profile_link}: *#{@resource.to_reference}* from #{project.name_with_namespace}" + end + + def author_profile_link + "[#{author.to_reference}](#{url_for(author)})" + end + end + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/show_issue.rb b/lib/gitlab/chat_commands/presenters/show_issue.rb index 2a89c30b972..e5644a4ad7e 100644 --- a/lib/gitlab/chat_commands/presenters/show_issue.rb +++ b/lib/gitlab/chat_commands/presenters/show_issue.rb @@ -1,38 +1,54 @@ -module Gitlab::ChatCommands::Presenters - class ShowIssue < Gitlab::ChatCommands::Presenters::Issuable - def present - in_channel_response(show_issue) - end +module Gitlab + module ChatCommands + module Presenters + class ShowIssue < Presenters::Issuable + def present + in_channel_response(show_issue) + end - private + private - def show_issue - { - attachments: [ + def show_issue { - title: @resource.title, - title_link: resource_url, - author_name: author.name, - author_icon: author.avatar_url, - fallback: "#{@resource.to_reference}: #{@resource.title}", - text: text, - fields: fields, - mrkdwn_in: [ - :title, - :text + attachments: [ + { + title: "#{@resource.title} · #{@resource.to_reference}", + title_link: resource_url, + author_name: author.name, + author_icon: author.avatar_url, + fallback: "New issue #{@resource.to_reference}: #{@resource.title}", + pretext: pretext, + text: text, + color: color(@resource), + fields: fields, + mrkdwn_in: [ + :pretext, + :text + ] + } ] } - ] - } - end + end - def text - message = "" - message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero? - message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero? - message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero? + def text + message = "**#{status_text(@resource)}**" - message + if @resource.upvotes.zero? && @resource.downvotes.zero? && @resource.user_notes_count.zero? + return message + end + + message << " · " + message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero? + message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero? + message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero? + + message + end + + def pretext + "Issue *#{@resource.to_reference} from #{project.name_with_namespace}" + end + end end end end diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb index 04d10ad52a1..551ccb79a58 100644 --- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do it 'returns all results' do expect(subject).to have_key(:attachments) - expect(subject[:text]).to match("Here are the issues I found:") + expect(subject[:text]).to eq("Here are the 2 issues I found:") end end diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb index 89932c395c6..1f20d0a44ce 100644 --- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::ChatCommands::IssueShow, service: true do it 'returns the issue' do expect(subject[:response_type]).to be(:in_channel) - expect(title).to eq(issue.title) + expect(title).to start_with(issue.title) end context 'when its reference is given' do @@ -28,7 +28,7 @@ describe Gitlab::ChatCommands::IssueShow, service: true do it 'shows the issue' do expect(subject[:response_type]).to be(:in_channel) - expect(title).to eq(issue.title) + expect(title).to start_with(issue.title) end end end diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb index 1c48c727e30..dc2dd300072 100644 --- a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::ChatCommands::Presenters::Deploy do end describe '#too_many_actions' do - subject { described_class.new(nil).too_many_actions } + subject { described_class.new([]).too_many_actions } it { is_expected.to have_key(:text) } it { is_expected.to have_key(:response_type) } diff --git a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb index 1852395fc97..13a1f70fe78 100644 --- a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb @@ -3,13 +3,12 @@ require 'spec_helper' describe Gitlab::ChatCommands::Presenters::ListIssues do let(:project) { create(:empty_project) } let(:message) { subject[:text] } - let(:issue) { project.issues.first } before { create_list(:issue, 2, project: project) } subject { described_class.new(project.issues).present } - it do + it 'formats the message correct' do is_expected.to have_key(:text) is_expected.to have_key(:status) is_expected.to have_key(:response_type) @@ -19,6 +18,6 @@ describe Gitlab::ChatCommands::Presenters::ListIssues do it 'shows a list of results' do expect(subject[:response_type]).to be(:ephemeral) - expect(message).to start_with("Here are the issues I found") + expect(message).to start_with("Here are the 2 issues I found") end end diff --git a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb index 13a318fe680..ca4062e692a 100644 --- a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::ChatCommands::Presenters::ShowIssue do it 'shows the issue' do expect(subject[:response_type]).to be(:in_channel) expect(subject).to have_key(:attachments) - expect(attachment[:title]).to eq(issue.title) + expect(attachment[:title]).to start_with(issue.title) end context 'with upvotes' do @@ -21,7 +21,7 @@ describe Gitlab::ChatCommands::Presenters::ShowIssue do end it 'shows the upvote count' do - expect(attachment[:text]).to start_with(":+1: 1") + expect(attachment[:text]).to start_with("**Open** · :+1: 1") end end end diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb new file mode 100644 index 00000000000..dc11a414717 --- /dev/null +++ b/spec/lib/mattermost/client_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Mattermost::Client do + let(:user) { build(:user) } + + subject { described_class.new(user) } + + context 'JSON parse error' do + before do + Struct.new("Request", :body, :success?) + end + + it 'yields an error on malformed JSON' do + bad_json = Struct::Request.new("I'm not json", true) + expect { subject.send(:json_response, bad_json) }.to raise_error(Mattermost::ClientError) + end + + it 'shows a client error if the request was unsuccessful' do + bad_request = Struct::Request.new("true", false) + + expect { subject.send(:json_response, bad_request) }.to raise_error(Mattermost::ClientError) + end + end +end diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb new file mode 100644 index 00000000000..5ccf1100898 --- /dev/null +++ b/spec/lib/mattermost/command_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe Mattermost::Command do + let(:params) { { 'token' => 'token', team_id: 'abc' } } + + before do + Mattermost::Session.base_uri('http://mattermost.example.com') + + allow_any_instance_of(Mattermost::Client).to receive(:with_session). + and_yield(Mattermost::Session.new(nil)) + end + + describe '#create' do + let(:params) do + { team_id: 'abc', + trigger: 'gitlab' + } + end + + subject { described_class.new(nil).create(params) } + + context 'for valid trigger word' do + before do + stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). + with(body: { + team_id: 'abc', + trigger: 'gitlab' }.to_json). + to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: { token: 'token' }.to_json + ) + end + + it 'returns a token' do + is_expected.to eq('token') + end + end + + context 'for error message' do + before do + stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). + to_return( + status: 500, + headers: { 'Content-Type' => 'application/json' }, + body: { + id: 'api.command.duplicate_trigger.app_error', + message: 'This trigger word is already in use. Please choose another word.', + detailed_error: '', + request_id: 'obc374man7bx5r3dbc1q5qhf3r', + status_code: 500 + }.to_json + ) + end + + it 'raises an error with message' do + expect { subject }.to raise_error(Mattermost::Error, 'This trigger word is already in use. Please choose another word.') + end + end + end +end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb new file mode 100644 index 00000000000..74d12e37181 --- /dev/null +++ b/spec/lib/mattermost/session_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +describe Mattermost::Session, type: :request do + let(:user) { create(:user) } + + let(:gitlab_url) { "http://gitlab.com" } + let(:mattermost_url) { "http://mattermost.com" } + + subject { described_class.new(user) } + + # Needed for doorkeeper to function + it { is_expected.to respond_to(:current_resource_owner) } + it { is_expected.to respond_to(:request) } + it { is_expected.to respond_to(:authorization) } + it { is_expected.to respond_to(:strategy) } + + before do + described_class.base_uri(mattermost_url) + end + + describe '#with session' do + let(:location) { 'http://location.tld' } + let!(:stub) do + WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login"). + to_return(headers: { 'location' => location }, status: 307) + end + + context 'without oauth uri' do + it 'makes a request to the oauth uri' do + expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) + end + end + + context 'with oauth_uri' do + let!(:doorkeeper) do + Doorkeeper::Application.create( + name: "GitLab Mattermost", + redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete", + scopes: "") + end + + context 'without token_uri' do + it 'can not create a session' do + expect do + subject.with_session + end.to raise_error(Mattermost::NoSessionError) + end + end + + context 'with token_uri' do + let(:state) { "state" } + let(:params) do + { response_type: "code", + client_id: doorkeeper.uid, + redirect_uri: "#{mattermost_url}/signup/gitlab/complete", + state: state } + end + let(:location) do + "#{gitlab_url}/oauth/authorize?#{URI.encode_www_form(params)}" + end + + before do + WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete"). + with(query: hash_including({ 'state' => state })). + to_return do |request| + post "/oauth/token", + client_id: doorkeeper.uid, + client_secret: doorkeeper.secret, + redirect_uri: params[:redirect_uri], + grant_type: 'authorization_code', + code: request.uri.query_values['code'] + + if response.status == 200 + { headers: { 'token' => 'thisworksnow' }, status: 202 } + end + end + + WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout"). + to_return(headers: { Authorization: 'token thisworksnow' }, status: 200) + end + + it 'can setup a session' do + subject.with_session do |session| + end + + expect(subject.token).not_to be_nil + end + + it 'returns the value of the block' do + result = subject.with_session do |session| + "value" + end + + expect(result).to eq("value") + end + end + end + + context 'with lease' do + before do + allow(subject).to receive(:lease_try_obtain).and_return('aldkfjsldfk') + end + + it 'tries to obtain a lease' do + expect(subject).to receive(:lease_try_obtain) + expect(Gitlab::ExclusiveLease).to receive(:cancel) + + # Cannot setup a session, but we should still cancel the lease + expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) + end + end + + context 'without lease' do + before do + allow(subject).to receive(:lease_try_obtain).and_return(nil) + end + + it 'returns a NoSessionError error' do + expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) + end + end + end +end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb new file mode 100644 index 00000000000..2d14be6bcc2 --- /dev/null +++ b/spec/lib/mattermost/team_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Mattermost::Team do + before do + Mattermost::Session.base_uri('http://mattermost.example.com') + + allow_any_instance_of(Mattermost::Client).to receive(:with_session). + and_yield(Mattermost::Session.new(nil)) + end + + describe '#all' do + subject { described_class.new(nil).all } + + context 'for valid request' do + let(:response) do + [{ + "id" => "xiyro8huptfhdndadpz8r3wnbo", + "create_at" => 1482174222155, + "update_at" => 1482174222155, + "delete_at" => 0, + "display_name" => "chatops", + "name" => "chatops", + "email" => "admin@example.com", + "type" => "O", + "company_name" => "", + "allowed_domains" => "", + "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", + "allow_open_invite" => false }] + end + + before do + stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). + to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: response.to_json + ) + end + + it 'returns a token' do + is_expected.to eq(response) + end + end + + context 'for error message' do + before do + stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). + to_return( + status: 500, + headers: { 'Content-Type' => 'application/json' }, + body: { + id: 'api.team.list.app_error', + message: 'Cannot list teams.', + detailed_error: '', + request_id: 'obc374man7bx5r3dbc1q5qhf3r', + status_code: 500 + }.to_json + ) + end + + it 'raises an error with message' do + expect { subject }.to raise_error(Mattermost::Error, 'Cannot list teams.') + end + end + end +end From 6a3d29c73d2578c7b2a40f2dfcb823b681a70f7e Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Mon, 14 Nov 2016 01:56:39 -0200 Subject: [PATCH 086/174] Add ability to define a coverage regex in the .gitlab-ci.yml * Instead of using the proposed `coverage` key, this expects `coverage_regex` --- app/models/ci/build.rb | 7 +++-- ...1114024742_add_coverage_regex_to_builds.rb | 13 ++++++++++ db/schema.rb | 1 + lib/ci/gitlab_ci_yaml_processor.rb | 1 + lib/gitlab/ci/config/entry/job.rb | 8 ++++-- .../config/entry/legacy_validation_helpers.rb | 10 ++++--- lib/gitlab/ci/config/entry/validators.rb | 10 +++++++ lib/gitlab/ci/config/node/regexp.rb | 26 +++++++++++++++++++ 8 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20161114024742_add_coverage_regex_to_builds.rb create mode 100644 lib/gitlab/ci/config/node/regexp.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5fe8ddf69d7..4691b33ee9b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -276,8 +276,7 @@ module Ci def update_coverage return unless project - coverage_regex = project.build_coverage_regex - return unless coverage_regex + return unless coverage_regex = self.coverage_regex coverage = extract_coverage(trace, coverage_regex) if coverage.is_a? Numeric @@ -522,6 +521,10 @@ module Ci self.update(artifacts_expire_at: nil) end + def coverage_regex + read_attribute(:coverage_regex) || build_attributes_from_config[:coverage] || project.build_coverage_regex + end + def when read_attribute(:when) || build_attributes_from_config[:when] || 'on_success' end diff --git a/db/migrate/20161114024742_add_coverage_regex_to_builds.rb b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb new file mode 100644 index 00000000000..88aa5d52b39 --- /dev/null +++ b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb @@ -0,0 +1,13 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddCoverageRegexToBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + add_column :ci_builds, :coverage_regex, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 3c836db27fc..1cc9e7eec5e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -215,6 +215,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do t.datetime "queued_at" t.string "token" t.integer "lock_version" + t.string "coverage_regex" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 7463bd719d5..2f5ef4d36bd 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -61,6 +61,7 @@ module Ci allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', environment: job[:environment_name], + coverage_regex: job[:coverage_regex], yaml_variables: yaml_variables(name), options: { image: job[:image], diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index a55362f0b6b..3c7ef99cefc 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -11,7 +11,7 @@ module Gitlab ALLOWED_KEYS = %i[tags script only except type image services allow_failure type stage when artifacts cache dependencies before_script - after_script variables environment] + after_script variables environment coverage_regex] validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -71,9 +71,12 @@ module Gitlab entry :environment, Entry::Environment, description: 'Environment configuration for this job.' + node :coverage_regex, Node::Regexp, + description: 'Coverage scanning regex configuration for this job.' + helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, - :artifacts, :commands, :environment + :artifacts, :commands, :environment, :coverage_regex attributes :script, :tags, :allow_failure, :when, :dependencies @@ -130,6 +133,7 @@ module Gitlab variables: variables_defined? ? variables_value : nil, environment: environment_defined? ? environment_value : nil, environment_name: environment_defined? ? environment_value[:name] : nil, + coverage_regex: coverage_regex_defined? ? coverage_regex_value : nil, artifacts: artifacts_value, after_script: after_script_value } end diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb index f01975aab5c..34e7052befc 100644 --- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb @@ -28,17 +28,21 @@ module Gitlab value.is_a?(String) || value.is_a?(Symbol) end + def validate_regexp(value) + !!::Regexp.new(value) + rescue RegexpError + false + end + def validate_string_or_regexp(value) return true if value.is_a?(Symbol) return false unless value.is_a?(String) if value.first == '/' && value.last == '/' - Regexp.new(value[1...-1]) + validate_regexp(value[1...-1]) else true end - rescue RegexpError - false end def validate_boolean(value) diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index 8632dd0e233..03a8205b081 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -54,6 +54,16 @@ module Gitlab end end + class RegexpValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_regexp(value) + record.errors.add(attribute, 'must be a regular expression') + end + end + end + class TypeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) type = options[:with] diff --git a/lib/gitlab/ci/config/node/regexp.rb b/lib/gitlab/ci/config/node/regexp.rb new file mode 100644 index 00000000000..7c5843eb8b6 --- /dev/null +++ b/lib/gitlab/ci/config/node/regexp.rb @@ -0,0 +1,26 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a Regular Expression. + # + class Regexp < Entry + include Validatable + + validations do + validates :config, regexp: true + end + + def value + if @config.first == '/' && @config.last == '/' + @config[1...-1] + else + @config + end + end + end + end + end + end +end From 646b9c54d043edf17924e82d8e80a56e18d14ce4 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Fri, 18 Nov 2016 01:42:35 -0200 Subject: [PATCH 087/174] Comply to requests made in the review and adjust to the Entry/Node changes This commit: * Turns `coverage_regex` into `coverage` entry in yml file * Fixes smaller requests from code reviewers for the previous commit * This commit is temporary (will be squashed afterwards) This commit does not (further commits will do though): * Add global `coverage` entry handling in yml file as suggested by Grzegorz * Add specs * Create changelog * Create docs --- app/models/ci/build.rb | 6 +++--- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- .../ci/config/{node/regexp.rb => entry/coverage.rb} | 4 ++-- lib/gitlab/ci/config/entry/job.rb | 8 ++++---- lib/gitlab/ci/config/entry/legacy_validation_helpers.rb | 3 ++- 5 files changed, 12 insertions(+), 11 deletions(-) rename lib/gitlab/ci/config/{node/regexp.rb => entry/coverage.rb} (90%) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 4691b33ee9b..46a6b4c724a 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -276,8 +276,8 @@ module Ci def update_coverage return unless project - return unless coverage_regex = self.coverage_regex - coverage = extract_coverage(trace, coverage_regex) + return unless regex = self.coverage_regex + coverage = extract_coverage(trace, regex) if coverage.is_a? Numeric update_attributes(coverage: coverage) @@ -522,7 +522,7 @@ module Ci end def coverage_regex - read_attribute(:coverage_regex) || build_attributes_from_config[:coverage] || project.build_coverage_regex + read_attribute(:coverage_regex) || project.build_coverage_regex end def when diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 2f5ef4d36bd..649ee4d018b 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -61,7 +61,7 @@ module Ci allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', environment: job[:environment_name], - coverage_regex: job[:coverage_regex], + coverage_regex: job[:coverage], yaml_variables: yaml_variables(name), options: { image: job[:image], diff --git a/lib/gitlab/ci/config/node/regexp.rb b/lib/gitlab/ci/config/entry/coverage.rb similarity index 90% rename from lib/gitlab/ci/config/node/regexp.rb rename to lib/gitlab/ci/config/entry/coverage.rb index 7c5843eb8b6..88fc03db2d9 100644 --- a/lib/gitlab/ci/config/node/regexp.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -1,11 +1,11 @@ module Gitlab module Ci class Config - module Node + module Entry ## # Entry that represents a Regular Expression. # - class Regexp < Entry + class Coverage < Node include Validatable validations do diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 3c7ef99cefc..bde6663344a 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -11,7 +11,7 @@ module Gitlab ALLOWED_KEYS = %i[tags script only except type image services allow_failure type stage when artifacts cache dependencies before_script - after_script variables environment coverage_regex] + after_script variables environment coverage] validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -71,12 +71,12 @@ module Gitlab entry :environment, Entry::Environment, description: 'Environment configuration for this job.' - node :coverage_regex, Node::Regexp, + entry :coverage, Entry::Coverage, description: 'Coverage scanning regex configuration for this job.' helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, - :artifacts, :commands, :environment, :coverage_regex + :artifacts, :commands, :environment, :coverage attributes :script, :tags, :allow_failure, :when, :dependencies @@ -133,7 +133,7 @@ module Gitlab variables: variables_defined? ? variables_value : nil, environment: environment_defined? ? environment_value : nil, environment_name: environment_defined? ? environment_value[:name] : nil, - coverage_regex: coverage_regex_defined? ? coverage_regex_value : nil, + coverage: coverage_defined? ? coverage_value : nil, artifacts: artifacts_value, after_script: after_script_value } end diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb index 34e7052befc..98db4632dad 100644 --- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb @@ -29,7 +29,8 @@ module Gitlab end def validate_regexp(value) - !!::Regexp.new(value) + Regexp.new(value) + true rescue RegexpError false end From d0afc500e30ad0fe334d6dc16dd1766d8f6c523a Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Sat, 19 Nov 2016 22:48:02 -0200 Subject: [PATCH 088/174] Change expected `coverage` structure for CI configuration YAML file Instead of using: `coverage: /\(\d+.\d+%\) covered/` This structure must be used now: ``` coverage: output_filter: /\(\d+.\d+%\) covered/` ``` The surrounding '/' is optional. --- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- lib/gitlab/ci/config/entry/coverage.rb | 22 ++++++++++++++----- lib/gitlab/ci/config/entry/job.rb | 2 +- .../config/entry/legacy_validation_helpers.rb | 4 ++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 649ee4d018b..02944e0385a 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -61,7 +61,7 @@ module Ci allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', environment: job[:environment_name], - coverage_regex: job[:coverage], + coverage_regex: job[:coverage][:output_filter], yaml_variables: yaml_variables(name), options: { image: job[:image], diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb index 88fc03db2d9..e5da3cf23fd 100644 --- a/lib/gitlab/ci/config/entry/coverage.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -8,16 +8,26 @@ module Gitlab class Coverage < Node include Validatable + ALLOWED_KEYS = %i[output_filter] + validations do - validates :config, regexp: true + validates :config, type: Hash + validates :config, allowed_keys: ALLOWED_KEYS + validates :output_filter, regexp: true + end + + def output_filter + output_filter_value = @config[:output_filter].to_s + + if output_filter_value.start_with?('/') && output_filter_value.end_with?('/') + output_filter_value[1...-1] + else + value[:output_filter] + end end def value - if @config.first == '/' && @config.last == '/' - @config[1...-1] - else - @config - end + @config.merge(output_filter: output_filter) end end end diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index bde6663344a..69a5e6f433d 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -72,7 +72,7 @@ module Gitlab description: 'Environment configuration for this job.' entry :coverage, Entry::Coverage, - description: 'Coverage scanning regex configuration for this job.' + description: 'Coverage configuration for this job.' helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb index 98db4632dad..d8e74b15712 100644 --- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb @@ -31,7 +31,7 @@ module Gitlab def validate_regexp(value) Regexp.new(value) true - rescue RegexpError + rescue RegexpError, TypeError false end @@ -39,7 +39,7 @@ module Gitlab return true if value.is_a?(Symbol) return false unless value.is_a?(String) - if value.first == '/' && value.last == '/' + if value.start_with?('/') && value.end_with?('/') validate_regexp(value[1...-1]) else true From 9f97cc6515ac1254c443673c84700942690bbc15 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Sun, 20 Nov 2016 00:05:49 -0200 Subject: [PATCH 089/174] Add `coverage` to the Global config entry as well --- lib/gitlab/ci/config/entry/global.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb index a4ec8f0ff2f..ede97cc0504 100644 --- a/lib/gitlab/ci/config/entry/global.rb +++ b/lib/gitlab/ci/config/entry/global.rb @@ -33,8 +33,11 @@ module Gitlab entry :cache, Entry::Cache, description: 'Configure caching between build jobs.' + entry :coverage, Entry::Coverage, + description: 'Coverage configuration for this pipeline.' + helpers :before_script, :image, :services, :after_script, - :variables, :stages, :types, :cache, :jobs + :variables, :stages, :types, :cache, :coverage, :jobs def compose!(_deps = nil) super(self) do From 94eb2f47c732dc9485aba4ebe52238e882a43473 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Sun, 20 Nov 2016 02:18:58 -0200 Subject: [PATCH 090/174] Add changelog entry and extend the documentation accordingly --- changelogs/unreleased/issue-20428.yml | 4 +++ doc/ci/yaml/README.md | 51 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 changelogs/unreleased/issue-20428.yml diff --git a/changelogs/unreleased/issue-20428.yml b/changelogs/unreleased/issue-20428.yml new file mode 100644 index 00000000000..60da1c14702 --- /dev/null +++ b/changelogs/unreleased/issue-20428.yml @@ -0,0 +1,4 @@ +--- +title: Add ability to define a coverage regex in the .gitlab-ci.yml +merge_request: 7447 +author: Leandro Camargo diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 75a0897eb15..a8c0721bbcc 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -76,6 +76,7 @@ There are a few reserved `keywords` that **cannot** be used as job names: | after_script | no | Define commands that run after each job's script | | variables | no | Define build variables | | cache | no | Define list of files that should be cached between subsequent runs | +| coverage | no | Define coverage settings for all jobs | ### image and services @@ -278,6 +279,33 @@ cache: untracked: true ``` +### coverage + +`coverage` allows you to configure how coverage will be filtered out from the +build outputs. Setting this up globally will make all the jobs to use this +setting for output filtering and extracting the coverage information from your +builds. + +#### coverage:output_filter + +For now, there is only the `output_filter` directive expected to be inside the +`coverage` entry. And it is expected to be a regular expression. + +So, in the end, you're going to have something like the following: + +```yaml +coverage: + output_filter: /\(\d+\.\d+\) covered\./ +``` + +It's worth to keep in mind that the surrounding `/` is optional. So, the above +example is the same as the following: + +```yaml +coverage: + output_filter: \(\d+\.\d+\) covered\. +``` + ## Jobs `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job @@ -319,6 +347,8 @@ job_name: | before_script | no | Override a set of commands that are executed before build | | after_script | no | Override a set of commands that are executed after build | | environment | no | Defines a name of environment to which deployment is done by this build | +| environment | no | Defines a name of environment to which deployment is done by this build | +| coverage | no | Define coverage settings for a given job | ### script @@ -993,6 +1023,27 @@ job: - execute this after my script ``` +### job coverage + +This entry is pretty much the same as described in the global context in +[`coverage`](#coverage). The only difference is that, by setting it inside +the job level, whatever is set in there will take precedence over what has +been defined in the global level. A quick example of one overwritting the +other would be: + +```yaml +coverage: + output_filter: /\(\d+\.\d+\) covered\./ + +job1: + coverage: + output_filter: /Code coverage: \d+\.\d+/ +``` + +In the example above, considering the context of the job `job1`, the coverage +regex that would be used is `/Code coverage: \d+\.\d+/` instead of +`/\(\d+\.\d+\) covered\./`. + ## Git Strategy > Introduced in GitLab 8.9 as an experimental feature. May change or be removed From 0713a7c3a9eb1bcfdf6adde0c3365549c19a3ee1 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Mon, 21 Nov 2016 02:38:03 -0200 Subject: [PATCH 091/174] Add specs to cover the implemented feature and fix a small bug --- lib/gitlab/ci/config/entry/coverage.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 31 ++++++++++++++ .../gitlab/ci/config/entry/coverage_spec.rb | 40 +++++++++++++++++++ .../lib/gitlab/ci/config/entry/global_spec.rb | 17 +++++--- spec/lib/gitlab/ci/config/entry/job_spec.rb | 14 +++++++ spec/models/ci/build_spec.rb | 33 +++++++++++++++ 6 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 spec/lib/gitlab/ci/config/entry/coverage_spec.rb diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb index e5da3cf23fd..af12837130c 100644 --- a/lib/gitlab/ci/config/entry/coverage.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -22,7 +22,7 @@ module Gitlab if output_filter_value.start_with?('/') && output_filter_value.end_with?('/') output_filter_value[1...-1] else - value[:output_filter] + @config[:output_filter] end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index f824e2e1efe..eb2d9c6e0e3 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -4,6 +4,37 @@ module Ci describe GitlabCiYamlProcessor, lib: true do let(:path) { 'path' } + describe '#build_attributes' do + context 'Coverage entry' do + subject { described_class.new(config, path).build_attributes(:rspec) } + + let(:config_base) { { rspec: { script: "rspec" } } } + let(:config) { YAML.dump(config_base) } + + context 'when config has coverage set at the global scope' do + before do + config_base.update( + coverage: { output_filter: '\(\d+\.\d+\) covered' } + ) + end + + context 'and \'rspec\' job doesn\'t have coverage set' do + it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') } + end + + context 'but \'rspec\' job also has coverage set' do + before do + config_base[:rspec].update( + coverage: { output_filter: '/Code coverage: \d+\.\d+/' } + ) + end + + it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') } + end + end + end + end + describe "#builds_for_ref" do let(:type) { 'test' } diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb new file mode 100644 index 00000000000..9e59755d9f8 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Coverage do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) { { output_filter: 'Code coverage: \d+\.\d+' } } + + describe '#value' do + subject { entry.value } + it { is_expected.to eq config } + end + + describe '#errors' do + subject { entry.errors } + it { is_expected.to be_empty } + end + + describe '#valid?' do + subject { entry } + it { is_expected.to be_valid } + end + end + + context 'when entry value is not correct' do + let(:config) { { output_filter: '(malformed regexp' } } + + describe '#errors' do + subject { entry.errors } + it { is_expected.to include /coverage output filter must be a regular expression/ } + end + + describe '#valid?' do + subject { entry } + it { is_expected.not_to be_valid } + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index e64c8d46bd8..66a1380bc61 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -4,12 +4,19 @@ describe Gitlab::Ci::Config::Entry::Global do let(:global) { described_class.new(hash) } describe '.nodes' do - it 'can contain global config keys' do - expect(described_class.nodes).to include :before_script - end + subject { described_class.nodes } - it 'returns a hash' do - expect(described_class.nodes).to be_a Hash + it { is_expected.to be_a Hash } + + context 'when filtering all the entry/node names' do + subject { described_class.nodes.keys } + + let(:result) do + %i[before_script image services after_script variables stages types + cache coverage] + end + + it { is_expected.to match_array result } end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index fc9b8b86dc4..d20f4ec207d 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Job do let(:entry) { described_class.new(config, name: :rspec) } + describe '.nodes' do + context 'when filtering all the entry/node names' do + subject { described_class.nodes.keys } + + let(:result) do + %i[before_script script stage type after_script cache + image services only except variables artifacts + environment coverage] + end + + it { is_expected.to match_array result } + end + end + describe 'validations' do before { entry.compose! } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index f031876e812..9e5481017e2 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -221,6 +221,39 @@ describe Ci::Build, :models do end end + describe '#coverage_regex' do + subject { build.coverage_regex } + let(:project_regex) { '\(\d+\.\d+\) covered' } + let(:build_regex) { 'Code coverage: \d+\.\d+' } + + context 'when project has build_coverage_regex set' do + before { project.build_coverage_regex = project_regex } + + context 'and coverage_regex attribute is not set' do + it { is_expected.to eq(project_regex) } + end + + context 'but coverage_regex attribute is also set' do + before { build.coverage_regex = build_regex } + it { is_expected.to eq(build_regex) } + end + end + + context 'when neither project nor build has coverage regex set' do + it { is_expected.to be_nil } + end + end + + describe '#update_coverage' do + it 'grants coverage_regex method is called inside of it' do + build.coverage_regex = '\(\d+.\d+\%\) covered' + allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' } + allow(build).to receive(:coverage_regex).and_call_original + allow(build).to receive(:update_attributes).with(coverage: 98.29) { true } + expect(build.update_coverage).to be true + end + end + describe 'deployment' do describe '#last_deployment' do subject { build.last_deployment } From bb12ee051f95ee747c0e2b98a85675de53dca8ea Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Mon, 21 Nov 2016 02:51:29 -0200 Subject: [PATCH 092/174] Fix wrong description for Coverage entry (in ruby comments) --- lib/gitlab/ci/config/entry/coverage.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb index af12837130c..41e1d6e0c86 100644 --- a/lib/gitlab/ci/config/entry/coverage.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -3,7 +3,7 @@ module Gitlab class Config module Entry ## - # Entry that represents a Regular Expression. + # Entry that represents Coverage settings. # class Coverage < Node include Validatable From f1e920ed86133bfea0abfc66ca44282813822073 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Sat, 26 Nov 2016 01:02:08 -0200 Subject: [PATCH 093/174] Simplify coverage setting and comply to some requests in code review --- doc/ci/yaml/README.md | 30 +++++-------------- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- lib/gitlab/ci/config/entry/coverage.rb | 22 ++++---------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 17 +++-------- .../gitlab/ci/config/entry/coverage_spec.rb | 14 ++++----- spec/models/ci/build_spec.rb | 7 +++-- 6 files changed, 29 insertions(+), 63 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index a8c0721bbcc..0a264c0e228 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -286,24 +286,11 @@ build outputs. Setting this up globally will make all the jobs to use this setting for output filtering and extracting the coverage information from your builds. -#### coverage:output_filter - -For now, there is only the `output_filter` directive expected to be inside the -`coverage` entry. And it is expected to be a regular expression. - -So, in the end, you're going to have something like the following: +Regular expressions are used by default. So using surrounding `/` is optional, given it'll always be read as a regular expression. Don't forget to escape special characters whenever you want to match them in the regular expression. +A simple example: ```yaml -coverage: - output_filter: /\(\d+\.\d+\) covered\./ -``` - -It's worth to keep in mind that the surrounding `/` is optional. So, the above -example is the same as the following: - -```yaml -coverage: - output_filter: \(\d+\.\d+\) covered\. +coverage: \(\d+\.\d+\) covered\. ``` ## Jobs @@ -347,7 +334,6 @@ job_name: | before_script | no | Override a set of commands that are executed before build | | after_script | no | Override a set of commands that are executed after build | | environment | no | Defines a name of environment to which deployment is done by this build | -| environment | no | Defines a name of environment to which deployment is done by this build | | coverage | no | Define coverage settings for a given job | ### script @@ -1032,17 +1018,15 @@ been defined in the global level. A quick example of one overwritting the other would be: ```yaml -coverage: - output_filter: /\(\d+\.\d+\) covered\./ +coverage: \(\d+\.\d+\) covered\. job1: - coverage: - output_filter: /Code coverage: \d+\.\d+/ + coverage: Code coverage: \d+\.\d+ ``` In the example above, considering the context of the job `job1`, the coverage -regex that would be used is `/Code coverage: \d+\.\d+/` instead of -`/\(\d+\.\d+\) covered\./`. +regex that would be used is `Code coverage: \d+\.\d+` instead of +`\(\d+\.\d+\) covered\.`. ## Git Strategy diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 02944e0385a..649ee4d018b 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -61,7 +61,7 @@ module Ci allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', environment: job[:environment_name], - coverage_regex: job[:coverage][:output_filter], + coverage_regex: job[:coverage], yaml_variables: yaml_variables(name), options: { image: job[:image], diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb index 41e1d6e0c86..aa738fcfd11 100644 --- a/lib/gitlab/ci/config/entry/coverage.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -8,26 +8,16 @@ module Gitlab class Coverage < Node include Validatable - ALLOWED_KEYS = %i[output_filter] - validations do - validates :config, type: Hash - validates :config, allowed_keys: ALLOWED_KEYS - validates :output_filter, regexp: true - end - - def output_filter - output_filter_value = @config[:output_filter].to_s - - if output_filter_value.start_with?('/') && output_filter_value.end_with?('/') - output_filter_value[1...-1] - else - @config[:output_filter] - end + validates :config, regexp: true end def value - @config.merge(output_filter: output_filter) + if @config.start_with?('/') && @config.end_with?('/') + @config[1...-1] + else + @config + end end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index eb2d9c6e0e3..ac706216d5a 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -9,26 +9,17 @@ module Ci subject { described_class.new(config, path).build_attributes(:rspec) } let(:config_base) { { rspec: { script: "rspec" } } } - let(:config) { YAML.dump(config_base) } + let(:config) { YAML.dump(config_base) } context 'when config has coverage set at the global scope' do - before do - config_base.update( - coverage: { output_filter: '\(\d+\.\d+\) covered' } - ) - end + before { config_base.update(coverage: '\(\d+\.\d+\) covered') } - context 'and \'rspec\' job doesn\'t have coverage set' do + context "and 'rspec' job doesn't have coverage set" do it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') } end context 'but \'rspec\' job also has coverage set' do - before do - config_base[:rspec].update( - coverage: { output_filter: '/Code coverage: \d+\.\d+/' } - ) - end - + before { config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/' } it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') } end end diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb index 9e59755d9f8..0549dbc732b 100644 --- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb @@ -5,35 +5,35 @@ describe Gitlab::Ci::Config::Entry::Coverage do describe 'validations' do context 'when entry config value is correct' do - let(:config) { { output_filter: 'Code coverage: \d+\.\d+' } } + let(:config) { 'Code coverage: \d+\.\d+' } describe '#value' do subject { entry.value } - it { is_expected.to eq config } + it { is_expected.to eq config } end describe '#errors' do subject { entry.errors } - it { is_expected.to be_empty } + it { is_expected.to be_empty } end describe '#valid?' do subject { entry } - it { is_expected.to be_valid } + it { is_expected.to be_valid } end end context 'when entry value is not correct' do - let(:config) { { output_filter: '(malformed regexp' } } + let(:config) { '(malformed regexp' } describe '#errors' do subject { entry.errors } - it { is_expected.to include /coverage output filter must be a regular expression/ } + it { is_expected.to include /coverage config must be a regular expression/ } end describe '#valid?' do subject { entry } - it { is_expected.not_to be_valid } + it { is_expected.not_to be_valid } end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 9e5481017e2..7c054dd95f5 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -222,9 +222,10 @@ describe Ci::Build, :models do end describe '#coverage_regex' do - subject { build.coverage_regex } + subject { build.coverage_regex } + let(:project_regex) { '\(\d+\.\d+\) covered' } - let(:build_regex) { 'Code coverage: \d+\.\d+' } + let(:build_regex) { 'Code coverage: \d+\.\d+' } context 'when project has build_coverage_regex set' do before { project.build_coverage_regex = project_regex } @@ -235,7 +236,7 @@ describe Ci::Build, :models do context 'but coverage_regex attribute is also set' do before { build.coverage_regex = build_regex } - it { is_expected.to eq(build_regex) } + it { is_expected.to eq(build_regex) } end end From 6323cd7203dbf1850e7939e81db4b1a9c6cf6d76 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Mon, 5 Dec 2016 02:00:47 -0200 Subject: [PATCH 094/174] Comply to more requirements and requests made in the code review --- app/models/ci/build.rb | 2 +- doc/ci/yaml/README.md | 16 +++++++++------- lib/gitlab/ci/config/entry/coverage.rb | 2 +- .../ci/config/entry/legacy_validation_helpers.rb | 5 ++--- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 9 +++++++-- spec/models/ci/build_spec.rb | 11 ++++++++--- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 46a6b4c724a..951818ad561 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -522,7 +522,7 @@ module Ci end def coverage_regex - read_attribute(:coverage_regex) || project.build_coverage_regex + super || project.build_coverage_regex end def when diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 0a264c0e228..5e2d9788f33 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -286,11 +286,13 @@ build outputs. Setting this up globally will make all the jobs to use this setting for output filtering and extracting the coverage information from your builds. -Regular expressions are used by default. So using surrounding `/` is optional, given it'll always be read as a regular expression. Don't forget to escape special characters whenever you want to match them in the regular expression. +Regular expressions are used by default. So using surrounding `/` is optional, +given it'll always be read as a regular expression. Don't forget to escape +special characters whenever you want to match them literally. A simple example: ```yaml -coverage: \(\d+\.\d+\) covered\. +coverage: /\(\d+\.\d+\) covered\./ ``` ## Jobs @@ -1014,19 +1016,19 @@ job: This entry is pretty much the same as described in the global context in [`coverage`](#coverage). The only difference is that, by setting it inside the job level, whatever is set in there will take precedence over what has -been defined in the global level. A quick example of one overwritting the +been defined in the global level. A quick example of one overriding the other would be: ```yaml -coverage: \(\d+\.\d+\) covered\. +coverage: /\(\d+\.\d+\) covered\./ job1: - coverage: Code coverage: \d+\.\d+ + coverage: /Code coverage: \d+\.\d+/ ``` In the example above, considering the context of the job `job1`, the coverage -regex that would be used is `Code coverage: \d+\.\d+` instead of -`\(\d+\.\d+\) covered\.`. +regex that would be used is `/Code coverage: \d+\.\d+/` instead of +`/\(\d+\.\d+\) covered\./`. ## Git Strategy diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb index aa738fcfd11..706bfc882de 100644 --- a/lib/gitlab/ci/config/entry/coverage.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -13,7 +13,7 @@ module Gitlab end def value - if @config.start_with?('/') && @config.end_with?('/') + if @config.first == '/' && @config.last == '/' @config[1...-1] else @config diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb index d8e74b15712..9b9a0a8125a 100644 --- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb @@ -29,8 +29,7 @@ module Gitlab end def validate_regexp(value) - Regexp.new(value) - true + !value.nil? && Regexp.new(value.to_s) && true rescue RegexpError, TypeError false end @@ -39,7 +38,7 @@ module Gitlab return true if value.is_a?(Symbol) return false unless value.is_a?(String) - if value.start_with?('/') && value.end_with?('/') + if value.first == '/' && value.last == '/' validate_regexp(value[1...-1]) else true diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ac706216d5a..3ffcfaa1f29 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -12,14 +12,19 @@ module Ci let(:config) { YAML.dump(config_base) } context 'when config has coverage set at the global scope' do - before { config_base.update(coverage: '\(\d+\.\d+\) covered') } + before do + config_base.update(coverage: '\(\d+\.\d+\) covered') + end context "and 'rspec' job doesn't have coverage set" do it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') } end context 'but \'rspec\' job also has coverage set' do - before { config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/' } + before do + config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/' + end + it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') } end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7c054dd95f5..7baaef9c85e 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -228,14 +228,19 @@ describe Ci::Build, :models do let(:build_regex) { 'Code coverage: \d+\.\d+' } context 'when project has build_coverage_regex set' do - before { project.build_coverage_regex = project_regex } + before do + project.build_coverage_regex = project_regex + end context 'and coverage_regex attribute is not set' do it { is_expected.to eq(project_regex) } end context 'but coverage_regex attribute is also set' do - before { build.coverage_regex = build_regex } + before do + build.coverage_regex = build_regex + end + it { is_expected.to eq(build_regex) } end end @@ -250,7 +255,7 @@ describe Ci::Build, :models do build.coverage_regex = '\(\d+.\d+\%\) covered' allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' } allow(build).to receive(:coverage_regex).and_call_original - allow(build).to receive(:update_attributes).with(coverage: 98.29) { true } + expect(build).to receive(:update_attributes).with(coverage: 98.29) { true } expect(build.update_coverage).to be true end end From f8bec0d1fb05d2c3e87a0470579ee7a650ade23c Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Tue, 6 Dec 2016 02:39:59 -0200 Subject: [PATCH 095/174] Improve specs styles/organization and add some more specs --- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- .../gitlab/ci/config/entry/coverage_spec.rb | 25 ++++++++++++++++--- .../lib/gitlab/ci/config/entry/global_spec.rb | 18 ++++++------- spec/models/ci/build_spec.rb | 13 +++++----- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 3ffcfaa1f29..b1e09350847 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -20,7 +20,7 @@ module Ci it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') } end - context 'but \'rspec\' job also has coverage set' do + context "but 'rspec' job also has coverage set" do before do config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/' end diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb index 0549dbc732b..8f989ebd732 100644 --- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb @@ -4,12 +4,31 @@ describe Gitlab::Ci::Config::Entry::Coverage do let(:entry) { described_class.new(config) } describe 'validations' do - context 'when entry config value is correct' do + context "when entry config value is correct without surrounding '/'" do let(:config) { 'Code coverage: \d+\.\d+' } describe '#value' do subject { entry.value } - it { is_expected.to eq config } + it { is_expected.to eq(config) } + end + + describe '#errors' do + subject { entry.errors } + it { is_expected.to be_empty } + end + + describe '#valid?' do + subject { entry } + it { is_expected.to be_valid } + end + end + + context "when entry config value is correct with surrounding '/'" do + let(:config) { '/Code coverage: \d+\.\d+/' } + + describe '#value' do + subject { entry.value } + it { is_expected.to eq(config[1...-1]) } end describe '#errors' do @@ -28,7 +47,7 @@ describe Gitlab::Ci::Config::Entry::Coverage do describe '#errors' do subject { entry.errors } - it { is_expected.to include /coverage config must be a regular expression/ } + it { is_expected.to include(/coverage config must be a regular expression/) } end describe '#valid?' do diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 66a1380bc61..7b7f5761ebd 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -4,19 +4,17 @@ describe Gitlab::Ci::Config::Entry::Global do let(:global) { described_class.new(hash) } describe '.nodes' do - subject { described_class.nodes } - - it { is_expected.to be_a Hash } + it 'returns a hash' do + expect(described_class.nodes).to be_a(Hash) + end context 'when filtering all the entry/node names' do - subject { described_class.nodes.keys } - - let(:result) do - %i[before_script image services after_script variables stages types - cache coverage] + it 'contains the expected node names' do + node_names = described_class.nodes.keys + expect(node_names).to match_array(%i[before_script image services + after_script variables stages + types cache coverage]) end - - it { is_expected.to match_array result } end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7baaef9c85e..52cc45f07b2 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -251,12 +251,13 @@ describe Ci::Build, :models do end describe '#update_coverage' do - it 'grants coverage_regex method is called inside of it' do - build.coverage_regex = '\(\d+.\d+\%\) covered' - allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' } - allow(build).to receive(:coverage_regex).and_call_original - expect(build).to receive(:update_attributes).with(coverage: 98.29) { true } - expect(build.update_coverage).to be true + context "regarding coverage_regex's value," do + it "saves the correct extracted coverage value" do + build.coverage_regex = '\(\d+.\d+\%\) covered' + allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' } + expect(build).to receive(:update_attributes).with(coverage: 98.29) { true } + expect(build.update_coverage).to be true + end end end From be7106a145b1e3d4c6e06503e0f7f3032ace3764 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Wed, 7 Dec 2016 03:01:34 -0200 Subject: [PATCH 096/174] Force coverage value to always be surrounded by '/' --- app/models/ci/build.rb | 10 +++-- doc/ci/yaml/README.md | 7 ++-- lib/gitlab/ci/config/entry/coverage.rb | 8 ---- lib/gitlab/ci/config/entry/trigger.rb | 10 +---- lib/gitlab/ci/config/entry/validators.rb | 39 +++++++++++++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +-- .../gitlab/ci/config/entry/coverage_spec.rb | 37 ++++++++---------- spec/models/ci/build_spec.rb | 9 +++-- 8 files changed, 75 insertions(+), 51 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 951818ad561..e3753869b67 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -275,8 +275,10 @@ module Ci end def update_coverage - return unless project - return unless regex = self.coverage_regex + regex = coverage_regex.to_s[1...-1] + + return if regex.blank? + coverage = extract_coverage(trace, regex) if coverage.is_a? Numeric @@ -522,7 +524,9 @@ module Ci end def coverage_regex - super || project.build_coverage_regex + super || + project.try(:build_coverage_regex).presence && + "/#{project.try(:build_coverage_regex)}/" end def when diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 5e2d9788f33..85b2c75cee8 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -286,9 +286,10 @@ build outputs. Setting this up globally will make all the jobs to use this setting for output filtering and extracting the coverage information from your builds. -Regular expressions are used by default. So using surrounding `/` is optional, -given it'll always be read as a regular expression. Don't forget to escape -special characters whenever you want to match them literally. +Regular expressions are the only valid kind of value expected here. So, using +surrounding `/` is mandatory in order to consistently and explicitly represent +a regular expression string. You must escape special characters if you want to +match them literally. A simple example: ```yaml diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb index 706bfc882de..25546f363fb 100644 --- a/lib/gitlab/ci/config/entry/coverage.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -11,14 +11,6 @@ module Gitlab validations do validates :config, regexp: true end - - def value - if @config.first == '/' && @config.last == '/' - @config[1...-1] - else - @config - end - end end end end diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb index 28b0a9ffe01..16b234e6c59 100644 --- a/lib/gitlab/ci/config/entry/trigger.rb +++ b/lib/gitlab/ci/config/entry/trigger.rb @@ -9,15 +9,7 @@ module Gitlab include Validatable validations do - include LegacyValidationHelpers - - validate :array_of_strings_or_regexps - - def array_of_strings_or_regexps - unless validate_array_of_strings_or_regexps(config) - errors.add(:config, 'should be an array of strings or regexps') - end - end + validates :config, array_of_strings_or_regexps: true end end end diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index 03a8205b081..5f50b80af6c 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -62,6 +62,45 @@ module Gitlab record.errors.add(attribute, 'must be a regular expression') end end + + private + + def look_like_regexp?(value) + value =~ %r{\A/.*/\z} + end + + def validate_regexp(value) + look_like_regexp?(value) && + Regexp.new(value.to_s[1...-1]) && + true + rescue RegexpError + false + end + end + + class ArrayOfStringsOrRegexps < RegexpValidator + def validate_each(record, attribute, value) + unless validate_array_of_strings_or_regexps(value) + record.errors.add(attribute, 'should be an array of strings or regexps') + end + end + + private + + def validate_array_of_strings_or_regexps(values) + values.is_a?(Array) && values.all?(&method(:validate_string_or_regexp)) + end + + def validate_string_or_regexp(value) + return true if value.is_a?(Symbol) + return false unless value.is_a?(String) + + if look_like_regexp?(value) + validate_regexp(value) + else + true + end + end end class TypeValidator < ActiveModel::EachValidator diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index b1e09350847..e2302f5968a 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -13,11 +13,11 @@ module Ci context 'when config has coverage set at the global scope' do before do - config_base.update(coverage: '\(\d+\.\d+\) covered') + config_base.update(coverage: '/\(\d+\.\d+\) covered/') end context "and 'rspec' job doesn't have coverage set" do - it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') } + it { is_expected.to include(coverage_regex: '/\(\d+\.\d+\) covered/') } end context "but 'rspec' job also has coverage set" do @@ -25,7 +25,7 @@ module Ci config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/' end - it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') } + it { is_expected.to include(coverage_regex: '/Code coverage: \d+\.\d+/') } end end end diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb index 8f989ebd732..eb04075f1be 100644 --- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb @@ -4,9 +4,23 @@ describe Gitlab::Ci::Config::Entry::Coverage do let(:entry) { described_class.new(config) } describe 'validations' do - context "when entry config value is correct without surrounding '/'" do + context "when entry config value doesn't have the surrounding '/'" do let(:config) { 'Code coverage: \d+\.\d+' } + describe '#errors' do + subject { entry.errors } + it { is_expected.to include(/coverage config must be a regular expression/) } + end + + describe '#valid?' do + subject { entry } + it { is_expected.not_to be_valid } + end + end + + context "when entry config value has the surrounding '/'" do + let(:config) { '/Code coverage: \d+\.\d+/' } + describe '#value' do subject { entry.value } it { is_expected.to eq(config) } @@ -23,26 +37,7 @@ describe Gitlab::Ci::Config::Entry::Coverage do end end - context "when entry config value is correct with surrounding '/'" do - let(:config) { '/Code coverage: \d+\.\d+/' } - - describe '#value' do - subject { entry.value } - it { is_expected.to eq(config[1...-1]) } - end - - describe '#errors' do - subject { entry.errors } - it { is_expected.to be_empty } - end - - describe '#valid?' do - subject { entry } - it { is_expected.to be_valid } - end - end - - context 'when entry value is not correct' do + context 'when entry value is not valid' do let(:config) { '(malformed regexp' } describe '#errors' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 52cc45f07b2..f23155a5d13 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -224,19 +224,20 @@ describe Ci::Build, :models do describe '#coverage_regex' do subject { build.coverage_regex } - let(:project_regex) { '\(\d+\.\d+\) covered' } - let(:build_regex) { 'Code coverage: \d+\.\d+' } - context 'when project has build_coverage_regex set' do + let(:project_regex) { '\(\d+\.\d+\) covered' } + before do project.build_coverage_regex = project_regex end context 'and coverage_regex attribute is not set' do - it { is_expected.to eq(project_regex) } + it { is_expected.to eq("/#{project_regex}/") } end context 'but coverage_regex attribute is also set' do + let(:build_regex) { '/Code coverage: \d+\.\d+/' } + before do build.coverage_regex = build_regex end From 518fd2eb93711e1e9c3d597a6bdf13366d9abdb5 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Fri, 9 Dec 2016 00:20:39 -0200 Subject: [PATCH 097/174] Improve/polish code logic for some Ci::Build methods --- app/models/ci/build.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e3753869b67..2a613d12913 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -275,11 +275,11 @@ module Ci end def update_coverage - regex = coverage_regex.to_s[1...-1] + regex = coverage_regex - return if regex.blank? + return unless regex - coverage = extract_coverage(trace, regex) + coverage = extract_coverage(trace, regex[1...-1]) if coverage.is_a? Numeric update_attributes(coverage: coverage) @@ -526,7 +526,7 @@ module Ci def coverage_regex super || project.try(:build_coverage_regex).presence && - "/#{project.try(:build_coverage_regex)}/" + "/#{project.build_coverage_regex}/" end def when From 8fe708f4a2850d71c11234b234e039b2a9422299 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Tue, 13 Dec 2016 02:53:12 -0200 Subject: [PATCH 098/174] Make more code improvements around the '/' stripping logic --- app/models/ci/build.rb | 35 +++++++------------ lib/gitlab/ci/config/entry/coverage.rb | 4 +++ lib/gitlab/ci/config/entry/validators.rb | 12 +++---- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 11 ++++-- .../gitlab/ci/config/entry/coverage_spec.rb | 2 +- .../lib/gitlab/ci/config/entry/global_spec.rb | 4 +-- spec/models/ci/build_spec.rb | 4 +-- 7 files changed, 35 insertions(+), 37 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 2a613d12913..62fec28d2d5 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -275,30 +275,23 @@ module Ci end def update_coverage - regex = coverage_regex - - return unless regex - - coverage = extract_coverage(trace, regex[1...-1]) - - if coverage.is_a? Numeric - update_attributes(coverage: coverage) - end + coverage = extract_coverage(trace, coverage_regex) + update_attributes(coverage: coverage) if coverage.is_a?(Numeric) end def extract_coverage(text, regex) - begin - matches = text.scan(Regexp.new(regex)).last - matches = matches.last if matches.kind_of?(Array) - coverage = matches.gsub(/\d+(\.\d+)?/).first + return unless regex - if coverage.present? - coverage.to_f - end - rescue - # if bad regex or something goes wrong we dont want to interrupt transition - # so we just silentrly ignore error for now + matches = text.scan(Regexp.new(regex)).last + matches = matches.last if matches.kind_of?(Array) + coverage = matches.gsub(/\d+(\.\d+)?/).first + + if coverage.present? + coverage.to_f end + rescue + # if bad regex or something goes wrong we dont want to interrupt transition + # so we just silentrly ignore error for now end def has_trace_file? @@ -524,9 +517,7 @@ module Ci end def coverage_regex - super || - project.try(:build_coverage_regex).presence && - "/#{project.build_coverage_regex}/" + super || project.try(:build_coverage_regex) end def when diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb index 25546f363fb..12a063059cb 100644 --- a/lib/gitlab/ci/config/entry/coverage.rb +++ b/lib/gitlab/ci/config/entry/coverage.rb @@ -11,6 +11,10 @@ module Gitlab validations do validates :config, regexp: true end + + def value + @config[1...-1] + end end end end diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index 5f50b80af6c..30c52dd65e8 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -66,7 +66,7 @@ module Gitlab private def look_like_regexp?(value) - value =~ %r{\A/.*/\z} + value.start_with?('/') && value.end_with?('/') end def validate_regexp(value) @@ -78,7 +78,7 @@ module Gitlab end end - class ArrayOfStringsOrRegexps < RegexpValidator + class ArrayOfStringsOrRegexpsValidator < RegexpValidator def validate_each(record, attribute, value) unless validate_array_of_strings_or_regexps(value) record.errors.add(attribute, 'should be an array of strings or regexps') @@ -94,12 +94,8 @@ module Gitlab def validate_string_or_regexp(value) return true if value.is_a?(Symbol) return false unless value.is_a?(String) - - if look_like_regexp?(value) - validate_regexp(value) - else - true - end + return validate_regexp(value) if look_like_regexp?(value) + true end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index e2302f5968a..49349035b3b 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -17,7 +17,7 @@ module Ci end context "and 'rspec' job doesn't have coverage set" do - it { is_expected.to include(coverage_regex: '/\(\d+\.\d+\) covered/') } + it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') } end context "but 'rspec' job also has coverage set" do @@ -25,7 +25,7 @@ module Ci config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/' end - it { is_expected.to include(coverage_regex: '/Code coverage: \d+\.\d+/') } + it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') } end end end @@ -48,6 +48,7 @@ module Ci stage_idx: 1, name: "rspec", commands: "pwd\nrspec", + coverage_regex: nil, tag_list: [], options: {}, allow_failure: false, @@ -462,6 +463,7 @@ module Ci stage_idx: 1, name: "rspec", commands: "pwd\nrspec", + coverage_regex: nil, tag_list: [], options: { image: "ruby:2.1", @@ -490,6 +492,7 @@ module Ci stage_idx: 1, name: "rspec", commands: "pwd\nrspec", + coverage_regex: nil, tag_list: [], options: { image: "ruby:2.5", @@ -729,6 +732,7 @@ module Ci stage_idx: 1, name: "rspec", commands: "pwd\nrspec", + coverage_regex: nil, tag_list: [], options: { image: "ruby:2.1", @@ -940,6 +944,7 @@ module Ci stage_idx: 1, name: "normal_job", commands: "test", + coverage_regex: nil, tag_list: [], options: {}, when: "on_success", @@ -985,6 +990,7 @@ module Ci stage_idx: 0, name: "job1", commands: "execute-script-for-job", + coverage_regex: nil, tag_list: [], options: {}, when: "on_success", @@ -997,6 +1003,7 @@ module Ci stage_idx: 0, name: "job2", commands: "execute-script-for-job", + coverage_regex: nil, tag_list: [], options: {}, when: "on_success", diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb index eb04075f1be..4c6bd859552 100644 --- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Ci::Config::Entry::Coverage do describe '#value' do subject { entry.value } - it { is_expected.to eq(config) } + it { is_expected.to eq(config[1...-1]) } end describe '#errors' do diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 7b7f5761ebd..d4f1780b174 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -40,7 +40,7 @@ describe Gitlab::Ci::Config::Entry::Global do end it 'creates node object for each entry' do - expect(global.descendants.count).to eq 8 + expect(global.descendants.count).to eq 9 end it 'creates node object using valid class' do @@ -181,7 +181,7 @@ describe Gitlab::Ci::Config::Entry::Global do describe '#nodes' do it 'instantizes all nodes' do - expect(global.descendants.count).to eq 8 + expect(global.descendants.count).to eq 9 end it 'contains unspecified nodes' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index f23155a5d13..fe0a9707b2a 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -232,11 +232,11 @@ describe Ci::Build, :models do end context 'and coverage_regex attribute is not set' do - it { is_expected.to eq("/#{project_regex}/") } + it { is_expected.to eq(project_regex) } end context 'but coverage_regex attribute is also set' do - let(:build_regex) { '/Code coverage: \d+\.\d+/' } + let(:build_regex) { 'Code coverage: \d+\.\d+' } before do build.coverage_regex = build_regex From 441a9beec3e6834d3fe5e047e65c4d8b32ff86d5 Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Wed, 18 Jan 2017 01:42:38 -0200 Subject: [PATCH 099/174] Make some other refinements to validation logic --- lib/gitlab/ci/config/entry/validators.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index 30c52dd65e8..bd7428b1272 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -66,7 +66,8 @@ module Gitlab private def look_like_regexp?(value) - value.start_with?('/') && value.end_with?('/') + value.is_a?(String) && value.start_with?('/') && + value.end_with?('/') end def validate_regexp(value) @@ -92,7 +93,6 @@ module Gitlab end def validate_string_or_regexp(value) - return true if value.is_a?(Symbol) return false unless value.is_a?(String) return validate_regexp(value) if look_like_regexp?(value) true From 1c24c79a83bff0d1535d813eb8146fc799d5d8ac Mon Sep 17 00:00:00 2001 From: Leandro Camargo <leandroico@gmail.com> Date: Wed, 25 Jan 2017 01:48:03 -0200 Subject: [PATCH 100/174] Be more lenient on build coverage value updates and fix specs --- app/models/ci/build.rb | 2 +- spec/lib/gitlab/import_export/safe_model_attributes.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 62fec28d2d5..b1f77bf242c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -276,7 +276,7 @@ module Ci def update_coverage coverage = extract_coverage(trace, coverage_regex) - update_attributes(coverage: coverage) if coverage.is_a?(Numeric) + update_attributes(coverage: coverage) if coverage.present? end def extract_coverage(text, regex) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 493bc2db21a..95b230e4f5c 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -222,6 +222,7 @@ CommitStatus: - queued_at - token - lock_version +- coverage_regex Ci::Variable: - id - project_id From acda0cd48d69dbd98ec9df8339f15139cd098726 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Thu, 26 Jan 2017 19:13:02 +0800 Subject: [PATCH 101/174] @tree_edit_project is no longer used Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_21626434 --- app/controllers/concerns/creates_commit.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 2ece99aebc0..fa7c22b5388 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -101,7 +101,6 @@ module CreatesCommit def set_commit_variables if can?(current_user, :push_code, @project) # Edit file in this project - @tree_edit_project = @project @mr_source_project = @project if @project.forked? @@ -114,10 +113,8 @@ module CreatesCommit @mr_target_branch = @ref || @target_branch end else - # Edit file in fork - @tree_edit_project = current_user.fork_of(@project) # Merge request from fork to this project - @mr_source_project = @tree_edit_project + @mr_source_project = current_user.fork_of(@project) @mr_target_project = @project @mr_target_branch = @ref || @target_branch end From 9bb4cd75ad51f61a53a2bef205be2b4b24acc513 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Thu, 26 Jan 2017 19:22:44 +0800 Subject: [PATCH 102/174] Use commit rather than branch, and rename to avoid confusion Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_21626953 https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_21626952 --- app/models/repository.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index f3a0148c423..6c847e07c00 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -996,10 +996,10 @@ class Repository end end - def check_revert_content(commit, branch_name) - source_sha = find_branch(branch_name).dereferenced_target.sha - args = [commit.id, source_sha] - args << { mainline: 1 } if commit.merge_commit? + def check_revert_content(target_commit, branch_name) + source_sha = commit(branch_name).sha + args = [target_commit.sha, source_sha] + args << { mainline: 1 } if target_commit.merge_commit? revert_index = rugged.revert_commit(*args) return false if revert_index.conflicts? @@ -1010,10 +1010,10 @@ class Repository tree_id end - def check_cherry_pick_content(commit, branch_name) - source_sha = find_branch(branch_name).dereferenced_target.sha - args = [commit.id, source_sha] - args << 1 if commit.merge_commit? + def check_cherry_pick_content(target_commit, branch_name) + source_sha = commit(branch_name).sha + args = [target_commit.sha, source_sha] + args << 1 if target_commit.merge_commit? cherry_pick_index = rugged.cherrypick_commit(*args) return false if cherry_pick_index.conflicts? From 05f4e48a4c761e13faf080e2d33fb8cc9886f723 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Thu, 26 Jan 2017 19:35:19 +0800 Subject: [PATCH 103/174] Make GitHooksService#execute return block value Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_21627207 --- app/services/git_hooks_service.rb | 6 +++--- app/services/git_operation_service.rb | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index 6cd3908d43a..d222d1e63aa 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -18,9 +18,9 @@ class GitHooksService end end - yield self - - run_hook('post-receive') + yield(self).tap do + run_hook('post-receive') + end end private diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index 2b2ba0870a4..df9c393844d 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -107,8 +107,6 @@ class GitOperationService end def with_hooks(ref, newrev, oldrev) - result = nil - GitHooksService.new.execute( user, repository.path_to_repo, @@ -116,10 +114,8 @@ class GitOperationService newrev, ref) do |service| - result = yield(service) if block_given? + yield(service) end - - result end def update_ref(ref, newrev, oldrev) From d475fa094689a6319fa60f2532898234979e30d3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Thu, 26 Jan 2017 21:18:04 +0800 Subject: [PATCH 104/174] We don't care about the return value now --- spec/services/git_hooks_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index 41b0968b8b4..3318dfb22b6 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -21,7 +21,7 @@ describe GitHooksService, services: true do hook = double(trigger: [true, nil]) expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) - expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq([true, nil]) + service.execute(user, @repo_path, @blankrev, @newrev, @ref) { } end end From 8f3aa6ac338ee3595909fea9938611fb03187e6a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Thu, 26 Jan 2017 21:59:12 +0800 Subject: [PATCH 105/174] Try to check if branch diverged explicitly Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_21627134 --- app/services/git_operation_service.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb index df9c393844d..27bcc047601 100644 --- a/app/services/git_operation_service.rb +++ b/app/services/git_operation_service.rb @@ -82,12 +82,7 @@ class GitOperationService end branch = repository.find_branch(branch_name) - oldrev = if branch - # This could verify we're not losing commits - repository.rugged.merge_base(newrev, branch.target) - else - Gitlab::Git::BLANK_SHA - end + oldrev = find_oldrev_from_branch(newrev, branch) ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name update_ref_in_hooks(ref, newrev, oldrev) @@ -100,6 +95,18 @@ class GitOperationService newrev end + def find_oldrev_from_branch(newrev, branch) + return Gitlab::Git::BLANK_SHA unless branch + + oldrev = branch.target + + if oldrev == repository.rugged.merge_base(newrev, branch.target) + oldrev + else + raise Repository::CommitError.new('Branch diverged') + end + end + def update_ref_in_hooks(ref, newrev, oldrev) with_hooks(ref, newrev, oldrev) do update_ref(ref, newrev, oldrev) From dbda72a79999998bfd1d77b3102bc16053a2685e Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 26 Jan 2017 15:30:34 +0100 Subject: [PATCH 106/174] Rename presenters for consitency --- lib/gitlab/chat_commands/command.rb | 2 +- .../{issue_create.rb => issue_new.rb} | 10 +++++----- lib/gitlab/chat_commands/issue_search.rb | 8 +++----- lib/gitlab/chat_commands/issue_show.rb | 2 +- lib/gitlab/chat_commands/presenters/deploy.rb | 8 -------- lib/gitlab/chat_commands/presenters/help.rb | 6 +++--- lib/gitlab/chat_commands/presenters/issuable.rb | 4 +--- .../presenters/{new_issue.rb => issue_new.rb} | 12 +++++++++--- .../{list_issues.rb => issue_search.rb} | 4 +++- .../presenters/{show_issue.rb => issue_show.rb} | 6 ++++-- spec/lib/gitlab/chat_commands/command_spec.rb | 2 +- .../{issue_create_spec.rb => issue_new_spec.rb} | 2 +- .../chat_commands/presenters/issue_new_spec.rb | 17 +++++++++++++++++ ...list_issues_spec.rb => issue_search_spec.rb} | 2 +- .../{show_issue_spec.rb => issue_show_spec.rb} | 2 +- 15 files changed, 51 insertions(+), 36 deletions(-) rename lib/gitlab/chat_commands/{issue_create.rb => issue_new.rb} (88%) rename lib/gitlab/chat_commands/presenters/{new_issue.rb => issue_new.rb} (80%) rename lib/gitlab/chat_commands/presenters/{list_issues.rb => issue_search.rb} (92%) rename lib/gitlab/chat_commands/presenters/{show_issue.rb => issue_show.rb} (89%) rename spec/lib/gitlab/chat_commands/{issue_create_spec.rb => issue_new_spec.rb} (97%) create mode 100644 spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb rename spec/lib/gitlab/chat_commands/presenters/{list_issues_spec.rb => issue_search_spec.rb} (90%) rename spec/lib/gitlab/chat_commands/presenters/{show_issue_spec.rb => issue_show_spec.rb} (92%) diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 4e5031a8a26..e7baa20356c 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -3,7 +3,7 @@ module Gitlab class Command < BaseCommand COMMANDS = [ Gitlab::ChatCommands::IssueShow, - Gitlab::ChatCommands::IssueCreate, + Gitlab::ChatCommands::IssueNew, Gitlab::ChatCommands::IssueSearch, Gitlab::ChatCommands::Deploy, ].freeze diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_new.rb similarity index 88% rename from lib/gitlab/chat_commands/issue_create.rb rename to lib/gitlab/chat_commands/issue_new.rb index 3f3d7de8b2e..016054ecd46 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_new.rb @@ -1,6 +1,6 @@ module Gitlab module ChatCommands - class IssueCreate < IssueCommand + class IssueNew < IssueCommand def self.match(text) # we can not match \n with the dot by passing the m modifier as than # the title and description are not seperated @@ -21,10 +21,10 @@ module Gitlab issue = create_issue(title: title, description: description) - if issue.errors.any? - presenter(issue).display_errors - else + if issue.persisted? presenter(issue).present + else + presenter(issue).display_errors end end @@ -35,7 +35,7 @@ module Gitlab end def presenter(issue) - Gitlab::ChatCommands::Presenters::NewIssue.new(issue) + Gitlab::ChatCommands::Presenters::IssueNew.new(issue) end end end diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb index e2d3a0f466a..3491b53093e 100644 --- a/lib/gitlab/chat_commands/issue_search.rb +++ b/lib/gitlab/chat_commands/issue_search.rb @@ -12,12 +12,10 @@ module Gitlab def execute(match) issues = collection.search(match[:query]).limit(QUERY_LIMIT) - if issues.none? - Presenters::Access.new(issues).not_found - elsif issues.one? - Presenters::ShowIssue.new(issues.first).present + if issues.present? + Presenters::IssueSearch.new(issues).present else - Presenters::ListIssues.new(issues).present + Presenters::Access.new(issues).not_found end end end diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb index 9f3e1b9a64b..d6013f4d10c 100644 --- a/lib/gitlab/chat_commands/issue_show.rb +++ b/lib/gitlab/chat_commands/issue_show.rb @@ -13,7 +13,7 @@ module Gitlab issue = find_by_iid(match[:iid]) if issue - Gitlab::ChatCommands::Presenters::ShowIssue.new(issue).present + Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present else Gitlab::ChatCommands::Presenters::Access.new.not_found end diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb index b1cfaac15af..863d0bf99ca 100644 --- a/lib/gitlab/chat_commands/presenters/deploy.rb +++ b/lib/gitlab/chat_commands/presenters/deploy.rb @@ -15,14 +15,6 @@ module Gitlab def too_many_actions ephemeral_response(text: "Too many actions defined") end - - private - - def resource_url - polymorphic_url( - [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] - ) - end end end end diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb index c7a67467b7e..39ad3249f5b 100644 --- a/lib/gitlab/chat_commands/presenters/help.rb +++ b/lib/gitlab/chat_commands/presenters/help.rb @@ -9,10 +9,10 @@ module Gitlab private def help_message(trigger) - if @resource.none? - "No commands available :thinking_face:" - else + if @resource.present? header_with_list("Available commands", full_commands(trigger)) + else + "No commands available :thinking_face:" end end diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb index 2cb6b1525fc..dfb1c8f6616 100644 --- a/lib/gitlab/chat_commands/presenters/issuable.rb +++ b/lib/gitlab/chat_commands/presenters/issuable.rb @@ -1,9 +1,7 @@ module Gitlab module ChatCommands module Presenters - class Issuable < Presenters::Base - private - + module Issuable def color(issuable) issuable.open? ? '#38ae67' : '#d22852' end diff --git a/lib/gitlab/chat_commands/presenters/new_issue.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb similarity index 80% rename from lib/gitlab/chat_commands/presenters/new_issue.rb rename to lib/gitlab/chat_commands/presenters/issue_new.rb index c7c6febb56e..d26dd22b2a0 100644 --- a/lib/gitlab/chat_commands/presenters/new_issue.rb +++ b/lib/gitlab/chat_commands/presenters/issue_new.rb @@ -1,14 +1,16 @@ module Gitlab module ChatCommands module Presenters - class NewIssue < Presenters::Issuable + class IssueNew < Presenters::Base + include Presenters::Issuable + def present - in_channel_response(show_issue) + in_channel_response(new_issue) end private - def show_issue + def new_issue { attachments: [ { @@ -33,6 +35,10 @@ module Gitlab "I opened an issue on behalf on #{author_profile_link}: *#{@resource.to_reference}* from #{project.name_with_namespace}" end + def project_link + "[#{project.name_with_namespace}](#{url_for(project)})" + end + def author_profile_link "[#{author.to_reference}](#{url_for(author)})" end diff --git a/lib/gitlab/chat_commands/presenters/list_issues.rb b/lib/gitlab/chat_commands/presenters/issue_search.rb similarity index 92% rename from lib/gitlab/chat_commands/presenters/list_issues.rb rename to lib/gitlab/chat_commands/presenters/issue_search.rb index 2458b9356b7..d58a6d6114a 100644 --- a/lib/gitlab/chat_commands/presenters/list_issues.rb +++ b/lib/gitlab/chat_commands/presenters/issue_search.rb @@ -1,7 +1,9 @@ module Gitlab module ChatCommands module Presenters - class ListIssues < Presenters::Issuable + class IssueSearch < Presenters::Base + include Presenters::Issuable + def present text = if @resource.count >= 5 "Here are the first 5 issues I found:" diff --git a/lib/gitlab/chat_commands/presenters/show_issue.rb b/lib/gitlab/chat_commands/presenters/issue_show.rb similarity index 89% rename from lib/gitlab/chat_commands/presenters/show_issue.rb rename to lib/gitlab/chat_commands/presenters/issue_show.rb index e5644a4ad7e..2fc671f13a6 100644 --- a/lib/gitlab/chat_commands/presenters/show_issue.rb +++ b/lib/gitlab/chat_commands/presenters/issue_show.rb @@ -1,7 +1,9 @@ module Gitlab module ChatCommands module Presenters - class ShowIssue < Presenters::Issuable + class IssueShow < Presenters::Base + include Presenters::Issuable + def present in_channel_response(show_issue) end @@ -16,7 +18,7 @@ module Gitlab title_link: resource_url, author_name: author.name, author_icon: author.avatar_url, - fallback: "New issue #{@resource.to_reference}: #{@resource.title}", + fallback: "Issue #{@resource.to_reference}: #{@resource.title}", pretext: pretext, text: text, color: color(@resource), diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index b634df52b68..f4441f9f93c 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -78,7 +78,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'IssueCreate is triggered' do let(:params) { { text: 'issue create my title' } } - it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) } + it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) } end context 'IssueSearch is triggered' do diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_new_spec.rb similarity index 97% rename from spec/lib/gitlab/chat_commands/issue_create_spec.rb rename to spec/lib/gitlab/chat_commands/issue_new_spec.rb index 0f84b19a5a4..84c22328064 100644 --- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_new_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::IssueCreate, service: true do +describe Gitlab::ChatCommands::IssueNew, service: true do describe '#execute' do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb new file mode 100644 index 00000000000..17fcdbc2452 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::IssueNew do + let(:project) { create(:empty_project) } + let(:issue) { create(:issue, project: project) } + let(:attachment) { subject[:attachments].first } + + subject { described_class.new(issue).present } + + it { is_expected.to be_a(Hash) } + + it 'shows the issue' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject).to have_key(:attachments) + expect(attachment[:title]).to start_with(issue.title) + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb similarity index 90% rename from spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb rename to spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb index 13a1f70fe78..ec6d3e34a96 100644 --- a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::ListIssues do +describe Gitlab::ChatCommands::Presenters::IssueSearch do let(:project) { create(:empty_project) } let(:message) { subject[:text] } diff --git a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb similarity index 92% rename from spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb rename to spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb index ca4062e692a..89d154e26e4 100644 --- a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::ShowIssue do +describe Gitlab::ChatCommands::Presenters::IssueShow do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:attachment) { subject[:attachments].first } From eb242fc865c032f6408f3b68700da9b840b416dd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Thu, 26 Jan 2017 22:37:22 +0800 Subject: [PATCH 107/174] Make sure different project gets a merge request Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_21626479 --- app/controllers/concerns/creates_commit.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index fa7c22b5388..6286d67d30c 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -93,8 +93,10 @@ module CreatesCommit def create_merge_request? # XXX: Even if the field is set, if we're checking the same branch - # as the target branch, we don't want to create a merge request. - params[:create_merge_request].present? && @ref != @target_branch + # as the target branch in the same project, + # we don't want to create a merge request. + params[:create_merge_request].present? && + (different_project? || @ref != @target_branch) end # TODO: We should really clean this up From 34a1e3dcdbb7fdfcc1bafdc9dbaeee3c79b94c1c Mon Sep 17 00:00:00 2001 From: Eric Eastwood <contact@ericeastwood.com> Date: Tue, 24 Jan 2017 23:12:06 -0600 Subject: [PATCH 108/174] Fix permalink discussion note being collapsed --- .../javascripts/behaviors/toggler_behavior.js | 26 ++++++++++------- ...ix-discussion-note-permalink-collapsed.yml | 4 +++ .../merge_requests/toggler_behavior_spec.rb | 29 +++++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml create mode 100644 spec/features/merge_requests/toggler_behavior_spec.rb diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 6a49715590c..a7181904ac9 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,6 +1,19 @@ /* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */ (function(w) { $(function() { + var toggleContainer = function(container, /* optional */toggleState) { + var $container = $(container); + + $container + .find('.js-toggle-button .fa') + .toggleClass('fa-chevron-up', toggleState) + .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined); + + $container + .find('.js-toggle-content') + .toggle(toggleState); + }; + // Toggle button. Show/hide content inside parent container. // Button does not change visibility. If button has icon - it changes chevron style. // @@ -10,14 +23,7 @@ // $('body').on('click', '.js-toggle-button', function(e) { e.preventDefault(); - $(this) - .find('.fa') - .toggleClass('fa-chevron-down fa-chevron-up') - .end() - .closest('.js-toggle-container') - .find('.js-toggle-content') - .toggle() - ; + toggleContainer($(this).closest('.js-toggle-container')); }); // If we're accessing a permalink, ensure it is not inside a @@ -26,8 +32,8 @@ var anchor = hash && document.getElementById(hash); var container = anchor && $(anchor).closest('.js-toggle-container'); - if (container && container.find('.js-toggle-content').is(':hidden')) { - container.find('.js-toggle-button').trigger('click'); + if (container) { + toggleContainer(container, true); anchor.scrollIntoView(); } }); diff --git a/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml new file mode 100644 index 00000000000..ddd454da376 --- /dev/null +++ b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml @@ -0,0 +1,4 @@ +--- +title: Fix permalink discussion note being collapsed +merge_request: +author: diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb new file mode 100644 index 00000000000..6958f6a2c9f --- /dev/null +++ b/spec/features/merge_requests/toggler_behavior_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +feature 'toggler_behavior', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, source_project: project, author: user) } + let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } + let(:fragment_id) { "#note_#{note.id}" } + + before do + login_as :admin + project = merge_request.source_project + visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}" + page.current_window.resize_to(1000, 300) + end + + describe 'scroll position' do + it 'should be scrolled down to fragment' do + page_height = page.current_window.size[1] + page_scroll_y = page.evaluate_script("window.scrollY") + fragment_position_top = page.evaluate_script("document.querySelector('#{fragment_id}').getBoundingClientRect().top") + + expect(find('.js-toggle-content').visible?).to eq true + expect(find(fragment_id).visible?).to eq true + expect(fragment_position_top).to be > page_scroll_y + expect(fragment_position_top).to be < (page_scroll_y + page_height) + end + end +end From 444ac6aa02e5b4b7025a9058a98dc6ae8db8e806 Mon Sep 17 00:00:00 2001 From: Clement Ho <ClemMakesApps@gmail.com> Date: Fri, 27 Jan 2017 13:22:12 -0600 Subject: [PATCH 109/174] Fix filtering usernames with multiple words --- .../filtered_search/dropdown_user.js.es6 | 9 ++++- ...filtering-username-with-multiple-words.yml | 4 ++ .../filtered_search/dropdown_user_spec.js.es6 | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/fix-filtering-username-with-multiple-words.yml create mode 100644 spec/javascripts/filtered_search/dropdown_user_spec.js.es6 diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 7bf199d9274..162fd6044e5 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -39,8 +39,15 @@ getSearchInput() { const query = gl.DropdownUtils.getSearchInput(this.input); const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); + let value = lastToken.value || ''; - return lastToken.value || ''; + // Removes the first character if it is a quotation so that we can search + // with multiple words + if (value[0] === '"' || value[0] === '\'') { + value = value.slice(1); + } + + return value; } init() { diff --git a/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml new file mode 100644 index 00000000000..3513f5afdfb --- /dev/null +++ b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml @@ -0,0 +1,4 @@ +--- +title: Fix filtering usernames with multiple words +merge_request: 8851 +author: diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 new file mode 100644 index 00000000000..5eba4343a1d --- /dev/null +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 @@ -0,0 +1,40 @@ +//= require filtered_search/dropdown_utils +//= require filtered_search/filtered_search_tokenizer +//= require filtered_search/filtered_search_dropdown +//= require filtered_search/dropdown_user + +(() => { + describe('Dropdown User', () => { + describe('getSearchInput', () => { + let dropdownUser; + + beforeEach(() => { + spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {}); + spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); + spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); + + dropdownUser = new gl.DropdownUser(); + }); + + it('should not return the double quote found in value', () => { + spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ + lastToken: { + value: '"johnny appleseed', + }, + }); + + expect(dropdownUser.getSearchInput()).toBe('johnny appleseed'); + }); + + it('should not return the single quote found in value', () => { + spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ + lastToken: { + value: '\'larry boy', + }, + }); + + expect(dropdownUser.getSearchInput()).toBe('larry boy'); + }); + }); + }); +})(); From 6dbd60f695faf4126bd9fa2a5cd8a36f672563a4 Mon Sep 17 00:00:00 2001 From: Nur Rony <pro.nmrony@gmail.com> Date: Mon, 30 Jan 2017 13:41:57 +0600 Subject: [PATCH 110/174] replace words user(s) with member(s) --- app/views/groups/group_members/_new_group_member.html.haml | 4 ++-- app/views/groups/group_members/index.html.haml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index b185b81db7f..5b1a4630c56 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -3,7 +3,7 @@ .col-md-4.col-lg-6 = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true) .help-block.append-bottom-10 - Search for users by name, username, or email, or invite new ones using their email address. + Search for members by name, username, or email, or invite new ones using their email address. .col-md-3.col-lg-2 = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select" @@ -16,7 +16,7 @@ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input .help-block.append-bottom-10 - On this date, the user(s) will automatically lose access to this group and all of its projects. + On this date, the member(s) will automatically lose access to this group and all of its projects. .col-md-2 = f.submit 'Add to group', class: "btn btn-create btn-block" diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index f4c432a095a..2e4e4511bb6 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -7,7 +7,7 @@ - if can?(current_user, :admin_group_member, @group) .project-members-new.append-bottom-default %p.clearfix - Add new user to + Add new member to %strong= @group.name = render "new_group_member" @@ -15,7 +15,7 @@ .append-bottom-default.clearfix %h5.member.existing-title - Existing users + Existing members = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } @@ -24,7 +24,7 @@ = render 'shared/members/sort_dropdown' .panel.panel-default .panel-heading - Users with access to + Members with access to %strong= @group.name %span.badge= @members.total_count %ul.content-list From c95d88f923b14a3387c7d148318bcadf50767def Mon Sep 17 00:00:00 2001 From: Nur Rony <pro.nmrony@gmail.com> Date: Mon, 30 Jan 2017 13:50:01 +0600 Subject: [PATCH 111/174] adds changelog --- .../unreleased/25460-replace-word-users-with-members.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/25460-replace-word-users-with-members.yml diff --git a/changelogs/unreleased/25460-replace-word-users-with-members.yml b/changelogs/unreleased/25460-replace-word-users-with-members.yml new file mode 100644 index 00000000000..dac90eaa34d --- /dev/null +++ b/changelogs/unreleased/25460-replace-word-users-with-members.yml @@ -0,0 +1,4 @@ +--- +title: Replace word user with member +merge_request: 8872 +author: From dc6921bdbbabd08be4426345140cb507b286eac7 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Tue, 10 Jan 2017 19:43:58 +0100 Subject: [PATCH 112/174] Chat Commands have presenters This improves the styling and readability of the code. This is supported by both Mattermost and Slack. --- .../chat_slash_commands_service.rb | 22 +-- lib/gitlab/chat_commands/base_command.rb | 4 - lib/gitlab/chat_commands/command.rb | 22 +-- lib/gitlab/chat_commands/deploy.rb | 24 +-- lib/gitlab/chat_commands/issue_create.rb | 18 +- lib/gitlab/chat_commands/issue_search.rb | 10 +- lib/gitlab/chat_commands/issue_show.rb | 8 +- lib/gitlab/chat_commands/presenter.rb | 131 -------------- lib/gitlab/chat_commands/presenters/access.rb | 22 +++ lib/gitlab/chat_commands/presenters/base.rb | 73 ++++++++ lib/gitlab/chat_commands/presenters/deploy.rb | 24 +++ .../chat_commands/presenters/issuable.rb | 33 ++++ .../chat_commands/presenters/list_issues.rb | 32 ++++ .../chat_commands/presenters/show_issue.rb | 38 +++++ lib/mattermost/error.rb | 3 - lib/mattermost/session.rb | 160 ------------------ spec/lib/gitlab/chat_commands/command_spec.rb | 50 +----- spec/lib/gitlab/chat_commands/deploy_spec.rb | 24 +-- .../gitlab/chat_commands/issue_create_spec.rb | 12 +- .../gitlab/chat_commands/issue_search_spec.rb | 12 +- .../gitlab/chat_commands/issue_show_spec.rb | 25 ++- .../chat_commands/presenters/access_spec.rb | 49 ++++++ .../chat_commands/presenters/deploy_spec.rb | 47 +++++ .../presenters/list_issues_spec.rb | 24 +++ .../presenters/show_issue_spec.rb | 27 +++ spec/lib/mattermost/client_spec.rb | 24 --- spec/lib/mattermost/command_spec.rb | 61 ------- spec/lib/mattermost/session_spec.rb | 123 -------------- spec/lib/mattermost/team_spec.rb | 66 -------- 29 files changed, 480 insertions(+), 688 deletions(-) delete mode 100644 lib/gitlab/chat_commands/presenter.rb create mode 100644 lib/gitlab/chat_commands/presenters/access.rb create mode 100644 lib/gitlab/chat_commands/presenters/base.rb create mode 100644 lib/gitlab/chat_commands/presenters/deploy.rb create mode 100644 lib/gitlab/chat_commands/presenters/issuable.rb create mode 100644 lib/gitlab/chat_commands/presenters/list_issues.rb create mode 100644 lib/gitlab/chat_commands/presenters/show_issue.rb delete mode 100644 lib/mattermost/error.rb delete mode 100644 lib/mattermost/session.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/access_spec.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb create mode 100644 spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb delete mode 100644 spec/lib/mattermost/client_spec.rb delete mode 100644 spec/lib/mattermost/command_spec.rb delete mode 100644 spec/lib/mattermost/session_spec.rb delete mode 100644 spec/lib/mattermost/team_spec.rb diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index 2bcff541cc0..608754f3035 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -28,20 +28,24 @@ class ChatSlashCommandsService < Service end def trigger(params) - return unless valid_token?(params[:token]) + return access_presenter unless valid_token?(params[:token]) user = find_chat_user(params) - unless user - url = authorize_chat_name_url(params) - return presenter.authorize_chat_name(url) - end - Gitlab::ChatCommands::Command.new(project, user, - params).execute + if user + Gitlab::ChatCommands::Command.new(project, user, params).execute + else + url = authorize_chat_name_url(params) + access_presenter(url).authorize + end end private + def access_presenter(url = nil) + Gitlab::ChatCommands::Presenters::Access.new(url) + end + def find_chat_user(params) ChatNames::FindUserService.new(self, params).execute end @@ -49,8 +53,4 @@ class ChatSlashCommandsService < Service def authorize_chat_name_url(params) ChatNames::AuthorizeUserService.new(self, params).execute end - - def presenter - Gitlab::ChatCommands::Presenter.new - end end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index 4fe53ce93a9..25da8474e95 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -42,10 +42,6 @@ module Gitlab def find_by_iid(iid) collection.find_by(iid: iid) end - - def presenter - Gitlab::ChatCommands::Presenter.new - end end end end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 145086755e4..ac7ee868402 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -13,9 +13,9 @@ module Gitlab if command if command.allowed?(project, current_user) - present command.new(project, current_user, params).execute(match) + command.new(project, current_user, params).execute(match) else - access_denied + Gitlab::ChatCommands::Presenters::Access.new.access_denied end else help(help_messages) @@ -25,7 +25,7 @@ module Gitlab def match_command match = nil service = available_commands.find do |klass| - match = klass.match(command) + match = klass.match(params[:text]) end [service, match] @@ -42,22 +42,6 @@ module Gitlab klass.available?(project) end end - - def command - params[:text] - end - - def help(messages) - presenter.help(messages, params[:command]) - end - - def access_denied - presenter.access_denied - end - - def present(resource) - presenter.present(resource) - end end end end diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb index 7127d2f6d04..458d90f84e8 100644 --- a/lib/gitlab/chat_commands/deploy.rb +++ b/lib/gitlab/chat_commands/deploy.rb @@ -1,8 +1,6 @@ module Gitlab module ChatCommands class Deploy < BaseCommand - include Gitlab::Routing.url_helpers - def self.match(text) /\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text) end @@ -24,35 +22,29 @@ module Gitlab to = match[:to] actions = find_actions(from, to) - return unless actions.present? - if actions.one? - play!(from, to, actions.first) + if actions.none? + Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions + elsif actions.one? + action = play!(from, to, actions.first) + Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to) else - Result.new(:error, 'Too many actions defined') + Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions end end private def play!(from, to, action) - new_action = action.play(current_user) - - Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.") + action.play(current_user) end def find_actions(from, to) environment = project.environments.find_by(name: from) - return unless environment + return [] unless environment environment.actions_for(to).select(&:starts_environment?) end - - def url(subject) - polymorphic_url( - [subject.project.namespace.becomes(Namespace), subject.project, subject] - ) - end end end end diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index cefb6775db8..a06f13b0f72 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -2,7 +2,7 @@ module Gitlab module ChatCommands class IssueCreate < IssueCommand def self.match(text) - # we can not match \n with the dot by passing the m modifier as than + # we can not match \n with the dot by passing the m modifier as than # the title and description are not seperated /\Aissue\s+(new|create)\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text) end @@ -19,8 +19,24 @@ module Gitlab title = match[:title] description = match[:description].to_s.rstrip + issue = create_issue(title: title, description: description) + + if issue.errors.any? + presenter(issue).display_errors + else + presenter(issue).present + end + end + + private + + def create_issue(title:, description:) Issues::CreateService.new(project, current_user, title: title, description: description).execute end + + def presenter(issue) + Gitlab::ChatCommands::Presenters::ShowIssue.new(issue) + end end end end diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb index 51bf80c800b..e2d3a0f466a 100644 --- a/lib/gitlab/chat_commands/issue_search.rb +++ b/lib/gitlab/chat_commands/issue_search.rb @@ -10,7 +10,15 @@ module Gitlab end def execute(match) - collection.search(match[:query]).limit(QUERY_LIMIT) + issues = collection.search(match[:query]).limit(QUERY_LIMIT) + + if issues.none? + Presenters::Access.new(issues).not_found + elsif issues.one? + Presenters::ShowIssue.new(issues.first).present + else + Presenters::ListIssues.new(issues).present + end end end end diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb index 2a45d49cf6b..9f3e1b9a64b 100644 --- a/lib/gitlab/chat_commands/issue_show.rb +++ b/lib/gitlab/chat_commands/issue_show.rb @@ -10,7 +10,13 @@ module Gitlab end def execute(match) - find_by_iid(match[:iid]) + issue = find_by_iid(match[:iid]) + + if issue + Gitlab::ChatCommands::Presenters::ShowIssue.new(issue).present + else + Gitlab::ChatCommands::Presenters::Access.new.not_found + end end end end diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb deleted file mode 100644 index 8930a21f406..00000000000 --- a/lib/gitlab/chat_commands/presenter.rb +++ /dev/null @@ -1,131 +0,0 @@ -module Gitlab - module ChatCommands - class Presenter - include Gitlab::Routing - - def authorize_chat_name(url) - message = if url - ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." - else - ":sweat_smile: Couldn't identify you, nor can I autorize you!" - end - - ephemeral_response(message) - end - - def help(commands, trigger) - if commands.none? - ephemeral_response("No commands configured") - else - commands.map! { |command| "#{trigger} #{command}" } - message = header_with_list("Available commands", commands) - - ephemeral_response(message) - end - end - - def present(subject) - return not_found unless subject - - if subject.is_a?(Gitlab::ChatCommands::Result) - show_result(subject) - elsif subject.respond_to?(:count) - if subject.none? - not_found - elsif subject.one? - single_resource(subject.first) - else - multiple_resources(subject) - end - else - single_resource(subject) - end - end - - def access_denied - ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") - end - - private - - def show_result(result) - case result.type - when :success - in_channel_response(result.message) - else - ephemeral_response(result.message) - end - end - - def not_found - ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") - end - - def single_resource(resource) - return error(resource) if resource.errors.any? || !resource.persisted? - - message = "#{title(resource)}:" - message << "\n\n#{resource.description}" if resource.try(:description) - - in_channel_response(message) - end - - def multiple_resources(resources) - titles = resources.map { |resource| title(resource) } - - message = header_with_list("Multiple results were found:", titles) - - ephemeral_response(message) - end - - def error(resource) - message = header_with_list("The action was not successful, because:", resource.errors.messages) - - ephemeral_response(message) - end - - def title(resource) - reference = resource.try(:to_reference) || resource.try(:id) - title = resource.try(:title) || resource.try(:name) - - "[#{reference} #{title}](#{url(resource)})" - end - - def header_with_list(header, items) - message = [header] - - items.each do |item| - message << "- #{item}" - end - - message.join("\n") - end - - def url(resource) - url_for( - [ - resource.project.namespace.becomes(Namespace), - resource.project, - resource - ] - ) - end - - def ephemeral_response(message) - { - response_type: :ephemeral, - text: message, - status: 200 - } - end - - def in_channel_response(message) - { - response_type: :in_channel, - text: message, - status: 200 - } - end - end - end -end diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb new file mode 100644 index 00000000000..6d18d745608 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/access.rb @@ -0,0 +1,22 @@ +module Gitlab::ChatCommands::Presenters + class Access < Gitlab::ChatCommands::Presenters::Base + def access_denied + ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).") + end + + def not_found + ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") + end + + def authorize + message = + if @resource + ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})." + else + ":sweat_smile: Couldn't identify you, nor can I autorize you!" + end + + ephemeral_response(text: message) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb new file mode 100644 index 00000000000..0897025d85f --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/base.rb @@ -0,0 +1,73 @@ +module Gitlab::ChatCommands::Presenters + class Base + include Gitlab::Routing.url_helpers + + def initialize(resource = nil) + @resource = resource + end + + def display_errors + message = header_with_list("The action was not successful, because:", @resource.errors.full_messages) + + ephemeral_response(text: message) + end + + private + + def header_with_list(header, items) + message = [header] + + items.each do |item| + message << "- #{item}" + end + + message.join("\n") + end + + def ephemeral_response(message) + response = { + response_type: :ephemeral, + status: 200 + }.merge(message) + + format_response(response) + end + + def in_channel_response(message) + response = { + response_type: :in_channel, + status: 200 + }.merge(message) + + format_response(response) + end + + def format_response(response) + response[:text] = format(response[:text]) if response.has_key?(:text) + + if response.has_key?(:attachments) + response[:attachments].each do |attachment| + attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext] + attachment[:text] = format(attachment[:text]) if attachment[:text] + end + end + + response + end + + # Convert Markdown to slacks format + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def resource_url + url_for( + [ + @resource.project.namespace.becomes(Namespace), + @resource.project, + @resource + ] + ) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb new file mode 100644 index 00000000000..4f6333812ff --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/deploy.rb @@ -0,0 +1,24 @@ +module Gitlab::ChatCommands::Presenters + class Deploy < Gitlab::ChatCommands::Presenters::Base + def present(from, to) + message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})." + in_channel_response(text: message) + end + + def no_actions + ephemeral_response(text: "No action found to be executed") + end + + def too_many_actions + ephemeral_response(text: "Too many actions defined") + end + + private + + def resource_url + polymorphic_url( + [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] + ) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb new file mode 100644 index 00000000000..9623387f188 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/issuable.rb @@ -0,0 +1,33 @@ +module Gitlab::ChatCommands::Presenters + class Issuable < Gitlab::ChatCommands::Presenters::Base + private + + def project + @resource.project + end + + def author + @resource.author + end + + def fields + [ + { + title: "Assignee", + value: @resource.assignee ? @resource.assignee.name : "_None_", + short: true + }, + { + title: "Milestone", + value: @resource.milestone ? @resource.milestone.title : "_None_", + short: true + }, + { + title: "Labels", + value: @resource.labels.any? ? @resource.label_names : "_None_", + short: true + } + ] + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/list_issues.rb b/lib/gitlab/chat_commands/presenters/list_issues.rb new file mode 100644 index 00000000000..5a7b3fca5c2 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/list_issues.rb @@ -0,0 +1,32 @@ +module Gitlab::ChatCommands::Presenters + class ListIssues < Gitlab::ChatCommands::Presenters::Base + def present + ephemeral_response(text: "Here are the issues I found:", attachments: attachments) + end + + private + + def attachments + @resource.map do |issue| + state = issue.open? ? "Open" : "Closed" + + { + fallback: "Issue #{issue.to_reference}: #{issue.title}", + color: "#d22852", + text: "[#{issue.to_reference}](#{url_for([namespace, project, issue])}) · #{issue.title} (#{state})", + mrkdwn_in: [ + "text" + ] + } + end + end + + def project + @project ||= @resource.first.project + end + + def namespace + @namespace ||= project.namespace.becomes(Namespace) + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/show_issue.rb b/lib/gitlab/chat_commands/presenters/show_issue.rb new file mode 100644 index 00000000000..2a89c30b972 --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/show_issue.rb @@ -0,0 +1,38 @@ +module Gitlab::ChatCommands::Presenters + class ShowIssue < Gitlab::ChatCommands::Presenters::Issuable + def present + in_channel_response(show_issue) + end + + private + + def show_issue + { + attachments: [ + { + title: @resource.title, + title_link: resource_url, + author_name: author.name, + author_icon: author.avatar_url, + fallback: "#{@resource.to_reference}: #{@resource.title}", + text: text, + fields: fields, + mrkdwn_in: [ + :title, + :text + ] + } + ] + } + end + + def text + message = "" + message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero? + message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero? + message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero? + + message + end + end +end diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb deleted file mode 100644 index 014df175be0..00000000000 --- a/lib/mattermost/error.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Mattermost - class Error < StandardError; end -end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb deleted file mode 100644 index 377cb7b1021..00000000000 --- a/lib/mattermost/session.rb +++ /dev/null @@ -1,160 +0,0 @@ -module Mattermost - class NoSessionError < Mattermost::Error - def message - 'No session could be set up, is Mattermost configured with Single Sign On?' - end - end - - class ConnectionError < Mattermost::Error; end - - # This class' prime objective is to obtain a session token on a Mattermost - # instance with SSO configured where this GitLab instance is the provider. - # - # The process depends on OAuth, but skips a step in the authentication cycle. - # For example, usually a user would click the 'login in GitLab' button on - # Mattermost, which would yield a 302 status code and redirects you to GitLab - # to approve the use of your account on Mattermost. Which would trigger a - # callback so Mattermost knows this request is approved and gets the required - # data to create the user account etc. - # - # This class however skips the button click, and also the approval phase to - # speed up the process and keep it without manual action and get a session - # going. - class Session - include Doorkeeper::Helpers::Controller - include HTTParty - - LEASE_TIMEOUT = 60 - - base_uri Settings.mattermost.host - - attr_accessor :current_resource_owner, :token - - def initialize(current_user) - @current_resource_owner = current_user - end - - def with_session - with_lease do - raise Mattermost::NoSessionError unless create - - begin - yield self - rescue Errno::ECONNREFUSED - raise Mattermost::NoSessionError - ensure - destroy - end - end - end - - # Next methods are needed for Doorkeeper - def pre_auth - @pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new( - Doorkeeper.configuration, server.client_via_uid, params) - end - - def authorization - @authorization ||= strategy.request - end - - def strategy - @strategy ||= server.authorization_request(pre_auth.response_type) - end - - def request - @request ||= OpenStruct.new(parameters: params) - end - - def params - Rack::Utils.parse_query(oauth_uri.query).symbolize_keys - end - - def get(path, options = {}) - handle_exceptions do - self.class.get(path, options.merge(headers: @headers)) - end - end - - def post(path, options = {}) - handle_exceptions do - self.class.post(path, options.merge(headers: @headers)) - end - end - - private - - def create - return unless oauth_uri - return unless token_uri - - @token = request_token - @headers = { - Authorization: "Bearer #{@token}" - } - - @token - end - - def destroy - post('/api/v3/users/logout') - end - - def oauth_uri - return @oauth_uri if defined?(@oauth_uri) - - @oauth_uri = nil - - response = get("/api/v3/oauth/gitlab/login", follow_redirects: false) - return unless 300 <= response.code && response.code < 400 - - redirect_uri = response.headers['location'] - return unless redirect_uri - - @oauth_uri = URI.parse(redirect_uri) - end - - def token_uri - @token_uri ||= - if oauth_uri - authorization.authorize.redirect_uri if pre_auth.authorizable? - end - end - - def request_token - response = get(token_uri, follow_redirects: false) - - if 200 <= response.code && response.code < 400 - response.headers['token'] - end - end - - def with_lease - lease_uuid = lease_try_obtain - raise NoSessionError unless lease_uuid - - begin - yield - ensure - Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid) - end - end - - def lease_key - "mattermost:session" - end - - def lease_try_obtain - lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) - lease.try_obtain - end - - def handle_exceptions - yield - rescue HTTParty::Error => e - raise Mattermost::ConnectionError.new(e.message) - rescue Errno::ECONNREFUSED - raise Mattermost::ConnectionError.new(e.message) - end - end -end diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index 1e81eaef18c..d8b2303555c 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -5,6 +5,7 @@ describe Gitlab::ChatCommands::Command, service: true do let(:user) { create(:user) } describe '#execute' do +<<<<<<< HEAD subject do described_class.new(project, user, params).execute end @@ -18,6 +19,9 @@ describe Gitlab::ChatCommands::Command, service: true do expect(subject[:text]).to start_with('404 not found') end end +======= + subject { described_class.new(project, user, params).execute } +>>>>>>> Chat Commands have presenters context 'when an unknown command is triggered' do let(:params) { { command: '/gitlab', text: "unknown command 123" } } @@ -34,47 +38,7 @@ describe Gitlab::ChatCommands::Command, service: true do it 'rejects the actions' do expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('Whoops! That action is not allowed') - end - end - - context 'issue is successfully created' do - let(:params) { { text: "issue create my new issue" } } - - before do - project.team << [user, :master] - end - - it 'presents the issue' do - expect(subject[:text]).to match("my new issue") - end - - it 'shows a link to the new issue' do - expect(subject[:text]).to match(/\/issues\/\d+/) - end - end - - context 'searching for an issue' do - let(:params) { { text: 'issue search find me' } } - let!(:issue) { create(:issue, project: project, title: 'find me') } - - before do - project.team << [user, :master] - end - - context 'a single issue is found' do - it 'presents the issue' do - expect(subject[:text]).to match(issue.title) - end - end - - context 'multiple issues found' do - let!(:issue2) { create(:issue, project: project, title: "someone find me") } - - it 'shows a link to the new issue' do - expect(subject[:text]).to match(issue.title) - expect(subject[:text]).to match(issue2.title) - end + expect(subject[:text]).to start_with('Whoops! This action is not allowed') end end @@ -90,7 +54,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'and user can not create deployment' do it 'returns action' do expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('Whoops! That action is not allowed') + expect(subject[:text]).to start_with('Whoops! This action is not allowed') end end @@ -100,7 +64,7 @@ describe Gitlab::ChatCommands::Command, service: true do end it 'returns action' do - expect(subject[:text]).to include('Deployment from staging to production started.') + expect(subject[:text]).to include('Deployment started from staging to production') expect(subject[:response_type]).to be(:in_channel) end diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb index bd8099c92da..b3358a32161 100644 --- a/spec/lib/gitlab/chat_commands/deploy_spec.rb +++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb @@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do end context 'if no environment is defined' do - it 'returns nil' do - expect(subject).to be_nil + it 'does not execute an action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("No action found to be executed") end end @@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do let!(:deployment) { create(:deployment, environment: staging, deployable: build) } context 'without actions' do - it 'returns nil' do - expect(subject).to be_nil + it 'does not execute an action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("No action found to be executed") end end @@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do end it 'returns success result' do - expect(subject.type).to eq(:success) - expect(subject.message).to include('Deployment from staging to production started') + expect(subject[:response_type]).to be(:in_channel) + expect(subject[:text]).to start_with('Deployment started from staging to production') end context 'when duplicate action exists' do @@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do end it 'returns error' do - expect(subject.type).to eq(:error) - expect(subject.message).to include('Too many actions defined') + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq('Too many actions defined') end end @@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do name: 'teardown', environment: 'production') end - it 'returns success result' do - expect(subject.type).to eq(:success) - expect(subject.message).to include('Deployment from staging to production started') + it 'returns the success message' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject[:text]).to start_with('Deployment started from staging to production') end end end diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_create_spec.rb index 6c71e79ff6d..0f84b19a5a4 100644 --- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_create_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do it 'creates the issue' do expect { subject }.to change { project.issues.count }.by(1) - expect(subject.title).to eq('bird is the word') + expect(subject[:response_type]).to be(:in_channel) end end @@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do expect { subject }.to change { project.issues.count }.by(1) end end + + context 'issue cannot be created' do + let!(:issue) { create(:issue, project: project, title: 'bird is the word') } + let(:regex_match) { described_class.match("issue create #{'a' * 512}}") } + + it 'displays the errors' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match("- Title is too long") + end + end end describe '.match' do diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb index 24c06a967fa..04d10ad52a1 100644 --- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe Gitlab::ChatCommands::IssueSearch, service: true do describe '#execute' do - let!(:issue) { create(:issue, title: 'find me') } + let!(:issue) { create(:issue, project: project, title: 'find me') } let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } - let(:project) { issue.project } + let(:project) { create(:empty_project) } let(:user) { issue.author } let(:regex_match) { described_class.match("issue search find") } @@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do context 'when the user has no access' do it 'only returns the open issues' do - expect(subject).not_to include(confidential) + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match("not found") end end @@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do end it 'returns all results' do - expect(subject).to include(confidential, issue) + expect(subject).to have_key(:attachments) + expect(subject[:text]).to match("Here are the issues I found:") end end context 'without hits on the query' do it 'returns an empty collection' do - expect(subject).to be_empty + expect(subject[:text]).to match("not found") end end end diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb index 2eab73e49e5..89932c395c6 100644 --- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::ChatCommands::IssueShow, service: true do describe '#execute' do - let(:issue) { create(:issue) } - let(:project) { issue.project } + let(:issue) { create(:issue, project: project) } + let(:project) { create(:empty_project) } let(:user) { issue.author } let(:regex_match) { described_class.match("issue show #{issue.iid}") } @@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do end context 'the issue exists' do + let(:title) { subject[:attachments].first[:title] } + it 'returns the issue' do - expect(subject.iid).to be issue.iid + expect(subject[:response_type]).to be(:in_channel) + expect(title).to eq(issue.title) end context 'when its reference is given' do let(:regex_match) { described_class.match("issue show #{issue.to_reference}") } it 'shows the issue' do - expect(subject.iid).to be issue.iid + expect(subject[:response_type]).to be(:in_channel) + expect(title).to eq(issue.title) end end end @@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do context 'the issue does not exist' do let(:regex_match) { described_class.match("issue show 2343242") } - it "returns nil" do - expect(subject).to be_nil + it "returns not found" do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match("not found") end end end - describe 'self.match' do + describe '.match' do it 'matches the iid' do match = described_class.match("issue show 123") expect(match[:iid]).to eq("123") end + + it 'accepts a reference' do + match = described_class.match("issue show #{Issue.reference_prefix}123") + + expect(match[:iid]).to eq("123") + end end end diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb new file mode 100644 index 00000000000..ae41d75ab0c --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::Access do + describe '#access_denied' do + subject { described_class.new.access_denied } + + it { is_expected.to be_a(Hash) } + + it 'displays an error message' do + expect(subject[:text]).to match("is not allowed") + expect(subject[:response_type]).to be(:ephemeral) + end + end + + describe '#not_found' do + subject { described_class.new.not_found } + + it { is_expected.to be_a(Hash) } + + it 'tells the user the resource was not found' do + expect(subject[:text]).to match("not found!") + expect(subject[:response_type]).to be(:ephemeral) + end + end + + describe '#authorize' do + context 'with an authorization URL' do + subject { described_class.new('http://authorize.me').authorize } + + it { is_expected.to be_a(Hash) } + + it 'tells the user to authorize' do + expect(subject[:text]).to match("connect your GitLab account") + expect(subject[:response_type]).to be(:ephemeral) + end + end + + context 'without authorization url' do + subject { described_class.new.authorize } + + it { is_expected.to be_a(Hash) } + + it 'tells the user to authorize' do + expect(subject[:text]).to match("Couldn't identify you") + expect(subject[:response_type]).to be(:ephemeral) + end + end + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb new file mode 100644 index 00000000000..1c48c727e30 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::Deploy do + let(:build) { create(:ci_build) } + + describe '#present' do + subject { described_class.new(build).present('staging', 'prod') } + + it { is_expected.to have_key(:text) } + it { is_expected.to have_key(:response_type) } + it { is_expected.to have_key(:status) } + it { is_expected.not_to have_key(:attachments) } + + it 'messages the channel of the deploy' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject[:text]).to start_with("Deployment started from staging to prod") + end + end + + describe '#no_actions' do + subject { described_class.new(nil).no_actions } + + it { is_expected.to have_key(:text) } + it { is_expected.to have_key(:response_type) } + it { is_expected.to have_key(:status) } + it { is_expected.not_to have_key(:attachments) } + + it 'tells the user there is no action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("No action found to be executed") + end + end + + describe '#too_many_actions' do + subject { described_class.new(nil).too_many_actions } + + it { is_expected.to have_key(:text) } + it { is_expected.to have_key(:response_type) } + it { is_expected.to have_key(:status) } + it { is_expected.not_to have_key(:attachments) } + + it 'tells the user there is no action' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to eq("Too many actions defined") + end + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb new file mode 100644 index 00000000000..1852395fc97 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::ListIssues do + let(:project) { create(:empty_project) } + let(:message) { subject[:text] } + let(:issue) { project.issues.first } + + before { create_list(:issue, 2, project: project) } + + subject { described_class.new(project.issues).present } + + it do + is_expected.to have_key(:text) + is_expected.to have_key(:status) + is_expected.to have_key(:response_type) + is_expected.to have_key(:attachments) + end + + it 'shows a list of results' do + expect(subject[:response_type]).to be(:ephemeral) + + expect(message).to start_with("Here are the issues I found") + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb new file mode 100644 index 00000000000..13a318fe680 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::ShowIssue do + let(:project) { create(:empty_project) } + let(:issue) { create(:issue, project: project) } + let(:attachment) { subject[:attachments].first } + + subject { described_class.new(issue).present } + + it { is_expected.to be_a(Hash) } + + it 'shows the issue' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject).to have_key(:attachments) + expect(attachment[:title]).to eq(issue.title) + end + + context 'with upvotes' do + before do + create(:award_emoji, :upvote, awardable: issue) + end + + it 'shows the upvote count' do + expect(attachment[:text]).to start_with(":+1: 1") + end + end +end diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb deleted file mode 100644 index dc11a414717..00000000000 --- a/spec/lib/mattermost/client_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Client do - let(:user) { build(:user) } - - subject { described_class.new(user) } - - context 'JSON parse error' do - before do - Struct.new("Request", :body, :success?) - end - - it 'yields an error on malformed JSON' do - bad_json = Struct::Request.new("I'm not json", true) - expect { subject.send(:json_response, bad_json) }.to raise_error(Mattermost::ClientError) - end - - it 'shows a client error if the request was unsuccessful' do - bad_request = Struct::Request.new("true", false) - - expect { subject.send(:json_response, bad_request) }.to raise_error(Mattermost::ClientError) - end - end -end diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb deleted file mode 100644 index 5ccf1100898..00000000000 --- a/spec/lib/mattermost/command_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Command do - let(:params) { { 'token' => 'token', team_id: 'abc' } } - - before do - Mattermost::Session.base_uri('http://mattermost.example.com') - - allow_any_instance_of(Mattermost::Client).to receive(:with_session). - and_yield(Mattermost::Session.new(nil)) - end - - describe '#create' do - let(:params) do - { team_id: 'abc', - trigger: 'gitlab' - } - end - - subject { described_class.new(nil).create(params) } - - context 'for valid trigger word' do - before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). - with(body: { - team_id: 'abc', - trigger: 'gitlab' }.to_json). - to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: { token: 'token' }.to_json - ) - end - - it 'returns a token' do - is_expected.to eq('token') - end - end - - context 'for error message' do - before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). - to_return( - status: 500, - headers: { 'Content-Type' => 'application/json' }, - body: { - id: 'api.command.duplicate_trigger.app_error', - message: 'This trigger word is already in use. Please choose another word.', - detailed_error: '', - request_id: 'obc374man7bx5r3dbc1q5qhf3r', - status_code: 500 - }.to_json - ) - end - - it 'raises an error with message' do - expect { subject }.to raise_error(Mattermost::Error, 'This trigger word is already in use. Please choose another word.') - end - end - end -end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb deleted file mode 100644 index 74d12e37181..00000000000 --- a/spec/lib/mattermost/session_spec.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Session, type: :request do - let(:user) { create(:user) } - - let(:gitlab_url) { "http://gitlab.com" } - let(:mattermost_url) { "http://mattermost.com" } - - subject { described_class.new(user) } - - # Needed for doorkeeper to function - it { is_expected.to respond_to(:current_resource_owner) } - it { is_expected.to respond_to(:request) } - it { is_expected.to respond_to(:authorization) } - it { is_expected.to respond_to(:strategy) } - - before do - described_class.base_uri(mattermost_url) - end - - describe '#with session' do - let(:location) { 'http://location.tld' } - let!(:stub) do - WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login"). - to_return(headers: { 'location' => location }, status: 307) - end - - context 'without oauth uri' do - it 'makes a request to the oauth uri' do - expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) - end - end - - context 'with oauth_uri' do - let!(:doorkeeper) do - Doorkeeper::Application.create( - name: "GitLab Mattermost", - redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete", - scopes: "") - end - - context 'without token_uri' do - it 'can not create a session' do - expect do - subject.with_session - end.to raise_error(Mattermost::NoSessionError) - end - end - - context 'with token_uri' do - let(:state) { "state" } - let(:params) do - { response_type: "code", - client_id: doorkeeper.uid, - redirect_uri: "#{mattermost_url}/signup/gitlab/complete", - state: state } - end - let(:location) do - "#{gitlab_url}/oauth/authorize?#{URI.encode_www_form(params)}" - end - - before do - WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete"). - with(query: hash_including({ 'state' => state })). - to_return do |request| - post "/oauth/token", - client_id: doorkeeper.uid, - client_secret: doorkeeper.secret, - redirect_uri: params[:redirect_uri], - grant_type: 'authorization_code', - code: request.uri.query_values['code'] - - if response.status == 200 - { headers: { 'token' => 'thisworksnow' }, status: 202 } - end - end - - WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout"). - to_return(headers: { Authorization: 'token thisworksnow' }, status: 200) - end - - it 'can setup a session' do - subject.with_session do |session| - end - - expect(subject.token).not_to be_nil - end - - it 'returns the value of the block' do - result = subject.with_session do |session| - "value" - end - - expect(result).to eq("value") - end - end - end - - context 'with lease' do - before do - allow(subject).to receive(:lease_try_obtain).and_return('aldkfjsldfk') - end - - it 'tries to obtain a lease' do - expect(subject).to receive(:lease_try_obtain) - expect(Gitlab::ExclusiveLease).to receive(:cancel) - - # Cannot setup a session, but we should still cancel the lease - expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) - end - end - - context 'without lease' do - before do - allow(subject).to receive(:lease_try_obtain).and_return(nil) - end - - it 'returns a NoSessionError error' do - expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) - end - end - end -end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb deleted file mode 100644 index 2d14be6bcc2..00000000000 --- a/spec/lib/mattermost/team_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'spec_helper' - -describe Mattermost::Team do - before do - Mattermost::Session.base_uri('http://mattermost.example.com') - - allow_any_instance_of(Mattermost::Client).to receive(:with_session). - and_yield(Mattermost::Session.new(nil)) - end - - describe '#all' do - subject { described_class.new(nil).all } - - context 'for valid request' do - let(:response) do - [{ - "id" => "xiyro8huptfhdndadpz8r3wnbo", - "create_at" => 1482174222155, - "update_at" => 1482174222155, - "delete_at" => 0, - "display_name" => "chatops", - "name" => "chatops", - "email" => "admin@example.com", - "type" => "O", - "company_name" => "", - "allowed_domains" => "", - "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", - "allow_open_invite" => false }] - end - - before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). - to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: response.to_json - ) - end - - it 'returns a token' do - is_expected.to eq(response) - end - end - - context 'for error message' do - before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). - to_return( - status: 500, - headers: { 'Content-Type' => 'application/json' }, - body: { - id: 'api.team.list.app_error', - message: 'Cannot list teams.', - detailed_error: '', - request_id: 'obc374man7bx5r3dbc1q5qhf3r', - status_code: 500 - }.to_json - ) - end - - it 'raises an error with message' do - expect { subject }.to raise_error(Mattermost::Error, 'Cannot list teams.') - end - end - end -end From 746f47208dc52cd6ca68c0893de5513c250f524b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Wed, 11 Jan 2017 08:54:44 -0500 Subject: [PATCH 113/174] Revert removing of some files --- lib/mattermost/command.rb | 4 + lib/mattermost/error.rb | 3 + lib/mattermost/session.rb | 160 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 lib/mattermost/error.rb create mode 100644 lib/mattermost/session.rb diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb index 33e450d7f0a..2e4f7705f86 100644 --- a/lib/mattermost/command.rb +++ b/lib/mattermost/command.rb @@ -1,7 +1,11 @@ module Mattermost class Command < Client def create(params) +<<<<<<< HEAD response = session_post("/api/v3/teams/#{params[:team_id]}/commands/create", +======= + response = json_post("/api/v3/teams/#{params[:team_id]}/commands/create", +>>>>>>> Revert removing of some files body: params.to_json) response['token'] diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb new file mode 100644 index 00000000000..014df175be0 --- /dev/null +++ b/lib/mattermost/error.rb @@ -0,0 +1,3 @@ +module Mattermost + class Error < StandardError; end +end diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb new file mode 100644 index 00000000000..377cb7b1021 --- /dev/null +++ b/lib/mattermost/session.rb @@ -0,0 +1,160 @@ +module Mattermost + class NoSessionError < Mattermost::Error + def message + 'No session could be set up, is Mattermost configured with Single Sign On?' + end + end + + class ConnectionError < Mattermost::Error; end + + # This class' prime objective is to obtain a session token on a Mattermost + # instance with SSO configured where this GitLab instance is the provider. + # + # The process depends on OAuth, but skips a step in the authentication cycle. + # For example, usually a user would click the 'login in GitLab' button on + # Mattermost, which would yield a 302 status code and redirects you to GitLab + # to approve the use of your account on Mattermost. Which would trigger a + # callback so Mattermost knows this request is approved and gets the required + # data to create the user account etc. + # + # This class however skips the button click, and also the approval phase to + # speed up the process and keep it without manual action and get a session + # going. + class Session + include Doorkeeper::Helpers::Controller + include HTTParty + + LEASE_TIMEOUT = 60 + + base_uri Settings.mattermost.host + + attr_accessor :current_resource_owner, :token + + def initialize(current_user) + @current_resource_owner = current_user + end + + def with_session + with_lease do + raise Mattermost::NoSessionError unless create + + begin + yield self + rescue Errno::ECONNREFUSED + raise Mattermost::NoSessionError + ensure + destroy + end + end + end + + # Next methods are needed for Doorkeeper + def pre_auth + @pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new( + Doorkeeper.configuration, server.client_via_uid, params) + end + + def authorization + @authorization ||= strategy.request + end + + def strategy + @strategy ||= server.authorization_request(pre_auth.response_type) + end + + def request + @request ||= OpenStruct.new(parameters: params) + end + + def params + Rack::Utils.parse_query(oauth_uri.query).symbolize_keys + end + + def get(path, options = {}) + handle_exceptions do + self.class.get(path, options.merge(headers: @headers)) + end + end + + def post(path, options = {}) + handle_exceptions do + self.class.post(path, options.merge(headers: @headers)) + end + end + + private + + def create + return unless oauth_uri + return unless token_uri + + @token = request_token + @headers = { + Authorization: "Bearer #{@token}" + } + + @token + end + + def destroy + post('/api/v3/users/logout') + end + + def oauth_uri + return @oauth_uri if defined?(@oauth_uri) + + @oauth_uri = nil + + response = get("/api/v3/oauth/gitlab/login", follow_redirects: false) + return unless 300 <= response.code && response.code < 400 + + redirect_uri = response.headers['location'] + return unless redirect_uri + + @oauth_uri = URI.parse(redirect_uri) + end + + def token_uri + @token_uri ||= + if oauth_uri + authorization.authorize.redirect_uri if pre_auth.authorizable? + end + end + + def request_token + response = get(token_uri, follow_redirects: false) + + if 200 <= response.code && response.code < 400 + response.headers['token'] + end + end + + def with_lease + lease_uuid = lease_try_obtain + raise NoSessionError unless lease_uuid + + begin + yield + ensure + Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid) + end + end + + def lease_key + "mattermost:session" + end + + def lease_try_obtain + lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) + lease.try_obtain + end + + def handle_exceptions + yield + rescue HTTParty::Error => e + raise Mattermost::ConnectionError.new(e.message) + rescue Errno::ECONNREFUSED + raise Mattermost::ConnectionError.new(e.message) + end + end +end From 53846da2c7fe3879b4f26383d6367b0bb69e5dc8 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Wed, 11 Jan 2017 09:04:49 -0500 Subject: [PATCH 114/174] Add help command --- lib/gitlab/chat_commands/command.rb | 13 ++++------ lib/gitlab/chat_commands/help.rb | 28 +++++++++++++++++++++ lib/gitlab/chat_commands/presenters/help.rb | 20 +++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 lib/gitlab/chat_commands/help.rb create mode 100644 lib/gitlab/chat_commands/presenters/help.rb diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index ac7ee868402..4e5031a8a26 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -18,25 +18,22 @@ module Gitlab Gitlab::ChatCommands::Presenters::Access.new.access_denied end else - help(help_messages) + Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands) end end def match_command match = nil - service = available_commands.find do |klass| - match = klass.match(params[:text]) - end + service = + available_commands.find do |klass| + match = klass.match(params[:text]) + end [service, match] end private - def help_messages - available_commands.map(&:help_message) - end - def available_commands COMMANDS.select do |klass| klass.available?(project) diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb new file mode 100644 index 00000000000..e76733f5445 --- /dev/null +++ b/lib/gitlab/chat_commands/help.rb @@ -0,0 +1,28 @@ +module Gitlab + module ChatCommands + class Help < BaseCommand + # This class has to be used last, as it always matches. It has to match + # because other commands were not triggered and we want to show the help + # command + def self.match(_text) + true + end + + def self.help_message + 'help' + end + + def self.allowed?(_project, _user) + true + end + + def execute(commands) + Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger) + end + + def trigger + params[:command] + end + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb new file mode 100644 index 00000000000..133b707231f --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/help.rb @@ -0,0 +1,20 @@ +module Gitlab::ChatCommands::Presenters + class Help < Gitlab::ChatCommands::Presenters::Base + def present(trigger) + message = + if @resource.none? + "No commands available :thinking_face:" + else + header_with_list("Available commands", full_commands(trigger)) + end + + ephemeral_response(text: message) + end + + private + + def full_commands(trigger) + @resource.map { |command| "#{trigger} #{command.help_message}" } + end + end +end From 72843e021dba0022b75f3fd3988115691c19a4fb Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 12 Jan 2017 09:04:21 -0500 Subject: [PATCH 115/174] Fix tests --- .../project_services/chat_slash_commands_service.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index 608754f3035..5eb1bd86e9d 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -28,7 +28,7 @@ class ChatSlashCommandsService < Service end def trigger(params) - return access_presenter unless valid_token?(params[:token]) + return unless valid_token?(params[:token]) user = find_chat_user(params) @@ -36,16 +36,12 @@ class ChatSlashCommandsService < Service Gitlab::ChatCommands::Command.new(project, user, params).execute else url = authorize_chat_name_url(params) - access_presenter(url).authorize + Gitlab::ChatCommands::Presenters::Access.new(url).authorize end end private - def access_presenter(url = nil) - Gitlab::ChatCommands::Presenters::Access.new(url) - end - def find_chat_user(params) ChatNames::FindUserService.new(self, params).execute end From 4ce1a17c9767a80dfae0b47cee236d2a5d88918b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 19 Jan 2017 09:22:09 +0100 Subject: [PATCH 116/174] Incorporate feedback --- .../unreleased/zj-format-chat-messages.yml | 4 + lib/gitlab/chat_commands/issue_create.rb | 2 +- lib/gitlab/chat_commands/presenters/access.rb | 38 +++--- lib/gitlab/chat_commands/presenters/base.rb | 114 ++++++++-------- lib/gitlab/chat_commands/presenters/deploy.rb | 39 +++--- lib/gitlab/chat_commands/presenters/help.rb | 31 +++-- .../chat_commands/presenters/issuable.rb | 66 ++++++---- .../chat_commands/presenters/list_issues.rb | 61 +++++---- .../chat_commands/presenters/new_issue.rb | 42 ++++++ .../chat_commands/presenters/show_issue.rb | 72 ++++++---- .../gitlab/chat_commands/issue_search_spec.rb | 2 +- .../gitlab/chat_commands/issue_show_spec.rb | 4 +- .../chat_commands/presenters/deploy_spec.rb | 2 +- .../presenters/list_issues_spec.rb | 5 +- .../presenters/show_issue_spec.rb | 4 +- spec/lib/mattermost/client_spec.rb | 24 ++++ spec/lib/mattermost/command_spec.rb | 61 +++++++++ spec/lib/mattermost/session_spec.rb | 123 ++++++++++++++++++ spec/lib/mattermost/team_spec.rb | 66 ++++++++++ 19 files changed, 568 insertions(+), 192 deletions(-) create mode 100644 changelogs/unreleased/zj-format-chat-messages.yml create mode 100644 lib/gitlab/chat_commands/presenters/new_issue.rb create mode 100644 spec/lib/mattermost/client_spec.rb create mode 100644 spec/lib/mattermost/command_spec.rb create mode 100644 spec/lib/mattermost/session_spec.rb create mode 100644 spec/lib/mattermost/team_spec.rb diff --git a/changelogs/unreleased/zj-format-chat-messages.yml b/changelogs/unreleased/zj-format-chat-messages.yml new file mode 100644 index 00000000000..2494884f5c9 --- /dev/null +++ b/changelogs/unreleased/zj-format-chat-messages.yml @@ -0,0 +1,4 @@ +--- +title: Reformat messages ChatOps +merge_request: 8528 +author: diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index a06f13b0f72..3f3d7de8b2e 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -35,7 +35,7 @@ module Gitlab end def presenter(issue) - Gitlab::ChatCommands::Presenters::ShowIssue.new(issue) + Gitlab::ChatCommands::Presenters::NewIssue.new(issue) end end end diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb index 6d18d745608..b66ef48d6a8 100644 --- a/lib/gitlab/chat_commands/presenters/access.rb +++ b/lib/gitlab/chat_commands/presenters/access.rb @@ -1,22 +1,26 @@ -module Gitlab::ChatCommands::Presenters - class Access < Gitlab::ChatCommands::Presenters::Base - def access_denied - ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).") - end - - def not_found - ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") - end - - def authorize - message = - if @resource - ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})." - else - ":sweat_smile: Couldn't identify you, nor can I autorize you!" +module Gitlab + module ChatCommands + module Presenters + class Access < Presenters::Base + def access_denied + ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).") end - ephemeral_response(text: message) + def not_found + ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:") + end + + def authorize + message = + if @resource + ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})." + else + ":sweat_smile: Couldn't identify you, nor can I autorize you!" + end + + ephemeral_response(text: message) + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb index 0897025d85f..2700a5a2ad5 100644 --- a/lib/gitlab/chat_commands/presenters/base.rb +++ b/lib/gitlab/chat_commands/presenters/base.rb @@ -1,73 +1,77 @@ -module Gitlab::ChatCommands::Presenters - class Base - include Gitlab::Routing.url_helpers +module Gitlab + module ChatCommands + module Presenters + class Base + include Gitlab::Routing.url_helpers - def initialize(resource = nil) - @resource = resource - end + def initialize(resource = nil) + @resource = resource + end - def display_errors - message = header_with_list("The action was not successful, because:", @resource.errors.full_messages) + def display_errors + message = header_with_list("The action was not successful, because:", @resource.errors.full_messages) - ephemeral_response(text: message) - end + ephemeral_response(text: message) + end - private + private - def header_with_list(header, items) - message = [header] + def header_with_list(header, items) + message = [header] - items.each do |item| - message << "- #{item}" - end + items.each do |item| + message << "- #{item}" + end - message.join("\n") - end + message.join("\n") + end - def ephemeral_response(message) - response = { - response_type: :ephemeral, - status: 200 - }.merge(message) + def ephemeral_response(message) + response = { + response_type: :ephemeral, + status: 200 + }.merge(message) - format_response(response) - end + format_response(response) + end - def in_channel_response(message) - response = { - response_type: :in_channel, - status: 200 - }.merge(message) + def in_channel_response(message) + response = { + response_type: :in_channel, + status: 200 + }.merge(message) - format_response(response) - end + format_response(response) + end - def format_response(response) - response[:text] = format(response[:text]) if response.has_key?(:text) + def format_response(response) + response[:text] = format(response[:text]) if response.has_key?(:text) - if response.has_key?(:attachments) - response[:attachments].each do |attachment| - attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext] - attachment[:text] = format(attachment[:text]) if attachment[:text] + if response.has_key?(:attachments) + response[:attachments].each do |attachment| + attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext] + attachment[:text] = format(attachment[:text]) if attachment[:text] + end + end + + response + end + + # Convert Markdown to slacks format + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def resource_url + url_for( + [ + @resource.project.namespace.becomes(Namespace), + @resource.project, + @resource + ] + ) end end - - response - end - - # Convert Markdown to slacks format - def format(string) - Slack::Notifier::LinkFormatter.format(string) - end - - def resource_url - url_for( - [ - @resource.project.namespace.becomes(Namespace), - @resource.project, - @resource - ] - ) end end end diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb index 4f6333812ff..b1cfaac15af 100644 --- a/lib/gitlab/chat_commands/presenters/deploy.rb +++ b/lib/gitlab/chat_commands/presenters/deploy.rb @@ -1,24 +1,29 @@ -module Gitlab::ChatCommands::Presenters - class Deploy < Gitlab::ChatCommands::Presenters::Base - def present(from, to) - message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})." - in_channel_response(text: message) - end +module Gitlab + module ChatCommands + module Presenters + class Deploy < Presenters::Base + def present(from, to) + message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})." - def no_actions - ephemeral_response(text: "No action found to be executed") - end + in_channel_response(text: message) + end - def too_many_actions - ephemeral_response(text: "Too many actions defined") - end + def no_actions + ephemeral_response(text: "No action found to be executed") + end - private + def too_many_actions + ephemeral_response(text: "Too many actions defined") + end - def resource_url - polymorphic_url( - [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] - ) + private + + def resource_url + polymorphic_url( + [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] + ) + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb index 133b707231f..c7a67467b7e 100644 --- a/lib/gitlab/chat_commands/presenters/help.rb +++ b/lib/gitlab/chat_commands/presenters/help.rb @@ -1,20 +1,25 @@ -module Gitlab::ChatCommands::Presenters - class Help < Gitlab::ChatCommands::Presenters::Base - def present(trigger) - message = - if @resource.none? - "No commands available :thinking_face:" - else - header_with_list("Available commands", full_commands(trigger)) +module Gitlab + module ChatCommands + module Presenters + class Help < Presenters::Base + def present(trigger) + ephemeral_response(text: help_message(trigger)) end - ephemeral_response(text: message) - end + private - private + def help_message(trigger) + if @resource.none? + "No commands available :thinking_face:" + else + header_with_list("Available commands", full_commands(trigger)) + end + end - def full_commands(trigger) - @resource.map { |command| "#{trigger} #{command.help_message}" } + def full_commands(trigger) + @resource.map { |command| "#{trigger} #{command.help_message}" } + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb index 9623387f188..2cb6b1525fc 100644 --- a/lib/gitlab/chat_commands/presenters/issuable.rb +++ b/lib/gitlab/chat_commands/presenters/issuable.rb @@ -1,33 +1,45 @@ -module Gitlab::ChatCommands::Presenters - class Issuable < Gitlab::ChatCommands::Presenters::Base - private +module Gitlab + module ChatCommands + module Presenters + class Issuable < Presenters::Base + private - def project - @resource.project - end + def color(issuable) + issuable.open? ? '#38ae67' : '#d22852' + end - def author - @resource.author - end + def status_text(issuable) + issuable.open? ? 'Open' : 'Closed' + end - def fields - [ - { - title: "Assignee", - value: @resource.assignee ? @resource.assignee.name : "_None_", - short: true - }, - { - title: "Milestone", - value: @resource.milestone ? @resource.milestone.title : "_None_", - short: true - }, - { - title: "Labels", - value: @resource.labels.any? ? @resource.label_names : "_None_", - short: true - } - ] + def project + @resource.project + end + + def author + @resource.author + end + + def fields + [ + { + title: "Assignee", + value: @resource.assignee ? @resource.assignee.name : "_None_", + short: true + }, + { + title: "Milestone", + value: @resource.milestone ? @resource.milestone.title : "_None_", + short: true + }, + { + title: "Labels", + value: @resource.labels.any? ? @resource.label_names : "_None_", + short: true + } + ] + end + end end end end diff --git a/lib/gitlab/chat_commands/presenters/list_issues.rb b/lib/gitlab/chat_commands/presenters/list_issues.rb index 5a7b3fca5c2..2458b9356b7 100644 --- a/lib/gitlab/chat_commands/presenters/list_issues.rb +++ b/lib/gitlab/chat_commands/presenters/list_issues.rb @@ -1,32 +1,43 @@ -module Gitlab::ChatCommands::Presenters - class ListIssues < Gitlab::ChatCommands::Presenters::Base - def present - ephemeral_response(text: "Here are the issues I found:", attachments: attachments) - end +module Gitlab + module ChatCommands + module Presenters + class ListIssues < Presenters::Issuable + def present + text = if @resource.count >= 5 + "Here are the first 5 issues I found:" + else + "Here are the #{@resource.count} issues I found:" + end - private + ephemeral_response(text: text, attachments: attachments) + end - def attachments - @resource.map do |issue| - state = issue.open? ? "Open" : "Closed" + private - { - fallback: "Issue #{issue.to_reference}: #{issue.title}", - color: "#d22852", - text: "[#{issue.to_reference}](#{url_for([namespace, project, issue])}) · #{issue.title} (#{state})", - mrkdwn_in: [ - "text" - ] - } + def attachments + @resource.map do |issue| + url = "[#{issue.to_reference}](#{url_for([namespace, project, issue])})" + + { + color: color(issue), + fallback: "#{issue.to_reference} #{issue.title}", + text: "#{url} · #{issue.title} (#{status_text(issue)})", + + mrkdwn_in: [ + "text" + ] + } + end + end + + def project + @project ||= @resource.first.project + end + + def namespace + @namespace ||= project.namespace.becomes(Namespace) + end end end - - def project - @project ||= @resource.first.project - end - - def namespace - @namespace ||= project.namespace.becomes(Namespace) - end end end diff --git a/lib/gitlab/chat_commands/presenters/new_issue.rb b/lib/gitlab/chat_commands/presenters/new_issue.rb new file mode 100644 index 00000000000..c7c6febb56e --- /dev/null +++ b/lib/gitlab/chat_commands/presenters/new_issue.rb @@ -0,0 +1,42 @@ +module Gitlab + module ChatCommands + module Presenters + class NewIssue < Presenters::Issuable + def present + in_channel_response(show_issue) + end + + private + + def show_issue + { + attachments: [ + { + title: "#{@resource.title} · #{@resource.to_reference}", + title_link: resource_url, + author_name: author.name, + author_icon: author.avatar_url, + fallback: "New issue #{@resource.to_reference}: #{@resource.title}", + pretext: pretext, + color: color(@resource), + fields: fields, + mrkdwn_in: [ + :title, + :text + ] + } + ] + } + end + + def pretext + "I opened an issue on behalf on #{author_profile_link}: *#{@resource.to_reference}* from #{project.name_with_namespace}" + end + + def author_profile_link + "[#{author.to_reference}](#{url_for(author)})" + end + end + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/show_issue.rb b/lib/gitlab/chat_commands/presenters/show_issue.rb index 2a89c30b972..e5644a4ad7e 100644 --- a/lib/gitlab/chat_commands/presenters/show_issue.rb +++ b/lib/gitlab/chat_commands/presenters/show_issue.rb @@ -1,38 +1,54 @@ -module Gitlab::ChatCommands::Presenters - class ShowIssue < Gitlab::ChatCommands::Presenters::Issuable - def present - in_channel_response(show_issue) - end +module Gitlab + module ChatCommands + module Presenters + class ShowIssue < Presenters::Issuable + def present + in_channel_response(show_issue) + end - private + private - def show_issue - { - attachments: [ + def show_issue { - title: @resource.title, - title_link: resource_url, - author_name: author.name, - author_icon: author.avatar_url, - fallback: "#{@resource.to_reference}: #{@resource.title}", - text: text, - fields: fields, - mrkdwn_in: [ - :title, - :text + attachments: [ + { + title: "#{@resource.title} · #{@resource.to_reference}", + title_link: resource_url, + author_name: author.name, + author_icon: author.avatar_url, + fallback: "New issue #{@resource.to_reference}: #{@resource.title}", + pretext: pretext, + text: text, + color: color(@resource), + fields: fields, + mrkdwn_in: [ + :pretext, + :text + ] + } ] } - ] - } - end + end - def text - message = "" - message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero? - message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero? - message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero? + def text + message = "**#{status_text(@resource)}**" - message + if @resource.upvotes.zero? && @resource.downvotes.zero? && @resource.user_notes_count.zero? + return message + end + + message << " · " + message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero? + message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero? + message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero? + + message + end + + def pretext + "Issue *#{@resource.to_reference} from #{project.name_with_namespace}" + end + end end end end diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb index 04d10ad52a1..551ccb79a58 100644 --- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do it 'returns all results' do expect(subject).to have_key(:attachments) - expect(subject[:text]).to match("Here are the issues I found:") + expect(subject[:text]).to eq("Here are the 2 issues I found:") end end diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb index 89932c395c6..1f20d0a44ce 100644 --- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::ChatCommands::IssueShow, service: true do it 'returns the issue' do expect(subject[:response_type]).to be(:in_channel) - expect(title).to eq(issue.title) + expect(title).to start_with(issue.title) end context 'when its reference is given' do @@ -28,7 +28,7 @@ describe Gitlab::ChatCommands::IssueShow, service: true do it 'shows the issue' do expect(subject[:response_type]).to be(:in_channel) - expect(title).to eq(issue.title) + expect(title).to start_with(issue.title) end end end diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb index 1c48c727e30..dc2dd300072 100644 --- a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::ChatCommands::Presenters::Deploy do end describe '#too_many_actions' do - subject { described_class.new(nil).too_many_actions } + subject { described_class.new([]).too_many_actions } it { is_expected.to have_key(:text) } it { is_expected.to have_key(:response_type) } diff --git a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb index 1852395fc97..13a1f70fe78 100644 --- a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb @@ -3,13 +3,12 @@ require 'spec_helper' describe Gitlab::ChatCommands::Presenters::ListIssues do let(:project) { create(:empty_project) } let(:message) { subject[:text] } - let(:issue) { project.issues.first } before { create_list(:issue, 2, project: project) } subject { described_class.new(project.issues).present } - it do + it 'formats the message correct' do is_expected.to have_key(:text) is_expected.to have_key(:status) is_expected.to have_key(:response_type) @@ -19,6 +18,6 @@ describe Gitlab::ChatCommands::Presenters::ListIssues do it 'shows a list of results' do expect(subject[:response_type]).to be(:ephemeral) - expect(message).to start_with("Here are the issues I found") + expect(message).to start_with("Here are the 2 issues I found") end end diff --git a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb index 13a318fe680..ca4062e692a 100644 --- a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::ChatCommands::Presenters::ShowIssue do it 'shows the issue' do expect(subject[:response_type]).to be(:in_channel) expect(subject).to have_key(:attachments) - expect(attachment[:title]).to eq(issue.title) + expect(attachment[:title]).to start_with(issue.title) end context 'with upvotes' do @@ -21,7 +21,7 @@ describe Gitlab::ChatCommands::Presenters::ShowIssue do end it 'shows the upvote count' do - expect(attachment[:text]).to start_with(":+1: 1") + expect(attachment[:text]).to start_with("**Open** · :+1: 1") end end end diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb new file mode 100644 index 00000000000..dc11a414717 --- /dev/null +++ b/spec/lib/mattermost/client_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Mattermost::Client do + let(:user) { build(:user) } + + subject { described_class.new(user) } + + context 'JSON parse error' do + before do + Struct.new("Request", :body, :success?) + end + + it 'yields an error on malformed JSON' do + bad_json = Struct::Request.new("I'm not json", true) + expect { subject.send(:json_response, bad_json) }.to raise_error(Mattermost::ClientError) + end + + it 'shows a client error if the request was unsuccessful' do + bad_request = Struct::Request.new("true", false) + + expect { subject.send(:json_response, bad_request) }.to raise_error(Mattermost::ClientError) + end + end +end diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb new file mode 100644 index 00000000000..5ccf1100898 --- /dev/null +++ b/spec/lib/mattermost/command_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe Mattermost::Command do + let(:params) { { 'token' => 'token', team_id: 'abc' } } + + before do + Mattermost::Session.base_uri('http://mattermost.example.com') + + allow_any_instance_of(Mattermost::Client).to receive(:with_session). + and_yield(Mattermost::Session.new(nil)) + end + + describe '#create' do + let(:params) do + { team_id: 'abc', + trigger: 'gitlab' + } + end + + subject { described_class.new(nil).create(params) } + + context 'for valid trigger word' do + before do + stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). + with(body: { + team_id: 'abc', + trigger: 'gitlab' }.to_json). + to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: { token: 'token' }.to_json + ) + end + + it 'returns a token' do + is_expected.to eq('token') + end + end + + context 'for error message' do + before do + stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create'). + to_return( + status: 500, + headers: { 'Content-Type' => 'application/json' }, + body: { + id: 'api.command.duplicate_trigger.app_error', + message: 'This trigger word is already in use. Please choose another word.', + detailed_error: '', + request_id: 'obc374man7bx5r3dbc1q5qhf3r', + status_code: 500 + }.to_json + ) + end + + it 'raises an error with message' do + expect { subject }.to raise_error(Mattermost::Error, 'This trigger word is already in use. Please choose another word.') + end + end + end +end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb new file mode 100644 index 00000000000..74d12e37181 --- /dev/null +++ b/spec/lib/mattermost/session_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +describe Mattermost::Session, type: :request do + let(:user) { create(:user) } + + let(:gitlab_url) { "http://gitlab.com" } + let(:mattermost_url) { "http://mattermost.com" } + + subject { described_class.new(user) } + + # Needed for doorkeeper to function + it { is_expected.to respond_to(:current_resource_owner) } + it { is_expected.to respond_to(:request) } + it { is_expected.to respond_to(:authorization) } + it { is_expected.to respond_to(:strategy) } + + before do + described_class.base_uri(mattermost_url) + end + + describe '#with session' do + let(:location) { 'http://location.tld' } + let!(:stub) do + WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login"). + to_return(headers: { 'location' => location }, status: 307) + end + + context 'without oauth uri' do + it 'makes a request to the oauth uri' do + expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) + end + end + + context 'with oauth_uri' do + let!(:doorkeeper) do + Doorkeeper::Application.create( + name: "GitLab Mattermost", + redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete", + scopes: "") + end + + context 'without token_uri' do + it 'can not create a session' do + expect do + subject.with_session + end.to raise_error(Mattermost::NoSessionError) + end + end + + context 'with token_uri' do + let(:state) { "state" } + let(:params) do + { response_type: "code", + client_id: doorkeeper.uid, + redirect_uri: "#{mattermost_url}/signup/gitlab/complete", + state: state } + end + let(:location) do + "#{gitlab_url}/oauth/authorize?#{URI.encode_www_form(params)}" + end + + before do + WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete"). + with(query: hash_including({ 'state' => state })). + to_return do |request| + post "/oauth/token", + client_id: doorkeeper.uid, + client_secret: doorkeeper.secret, + redirect_uri: params[:redirect_uri], + grant_type: 'authorization_code', + code: request.uri.query_values['code'] + + if response.status == 200 + { headers: { 'token' => 'thisworksnow' }, status: 202 } + end + end + + WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout"). + to_return(headers: { Authorization: 'token thisworksnow' }, status: 200) + end + + it 'can setup a session' do + subject.with_session do |session| + end + + expect(subject.token).not_to be_nil + end + + it 'returns the value of the block' do + result = subject.with_session do |session| + "value" + end + + expect(result).to eq("value") + end + end + end + + context 'with lease' do + before do + allow(subject).to receive(:lease_try_obtain).and_return('aldkfjsldfk') + end + + it 'tries to obtain a lease' do + expect(subject).to receive(:lease_try_obtain) + expect(Gitlab::ExclusiveLease).to receive(:cancel) + + # Cannot setup a session, but we should still cancel the lease + expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) + end + end + + context 'without lease' do + before do + allow(subject).to receive(:lease_try_obtain).and_return(nil) + end + + it 'returns a NoSessionError error' do + expect { subject.with_session }.to raise_error(Mattermost::NoSessionError) + end + end + end +end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb new file mode 100644 index 00000000000..2d14be6bcc2 --- /dev/null +++ b/spec/lib/mattermost/team_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Mattermost::Team do + before do + Mattermost::Session.base_uri('http://mattermost.example.com') + + allow_any_instance_of(Mattermost::Client).to receive(:with_session). + and_yield(Mattermost::Session.new(nil)) + end + + describe '#all' do + subject { described_class.new(nil).all } + + context 'for valid request' do + let(:response) do + [{ + "id" => "xiyro8huptfhdndadpz8r3wnbo", + "create_at" => 1482174222155, + "update_at" => 1482174222155, + "delete_at" => 0, + "display_name" => "chatops", + "name" => "chatops", + "email" => "admin@example.com", + "type" => "O", + "company_name" => "", + "allowed_domains" => "", + "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", + "allow_open_invite" => false }] + end + + before do + stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). + to_return( + status: 200, + headers: { 'Content-Type' => 'application/json' }, + body: response.to_json + ) + end + + it 'returns a token' do + is_expected.to eq(response) + end + end + + context 'for error message' do + before do + stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all'). + to_return( + status: 500, + headers: { 'Content-Type' => 'application/json' }, + body: { + id: 'api.team.list.app_error', + message: 'Cannot list teams.', + detailed_error: '', + request_id: 'obc374man7bx5r3dbc1q5qhf3r', + status_code: 500 + }.to_json + ) + end + + it 'raises an error with message' do + expect { subject }.to raise_error(Mattermost::Error, 'Cannot list teams.') + end + end + end +end From 5ec214b0a5d9d3f0f0418a0e14ebf30b60a14a12 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 26 Jan 2017 15:30:34 +0100 Subject: [PATCH 117/174] Rename presenters for consitency --- lib/gitlab/chat_commands/command.rb | 2 +- .../{issue_create.rb => issue_new.rb} | 10 +++++----- lib/gitlab/chat_commands/issue_search.rb | 8 +++----- lib/gitlab/chat_commands/issue_show.rb | 2 +- lib/gitlab/chat_commands/presenters/deploy.rb | 8 -------- lib/gitlab/chat_commands/presenters/help.rb | 6 +++--- lib/gitlab/chat_commands/presenters/issuable.rb | 4 +--- .../presenters/{new_issue.rb => issue_new.rb} | 12 +++++++++--- .../{list_issues.rb => issue_search.rb} | 4 +++- .../presenters/{show_issue.rb => issue_show.rb} | 6 ++++-- lib/mattermost/command.rb | 4 ---- spec/lib/gitlab/chat_commands/command_spec.rb | 2 +- .../{issue_create_spec.rb => issue_new_spec.rb} | 2 +- .../chat_commands/presenters/issue_new_spec.rb | 17 +++++++++++++++++ ...list_issues_spec.rb => issue_search_spec.rb} | 2 +- .../{show_issue_spec.rb => issue_show_spec.rb} | 2 +- 16 files changed, 51 insertions(+), 40 deletions(-) rename lib/gitlab/chat_commands/{issue_create.rb => issue_new.rb} (88%) rename lib/gitlab/chat_commands/presenters/{new_issue.rb => issue_new.rb} (80%) rename lib/gitlab/chat_commands/presenters/{list_issues.rb => issue_search.rb} (92%) rename lib/gitlab/chat_commands/presenters/{show_issue.rb => issue_show.rb} (89%) rename spec/lib/gitlab/chat_commands/{issue_create_spec.rb => issue_new_spec.rb} (97%) create mode 100644 spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb rename spec/lib/gitlab/chat_commands/presenters/{list_issues_spec.rb => issue_search_spec.rb} (90%) rename spec/lib/gitlab/chat_commands/presenters/{show_issue_spec.rb => issue_show_spec.rb} (92%) diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 4e5031a8a26..e7baa20356c 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -3,7 +3,7 @@ module Gitlab class Command < BaseCommand COMMANDS = [ Gitlab::ChatCommands::IssueShow, - Gitlab::ChatCommands::IssueCreate, + Gitlab::ChatCommands::IssueNew, Gitlab::ChatCommands::IssueSearch, Gitlab::ChatCommands::Deploy, ].freeze diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_new.rb similarity index 88% rename from lib/gitlab/chat_commands/issue_create.rb rename to lib/gitlab/chat_commands/issue_new.rb index 3f3d7de8b2e..016054ecd46 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_new.rb @@ -1,6 +1,6 @@ module Gitlab module ChatCommands - class IssueCreate < IssueCommand + class IssueNew < IssueCommand def self.match(text) # we can not match \n with the dot by passing the m modifier as than # the title and description are not seperated @@ -21,10 +21,10 @@ module Gitlab issue = create_issue(title: title, description: description) - if issue.errors.any? - presenter(issue).display_errors - else + if issue.persisted? presenter(issue).present + else + presenter(issue).display_errors end end @@ -35,7 +35,7 @@ module Gitlab end def presenter(issue) - Gitlab::ChatCommands::Presenters::NewIssue.new(issue) + Gitlab::ChatCommands::Presenters::IssueNew.new(issue) end end end diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb index e2d3a0f466a..3491b53093e 100644 --- a/lib/gitlab/chat_commands/issue_search.rb +++ b/lib/gitlab/chat_commands/issue_search.rb @@ -12,12 +12,10 @@ module Gitlab def execute(match) issues = collection.search(match[:query]).limit(QUERY_LIMIT) - if issues.none? - Presenters::Access.new(issues).not_found - elsif issues.one? - Presenters::ShowIssue.new(issues.first).present + if issues.present? + Presenters::IssueSearch.new(issues).present else - Presenters::ListIssues.new(issues).present + Presenters::Access.new(issues).not_found end end end diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb index 9f3e1b9a64b..d6013f4d10c 100644 --- a/lib/gitlab/chat_commands/issue_show.rb +++ b/lib/gitlab/chat_commands/issue_show.rb @@ -13,7 +13,7 @@ module Gitlab issue = find_by_iid(match[:iid]) if issue - Gitlab::ChatCommands::Presenters::ShowIssue.new(issue).present + Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present else Gitlab::ChatCommands::Presenters::Access.new.not_found end diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb index b1cfaac15af..863d0bf99ca 100644 --- a/lib/gitlab/chat_commands/presenters/deploy.rb +++ b/lib/gitlab/chat_commands/presenters/deploy.rb @@ -15,14 +15,6 @@ module Gitlab def too_many_actions ephemeral_response(text: "Too many actions defined") end - - private - - def resource_url - polymorphic_url( - [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource] - ) - end end end end diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb index c7a67467b7e..39ad3249f5b 100644 --- a/lib/gitlab/chat_commands/presenters/help.rb +++ b/lib/gitlab/chat_commands/presenters/help.rb @@ -9,10 +9,10 @@ module Gitlab private def help_message(trigger) - if @resource.none? - "No commands available :thinking_face:" - else + if @resource.present? header_with_list("Available commands", full_commands(trigger)) + else + "No commands available :thinking_face:" end end diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb index 2cb6b1525fc..dfb1c8f6616 100644 --- a/lib/gitlab/chat_commands/presenters/issuable.rb +++ b/lib/gitlab/chat_commands/presenters/issuable.rb @@ -1,9 +1,7 @@ module Gitlab module ChatCommands module Presenters - class Issuable < Presenters::Base - private - + module Issuable def color(issuable) issuable.open? ? '#38ae67' : '#d22852' end diff --git a/lib/gitlab/chat_commands/presenters/new_issue.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb similarity index 80% rename from lib/gitlab/chat_commands/presenters/new_issue.rb rename to lib/gitlab/chat_commands/presenters/issue_new.rb index c7c6febb56e..d26dd22b2a0 100644 --- a/lib/gitlab/chat_commands/presenters/new_issue.rb +++ b/lib/gitlab/chat_commands/presenters/issue_new.rb @@ -1,14 +1,16 @@ module Gitlab module ChatCommands module Presenters - class NewIssue < Presenters::Issuable + class IssueNew < Presenters::Base + include Presenters::Issuable + def present - in_channel_response(show_issue) + in_channel_response(new_issue) end private - def show_issue + def new_issue { attachments: [ { @@ -33,6 +35,10 @@ module Gitlab "I opened an issue on behalf on #{author_profile_link}: *#{@resource.to_reference}* from #{project.name_with_namespace}" end + def project_link + "[#{project.name_with_namespace}](#{url_for(project)})" + end + def author_profile_link "[#{author.to_reference}](#{url_for(author)})" end diff --git a/lib/gitlab/chat_commands/presenters/list_issues.rb b/lib/gitlab/chat_commands/presenters/issue_search.rb similarity index 92% rename from lib/gitlab/chat_commands/presenters/list_issues.rb rename to lib/gitlab/chat_commands/presenters/issue_search.rb index 2458b9356b7..d58a6d6114a 100644 --- a/lib/gitlab/chat_commands/presenters/list_issues.rb +++ b/lib/gitlab/chat_commands/presenters/issue_search.rb @@ -1,7 +1,9 @@ module Gitlab module ChatCommands module Presenters - class ListIssues < Presenters::Issuable + class IssueSearch < Presenters::Base + include Presenters::Issuable + def present text = if @resource.count >= 5 "Here are the first 5 issues I found:" diff --git a/lib/gitlab/chat_commands/presenters/show_issue.rb b/lib/gitlab/chat_commands/presenters/issue_show.rb similarity index 89% rename from lib/gitlab/chat_commands/presenters/show_issue.rb rename to lib/gitlab/chat_commands/presenters/issue_show.rb index e5644a4ad7e..2fc671f13a6 100644 --- a/lib/gitlab/chat_commands/presenters/show_issue.rb +++ b/lib/gitlab/chat_commands/presenters/issue_show.rb @@ -1,7 +1,9 @@ module Gitlab module ChatCommands module Presenters - class ShowIssue < Presenters::Issuable + class IssueShow < Presenters::Base + include Presenters::Issuable + def present in_channel_response(show_issue) end @@ -16,7 +18,7 @@ module Gitlab title_link: resource_url, author_name: author.name, author_icon: author.avatar_url, - fallback: "New issue #{@resource.to_reference}: #{@resource.title}", + fallback: "Issue #{@resource.to_reference}: #{@resource.title}", pretext: pretext, text: text, color: color(@resource), diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb index 2e4f7705f86..33e450d7f0a 100644 --- a/lib/mattermost/command.rb +++ b/lib/mattermost/command.rb @@ -1,11 +1,7 @@ module Mattermost class Command < Client def create(params) -<<<<<<< HEAD response = session_post("/api/v3/teams/#{params[:team_id]}/commands/create", -======= - response = json_post("/api/v3/teams/#{params[:team_id]}/commands/create", ->>>>>>> Revert removing of some files body: params.to_json) response['token'] diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index d8b2303555c..0acf40de1d3 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -94,7 +94,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'IssueCreate is triggered' do let(:params) { { text: 'issue create my title' } } - it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) } + it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) } end context 'IssueSearch is triggered' do diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_new_spec.rb similarity index 97% rename from spec/lib/gitlab/chat_commands/issue_create_spec.rb rename to spec/lib/gitlab/chat_commands/issue_new_spec.rb index 0f84b19a5a4..84c22328064 100644 --- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_new_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::IssueCreate, service: true do +describe Gitlab::ChatCommands::IssueNew, service: true do describe '#execute' do let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb new file mode 100644 index 00000000000..17fcdbc2452 --- /dev/null +++ b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::Presenters::IssueNew do + let(:project) { create(:empty_project) } + let(:issue) { create(:issue, project: project) } + let(:attachment) { subject[:attachments].first } + + subject { described_class.new(issue).present } + + it { is_expected.to be_a(Hash) } + + it 'shows the issue' do + expect(subject[:response_type]).to be(:in_channel) + expect(subject).to have_key(:attachments) + expect(attachment[:title]).to start_with(issue.title) + end +end diff --git a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb similarity index 90% rename from spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb rename to spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb index 13a1f70fe78..ec6d3e34a96 100644 --- a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::ListIssues do +describe Gitlab::ChatCommands::Presenters::IssueSearch do let(:project) { create(:empty_project) } let(:message) { subject[:text] } diff --git a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb similarity index 92% rename from spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb rename to spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb index ca4062e692a..89d154e26e4 100644 --- a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::ChatCommands::Presenters::ShowIssue do +describe Gitlab::ChatCommands::Presenters::IssueShow do let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:attachment) { subject[:attachments].first } From 500e1a56e0a2225a61ec4bea40a474e7e3e3d1cc Mon Sep 17 00:00:00 2001 From: Nur Rony <pro.nmrony@gmail.com> Date: Mon, 30 Jan 2017 16:58:30 +0600 Subject: [PATCH 118/174] unifies mr diff file button style --- app/helpers/commits_helper.rb | 2 +- app/views/projects/diffs/_file.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index e9461b9f859..6dcb624c4da 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -198,7 +198,7 @@ module CommitsHelper link_to( namespace_project_blob_path(project.namespace, project, tree_join(commit_sha, diff_new_path)), - class: 'btn view-file js-view-file btn-file-option' + class: 'btn view-file js-view-file' ) do raw('View file @') + content_tag(:span, commit_sha[0..6], class: 'commit-short-id') diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index c37a33bbcd5..fc478ccc995 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -5,7 +5,7 @@ - unless diff_file.submodule? .file-actions.hidden-xs - if blob_text_viewable?(blob) - = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do + = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ - if editable_diff?(diff_file) From d4dd1fcf93d10b65b9e1b5ca392daacaf7c5138c Mon Sep 17 00:00:00 2001 From: Nur Rony <pro.nmrony@gmail.com> Date: Mon, 30 Jan 2017 17:19:32 +0600 Subject: [PATCH 119/174] adds changelog --- changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml diff --git a/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml new file mode 100644 index 00000000000..293aab67d39 --- /dev/null +++ b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml @@ -0,0 +1,4 @@ +--- +title: Unify MR diff file button style +merge_request: 8874 +author: From 29414ab0438583c7401e94a74a613497874b5e4e Mon Sep 17 00:00:00 2001 From: Drew Blessing <drew@gitlab.com> Date: Tue, 24 Jan 2017 11:12:49 -0600 Subject: [PATCH 120/174] Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms We accept half a dozen different authentication mechanisms for Git over HTTP. Fairly high in the list we were checking user password, which would also query LDAP. In the case of LFS, OAuth tokens or personal access tokens, we were unnecessarily hitting LDAP when the authentication will not succeed. This was causing some LDAP/AD systems to lock the account. Now, user password authentication is the last mechanism tried since it's the most expensive. --- .../24462-reduce_ldap_queries_for_lfs.yml | 4 + lib/gitlab/auth.rb | 11 +- spec/lib/gitlab/auth_spec.rb | 104 +++++++++++++----- 3 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml diff --git a/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml new file mode 100644 index 00000000000..05fbd8f0bf2 --- /dev/null +++ b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml @@ -0,0 +1,4 @@ +--- +title: Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms +merge_request: 8752 +author: diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 8dda65c71ef..f638905a1e0 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -10,13 +10,16 @@ module Gitlab def find_for_git_client(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? + # `user_with_password_for_git` should be the last check + # because it's the most expensive, especially when LDAP + # is enabled. result = service_request_check(login, password, project) || build_access_token_check(login, password) || - user_with_password_for_git(login, password) || - oauth_access_token_check(login, password) || lfs_token_check(login, password) || + oauth_access_token_check(login, password) || personal_access_token_check(login, password) || + user_with_password_for_git(login, password) || Gitlab::Auth::Result.new rate_limit!(ip, success: result.success?, login: login) @@ -143,7 +146,9 @@ module Gitlab read_authentication_abilities end - Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.token, password) + if Devise.secure_compare(token_handler.token, password) + Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities) + end end def build_access_token_check(login, password) diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index f251c0dd25a..b234de4c772 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -58,58 +58,102 @@ describe Gitlab::Auth, lib: true do expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) end - it 'recognizes user lfs tokens' do - user = create(:user) - token = Gitlab::LfsToken.new(user).token - - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities)) - end - - it 'recognizes deploy key lfs tokens' do - key = create(:deploy_key) - token = Gitlab::LfsToken.new(key).token - - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") - expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities)) - end - - context "while using OAuth tokens as passwords" do - it 'succeeds for OAuth tokens with the `api` scope' do + context 'while using LFS authenticate' do + it 'recognizes user lfs tokens' do user = create(:user) - application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) - token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api") + token = Gitlab::LfsToken.new(user).token + expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username) + expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities)) + end + + it 'recognizes deploy key lfs tokens' do + key = create(:deploy_key) + token = Gitlab::LfsToken.new(key).token + + expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities)) + end + + it 'does not try password auth before oauth' do + user = create(:user) + token = Gitlab::LfsToken.new(user).token + + expect(gl_auth).not_to receive(:find_with_user_password) + + gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip') + end + end + + context 'while using OAuth tokens as passwords' do + let(:user) { create(:user) } + let(:token_w_api_scope) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') } + let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) } + + it 'succeeds for OAuth tokens with the `api` scope' do expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2') - expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)) + expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)) end it 'fails for OAuth tokens with other scopes' do - user = create(:user) - application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) - token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "read_user") + token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user') expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2') expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) end + + it 'does not try password auth before oauth' do + expect(gl_auth).not_to receive(:find_with_user_password) + + gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip') + end end - context "while using personal access tokens as passwords" do - it 'succeeds for personal access tokens with the `api` scope' do - user = create(:user) - personal_access_token = create(:personal_access_token, user: user, scopes: ['api']) + context 'while using personal access tokens as passwords' do + let(:user) { create(:user) } + let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) } + it 'succeeds for personal access tokens with the `api` scope' do expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email) - expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities)) + expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities)) end it 'fails for personal access tokens with other scopes' do - user = create(:user) personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email) expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) end + + it 'does not try password auth before personal access tokens' do + expect(gl_auth).not_to receive(:find_with_user_password) + + gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip') + end + end + + context 'while using regular user and password' do + it 'falls through lfs authentication' do + user = create( + :user, + username: 'normal_user', + password: 'my-secret', + ) + + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) + .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) + end + + it 'falls through oauth authentication when the username is oauth2' do + user = create( + :user, + username: 'oauth2', + password: 'my-secret', + ) + + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) + .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) + end end it 'returns double nil for invalid credentials' do From d7f298c177555a09ac06acc9ad037611f664cc9e Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Mon, 30 Jan 2017 12:12:57 +0100 Subject: [PATCH 121/174] Incorporate feedback --- lib/gitlab/chat_commands/command.rb | 2 +- lib/gitlab/chat_commands/help.rb | 4 ++-- lib/gitlab/chat_commands/presenters/access.rb | 14 ++++++++++++++ lib/gitlab/chat_commands/presenters/help.rb | 12 +++++++----- lib/gitlab/chat_commands/presenters/issue_new.rb | 4 +++- .../chat_commands/presenters/issue_search.rb | 4 +++- lib/gitlab/chat_commands/presenters/issue_show.rb | 11 ++++++++--- spec/lib/gitlab/chat_commands/command_spec.rb | 6 +----- .../chat_commands/presenters/issue_show_spec.rb | 10 ++++++++++ 9 files changed, 49 insertions(+), 18 deletions(-) diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index e7baa20356c..f34ed0f4cf2 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -18,7 +18,7 @@ module Gitlab Gitlab::ChatCommands::Presenters::Access.new.access_denied end else - Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands) + Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text]) end end diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb index e76733f5445..6c0e4d304a4 100644 --- a/lib/gitlab/chat_commands/help.rb +++ b/lib/gitlab/chat_commands/help.rb @@ -16,8 +16,8 @@ module Gitlab true end - def execute(commands) - Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger) + def execute(commands, text) + Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text) end def trigger diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb index b66ef48d6a8..92f4fa17f78 100644 --- a/lib/gitlab/chat_commands/presenters/access.rb +++ b/lib/gitlab/chat_commands/presenters/access.rb @@ -20,6 +20,20 @@ module Gitlab ephemeral_response(text: message) end + + def unknown_command(commands) + ephemeral_response(text: help_message(trigger)) + end + + private + + def help_message(trigger) + header_with_list("Command not found, these are the commands you can use", full_commands(trigger)) + end + + def full_commands(trigger) + @resource.map { |command| "#{trigger} #{command.help_message}" } + end end end end diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb index 39ad3249f5b..cd47b7f4c6a 100644 --- a/lib/gitlab/chat_commands/presenters/help.rb +++ b/lib/gitlab/chat_commands/presenters/help.rb @@ -2,17 +2,19 @@ module Gitlab module ChatCommands module Presenters class Help < Presenters::Base - def present(trigger) - ephemeral_response(text: help_message(trigger)) + def present(trigger, text) + ephemeral_response(text: help_message(trigger, text)) end private - def help_message(trigger) - if @resource.present? + def help_message(trigger, text) + return "No commands available :thinking_face:" unless @resource.present? + + if text.start_with?('help') header_with_list("Available commands", full_commands(trigger)) else - "No commands available :thinking_face:" + header_with_list("Unknown command, these commands are available", full_commands(trigger)) end end diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb index d26dd22b2a0..6e88e0574a3 100644 --- a/lib/gitlab/chat_commands/presenters/issue_new.rb +++ b/lib/gitlab/chat_commands/presenters/issue_new.rb @@ -24,7 +24,9 @@ module Gitlab fields: fields, mrkdwn_in: [ :title, - :text + :pretext, + :text, + :fields ] } ] diff --git a/lib/gitlab/chat_commands/presenters/issue_search.rb b/lib/gitlab/chat_commands/presenters/issue_search.rb index d58a6d6114a..3478359b91d 100644 --- a/lib/gitlab/chat_commands/presenters/issue_search.rb +++ b/lib/gitlab/chat_commands/presenters/issue_search.rb @@ -7,6 +7,8 @@ module Gitlab def present text = if @resource.count >= 5 "Here are the first 5 issues I found:" + elsif @resource.one? + "Here is the only issue I found:" else "Here are the #{@resource.count} issues I found:" end @@ -26,7 +28,7 @@ module Gitlab text: "#{url} · #{issue.title} (#{status_text(issue)})", mrkdwn_in: [ - "text" + :text ] } end diff --git a/lib/gitlab/chat_commands/presenters/issue_show.rb b/lib/gitlab/chat_commands/presenters/issue_show.rb index 2fc671f13a6..fe5847ccd15 100644 --- a/lib/gitlab/chat_commands/presenters/issue_show.rb +++ b/lib/gitlab/chat_commands/presenters/issue_show.rb @@ -5,7 +5,11 @@ module Gitlab include Presenters::Issuable def present - in_channel_response(show_issue) + if @resource.confidential? + ephemeral_response(show_issue) + else + in_channel_response(show_issue) + end end private @@ -25,7 +29,8 @@ module Gitlab fields: fields, mrkdwn_in: [ :pretext, - :text + :text, + :fields ] } ] @@ -48,7 +53,7 @@ module Gitlab end def pretext - "Issue *#{@resource.to_reference} from #{project.name_with_namespace}" + "Issue *#{@resource.to_reference}* from #{project.name_with_namespace}" end end end diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index 0acf40de1d3..b6e924d67be 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -5,7 +5,6 @@ describe Gitlab::ChatCommands::Command, service: true do let(:user) { create(:user) } describe '#execute' do -<<<<<<< HEAD subject do described_class.new(project, user, params).execute end @@ -19,16 +18,13 @@ describe Gitlab::ChatCommands::Command, service: true do expect(subject[:text]).to start_with('404 not found') end end -======= - subject { described_class.new(project, user, params).execute } ->>>>>>> Chat Commands have presenters context 'when an unknown command is triggered' do let(:params) { { command: '/gitlab', text: "unknown command 123" } } it 'displays the help message' do expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('Available commands') + expect(subject[:text]).to start_with('Unknown command') expect(subject[:text]).to match('/gitlab issue show') end end diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb index 89d154e26e4..5b678d31fce 100644 --- a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb +++ b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb @@ -21,7 +21,17 @@ describe Gitlab::ChatCommands::Presenters::IssueShow do end it 'shows the upvote count' do + expect(subject[:response_type]).to be(:in_channel) expect(attachment[:text]).to start_with("**Open** · :+1: 1") end end + + context 'confidential issue' do + let(:issue) { create(:issue, project: project) } + + it 'shows an ephemeral response' do + expect(subject[:response_type]).to be(:in_channel) + expect(attachment[:text]).to start_with("**Open**") + end + end end From 538d1bffec52ecdaae44eaf9fdbcb6102c1cbefd Mon Sep 17 00:00:00 2001 From: Adam Pahlevi <adam.pahlevi@gmail.com> Date: Sat, 28 Jan 2017 09:50:25 +0700 Subject: [PATCH 122/174] resolve deprecation warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit don’t pass AR object, use the ID to avoid depr warning pass in the id instead of AR object to specs for `ProjectDestroyWorker` --- app/controllers/groups_controller.rb | 2 +- spec/models/members/project_member_spec.rb | 2 +- spec/workers/project_destroy_worker_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index f81237db991..264b14713fb 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController if Groups::UpdateService.new(@group, current_user, group_params).execute redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated." else - @group.reset_path! + @group.restore_path! render action: "edit" end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 90d14c2c0b9..e4be0aba7a6 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -117,7 +117,7 @@ describe ProjectMember, models: true do users = create_list(:user, 2) described_class.add_users_to_projects( - [projects.first.id, projects.second], + [projects.first.id, projects.second.id], [users.first.id, users.second], described_class::MASTER) diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb index 1b910d9b91e..1f4c39eb64a 100644 --- a/spec/workers/project_destroy_worker_spec.rb +++ b/spec/workers/project_destroy_worker_spec.rb @@ -8,14 +8,14 @@ describe ProjectDestroyWorker do describe "#perform" do it "deletes the project" do - subject.perform(project.id, project.owner, {}) + subject.perform(project.id, project.owner.id, {}) expect(Project.all).not_to include(project) expect(Dir.exist?(path)).to be_falsey end it "deletes the project but skips repo deletion" do - subject.perform(project.id, project.owner, { "skip_repo" => true }) + subject.perform(project.id, project.owner.id, { "skip_repo" => true }) expect(Project.all).not_to include(project) expect(Dir.exist?(path)).to be_truthy From 124c99032292c11a2f69197233683db9bee4d463 Mon Sep 17 00:00:00 2001 From: Adam Pahlevi <adam.pahlevi@gmail.com> Date: Sat, 28 Jan 2017 10:18:39 +0700 Subject: [PATCH 123/174] add complete changelog --- changelogs/unreleased/fix-depr-warn.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-depr-warn.yml diff --git a/changelogs/unreleased/fix-depr-warn.yml b/changelogs/unreleased/fix-depr-warn.yml new file mode 100644 index 00000000000..61817027720 --- /dev/null +++ b/changelogs/unreleased/fix-depr-warn.yml @@ -0,0 +1,4 @@ +--- +title: resolve deprecation warnings +merge_request: 8855 +author: Adam Pahlevi From 8615138706337e6aca75d635e8fe3a867f9e69bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me> Date: Tue, 31 Jan 2017 11:05:50 +0100 Subject: [PATCH 124/174] Move Dashboard shortcuts specs from Spinah to RSpec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable <remy@rymai.me> --- features/dashboard/shortcuts.feature | 21 ---------------- features/steps/dashboard/shortcuts.rb | 7 ------ spec/features/dashboard/shortcuts_spec.rb | 29 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 features/dashboard/shortcuts.feature delete mode 100644 features/steps/dashboard/shortcuts.rb create mode 100644 spec/features/dashboard/shortcuts_spec.rb diff --git a/features/dashboard/shortcuts.feature b/features/dashboard/shortcuts.feature deleted file mode 100644 index 41d79aa6ec8..00000000000 --- a/features/dashboard/shortcuts.feature +++ /dev/null @@ -1,21 +0,0 @@ -@dashboard -Feature: Dashboard Shortcuts - Background: - Given I sign in as a user - And I visit dashboard page - - @javascript - Scenario: Navigate to projects tab - Given I press "g" and "p" - Then the active main tab should be Projects - - @javascript - Scenario: Navigate to issue tab - Given I press "g" and "i" - Then the active main tab should be Issues - - @javascript - Scenario: Navigate to merge requests tab - Given I press "g" and "m" - Then the active main tab should be Merge Requests - diff --git a/features/steps/dashboard/shortcuts.rb b/features/steps/dashboard/shortcuts.rb deleted file mode 100644 index 118d27888df..00000000000 --- a/features/steps/dashboard/shortcuts.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - include SharedSidebarActiveTab - include SharedShortcuts -end diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb new file mode 100644 index 00000000000..d9be4e5dbdd --- /dev/null +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +feature 'Dashboard shortcuts', feature: true, js: true do + before do + login_as :user + visit dashboard_projects_path + end + + scenario 'Navigate to tabs' do + find('body').native.send_key('g') + find('body').native.send_key('p') + + ensure_active_main_tab('Projects') + + find('body').native.send_key('g') + find('body').native.send_key('i') + + ensure_active_main_tab('Issues') + + find('body').native.send_key('g') + find('body').native.send_key('m') + + ensure_active_main_tab('Merge Requests') + end + + def ensure_active_main_tab(content) + expect(find('.nav-sidebar li.active')).to have_content(content) + end +end From 7d0cdf62673303e9164c2a98ca9387d8f26c2233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me> Date: Tue, 31 Jan 2017 11:28:56 +0100 Subject: [PATCH 125/174] Simplify the SSH protocol introduction and link to a DO tutorial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable <remy@rymai.me> --- doc/ssh/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index 9803937fcf9..9e391d647a8 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -4,10 +4,12 @@ Git is a distributed version control system, which means you can work locally but you can also share or "push" your changes to other servers. Before you can push your changes to a GitLab server you need a secure communication channel for sharing information. -GitLab uses Public-key or asymmetric cryptography -which encrypts a communication channel by locking it with your "private key" -and allows trusted parties to unlock it with your "public key". -If someone does not have your public key they cannot access the unencrypted message. + +The SSH protocol provides this security and allows you to authenticate to the +GitLab remote server without supplying your username or password each time. + +For a more detailed explanation of how the SSH protocol works, we advise you to +read [this nice tutorial by DigitalOcean](https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process). ## Locating an existing SSH key pair From ef20bb2edd932a8e144aded11c83046e77ea79d9 Mon Sep 17 00:00:00 2001 From: dimitrieh <dimitriehoekstra@gmail.com> Date: Tue, 31 Jan 2017 12:09:37 +0100 Subject: [PATCH 126/174] Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles --- .../javascripts/environments/components/environment.js.es6 | 2 +- app/views/projects/environments/show.html.haml | 2 +- .../unreleased/27494-environment-list-column-headers.yml | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/27494-environment-list-column-headers.yml diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index fea642467fa..971be04e2d2 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -182,7 +182,7 @@ <th class="environments-deploy">Last deployment</th> <th class="environments-build">Build</th> <th class="environments-commit">Commit</th> - <th class="environments-date">Created</th> + <th class="environments-date">Updated</th> <th class="hidden-xs environments-actions"></th> </tr> </thead> diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 6e0d9456900..51c31a09378 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -33,7 +33,7 @@ %th ID %th Commit %th Build - %th + %th Created %th.hidden-xs = render @deployments diff --git a/changelogs/unreleased/27494-environment-list-column-headers.yml b/changelogs/unreleased/27494-environment-list-column-headers.yml new file mode 100644 index 00000000000..798c01f3238 --- /dev/null +++ b/changelogs/unreleased/27494-environment-list-column-headers.yml @@ -0,0 +1,4 @@ +--- +title: Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles +merge_request: +author: From 164eb3aa37cbcff2c5fbf582c3acdbaa3e6fee77 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Tue, 31 Jan 2017 12:00:43 +0100 Subject: [PATCH 127/174] Improve styling of the new issue message --- lib/gitlab/chat_commands/presenters/issue_new.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb index 6e88e0574a3..a1a3add56c9 100644 --- a/lib/gitlab/chat_commands/presenters/issue_new.rb +++ b/lib/gitlab/chat_commands/presenters/issue_new.rb @@ -34,11 +34,11 @@ module Gitlab end def pretext - "I opened an issue on behalf on #{author_profile_link}: *#{@resource.to_reference}* from #{project.name_with_namespace}" + "I created an issue on #{author_profile_link}'s behalf: **#{@resource.to_reference}** in #{project_link}" end def project_link - "[#{project.name_with_namespace}](#{url_for(project)})" + "[#{project.name_with_namespace}](#{projects_url(project)})" end def author_profile_link From 89a2438ab44862b34ba1030761c27b37059389ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me> Date: Tue, 31 Jan 2017 17:37:31 +0100 Subject: [PATCH 128/174] Fix wrong call to ProjectCacheWorker.perform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's either ProjectCacheWorker#perform or ProjectCacheWorker.perform_async! Signed-off-by: Rémy Coutable <remy@rymai.me> --- .../27516-fix-wrong-call-to-project_cache_worker-method.yml | 4 ++++ lib/tasks/gitlab/import.rake | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml diff --git a/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml b/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml new file mode 100644 index 00000000000..bc990c66866 --- /dev/null +++ b/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml @@ -0,0 +1,4 @@ +--- +title: Fix wrong call to ProjectCacheWorker.perform +merge_request: 8910 +author: diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index a2eca74a3c8..036a9307ab5 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -63,7 +63,7 @@ namespace :gitlab do if project.persisted? puts " * Created #{project.name} (#{repo_path})".color(:green) - ProjectCacheWorker.perform(project.id) + ProjectCacheWorker.perform_async(project.id) else puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red) puts " Errors: #{project.errors.messages}".color(:red) From 7bf6df8463c4f8871682f385e9368d169b4ffecf Mon Sep 17 00:00:00 2001 From: Brian Hall <brian@hack.design> Date: Mon, 30 Jan 2017 22:12:31 -0600 Subject: [PATCH 129/174] Change the reply shortcut to focus the field even without a selection. --- app/assets/javascripts/lib/utils/common_utils.js.es6 | 1 + app/assets/javascripts/shortcuts_issuable.js | 9 ++++++--- .../unreleased/empty-selection-reply-shortcut.yml | 4 ++++ spec/javascripts/shortcuts_issuable_spec.js | 12 ++++++++++-- 4 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/empty-selection-reply-shortcut.yml diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 51993bb3420..e3bff2559fd 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -162,6 +162,7 @@ w.gl.utils.getSelectedFragment = () => { const selection = window.getSelection(); + if (selection.rangeCount === 0) return null; const documentFragment = selection.getRangeAt(0).cloneContents(); if (documentFragment.textContent.length === 0) return null; diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index 4ef516af8c8..4dcc5ebe28f 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -39,17 +39,20 @@ } ShortcutsIssuable.prototype.replyWithSelectedText = function() { - var quote, replyField, documentFragment, selected, separator; + var quote, documentFragment, selected, separator; + var replyField = $('.js-main-target-form #note_note'); documentFragment = window.gl.utils.getSelectedFragment(); - if (!documentFragment) return; + if (!documentFragment) { + replyField.focus(); + return; + } // If the documentFragment contains more than just Markdown, don't copy as GFM. if (documentFragment.querySelector('.md, .wiki')) return; selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment); - replyField = $('.js-main-target-form #note_note'); if (selected.trim() === "") { return; } diff --git a/changelogs/unreleased/empty-selection-reply-shortcut.yml b/changelogs/unreleased/empty-selection-reply-shortcut.yml new file mode 100644 index 00000000000..5a42c98a800 --- /dev/null +++ b/changelogs/unreleased/empty-selection-reply-shortcut.yml @@ -0,0 +1,4 @@ +--- +title: Change the reply shortcut to focus the field even without a selection. +merge_request: 8873 +author: Brian Hall diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index 386fc8f514e..db2302c4fb0 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -27,11 +27,19 @@ return this.selector = 'form.js-main-target-form textarea#note_note'; }); describe('with empty selection', function() { - return it('does nothing', function() { - stubSelection(''); + it('does not return an error', function() { this.shortcut.replyWithSelectedText(); return expect($(this.selector).val()).toBe(''); }); + return it('triggers `input`', function() { + var focused; + focused = false; + $(this.selector).on('focus', function() { + return focused = true; + }); + this.shortcut.replyWithSelectedText(); + return expect(focused).toBe(true); + }); }); describe('with any selection', function() { beforeEach(function() { From cd582d3c19843e776cf4aaf151753ec61a9e56ef Mon Sep 17 00:00:00 2001 From: Brian Hall <brian@hack.design> Date: Tue, 31 Jan 2017 13:29:34 -0600 Subject: [PATCH 130/174] Remove unnecessary returns / unset variables from the CoffeeScript -> JS conversion. --- spec/javascripts/shortcuts_issuable_spec.js | 47 ++++++++++----------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index db2302c4fb0..db11c2516a6 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -11,9 +11,9 @@ beforeEach(function() { loadFixtures(fixtureName); document.querySelector('.js-new-note-form').classList.add('js-main-target-form'); - return this.shortcut = new ShortcutsIssuable(); + this.shortcut = new ShortcutsIssuable(); }); - return describe('#replyWithSelectedText', function() { + describe('#replyWithSelectedText', function() { var stubSelection; // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML. stubSelection = function(html) { @@ -24,64 +24,61 @@ }; }; beforeEach(function() { - return this.selector = 'form.js-main-target-form textarea#note_note'; + this.selector = 'form.js-main-target-form textarea#note_note'; }); describe('with empty selection', function() { it('does not return an error', function() { this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe(''); + expect($(this.selector).val()).toBe(''); }); - return it('triggers `input`', function() { - var focused; - focused = false; + it('triggers `input`', function() { + var focused = false; $(this.selector).on('focus', function() { - return focused = true; + focused = true; }); this.shortcut.replyWithSelectedText(); - return expect(focused).toBe(true); + expect(focused).toBe(true); }); }); describe('with any selection', function() { beforeEach(function() { - return stubSelection('<p>Selected text.</p>'); + stubSelection('<p>Selected text.</p>'); }); it('leaves existing input intact', function() { $(this.selector).val('This text was already here.'); expect($(this.selector).val()).toBe('This text was already here.'); this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n"); + expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n"); }); it('triggers `input`', function() { - var triggered; - triggered = false; + var triggered = false; $(this.selector).on('input', function() { - return triggered = true; + triggered = true; }); this.shortcut.replyWithSelectedText(); - return expect(triggered).toBe(true); + expect(triggered).toBe(true); }); - return it('triggers `focus`', function() { - var focused; - focused = false; + it('triggers `focus`', function() { + var focused = false; $(this.selector).on('focus', function() { - return focused = true; + focused = true; }); this.shortcut.replyWithSelectedText(); - return expect(focused).toBe(true); + expect(focused).toBe(true); }); }); describe('with a one-line selection', function() { - return it('quotes the selection', function() { + it('quotes the selection', function() { stubSelection('<p>This text has been selected.</p>'); this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe("> This text has been selected.\n\n"); + expect($(this.selector).val()).toBe("> This text has been selected.\n\n"); }); }); - return describe('with a multi-line selection', function() { - return it('quotes the selected lines as a group', function() { + describe('with a multi-line selection', function() { + it('quotes the selected lines as a group', function() { stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>"); this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n"); + expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n"); }); }); }); From dd1c410ea4478400e2650f4102726f2ab1a906bd Mon Sep 17 00:00:00 2001 From: Robert Speicher <rspeicher@gmail.com> Date: Sun, 29 Jan 2017 17:53:48 -0500 Subject: [PATCH 131/174] Reduce the number of loops that Cycle Analytics specs use See https://gitlab.com/gitlab-org/gitlab-ce/issues/27402 --- spec/models/cycle_analytics/code_spec.rb | 22 ++++---- spec/models/cycle_analytics/issue_spec.rb | 12 ++--- .../models/cycle_analytics/production_spec.rb | 18 +++---- spec/models/cycle_analytics/review_spec.rb | 4 +- spec/models/cycle_analytics/staging_spec.rb | 18 +++---- spec/models/cycle_analytics/test_spec.rb | 50 ++++++++---------- .../test_generation.rb | 52 ++++++++----------- 7 files changed, 73 insertions(+), 103 deletions(-) diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 3b7cc7d9e2e..9053485939e 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -27,15 +27,13 @@ describe 'CycleAnalytics#code', feature: true do context "when a regular merge request (that doesn't close the issue) is created" do it "returns nil" do - 5.times do - issue = create(:issue, project: project) + issue = create(:issue, project: project) - create_commit_referencing_issue(issue) - create_merge_request_closing_issue(issue, message: "Closes nothing") + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") - merge_merge_requests_closing_issue(issue) - deploy_master - end + merge_merge_requests_closing_issue(issue) + deploy_master expect(subject[:code].median).to be_nil end @@ -60,14 +58,12 @@ describe 'CycleAnalytics#code', feature: true do context "when a regular merge request (that doesn't close the issue) is created" do it "returns nil" do - 5.times do - issue = create(:issue, project: project) + issue = create(:issue, project: project) - create_commit_referencing_issue(issue) - create_merge_request_closing_issue(issue, message: "Closes nothing") + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") - merge_merge_requests_closing_issue(issue) - end + merge_merge_requests_closing_issue(issue) expect(subject[:code].median).to be_nil end diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index 5c73edbbc53..fc7d18bd40e 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -33,14 +33,12 @@ describe 'CycleAnalytics#issue', models: true do context "when a regular label (instead of a list label) is added to the issue" do it "returns nil" do - 5.times do - regular_label = create(:label) - issue = create(:issue, project: project) - issue.update(label_ids: [regular_label.id]) + regular_label = create(:label) + issue = create(:issue, project: project) + issue.update(label_ids: [regular_label.id]) - create_merge_request_closing_issue(issue) - merge_merge_requests_closing_issue(issue) - end + create_merge_request_closing_issue(issue) + merge_merge_requests_closing_issue(issue) expect(subject[:issue].median).to be_nil end diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 591bbdddf55..2cbee741fb0 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -29,11 +29,9 @@ describe 'CycleAnalytics#production', feature: true do context "when a regular merge request (that doesn't close the issue) is merged and deployed" do it "returns nil" do - 5.times do - merge_request = create(:merge_request) - MergeRequests::MergeService.new(project, user).execute(merge_request) - deploy_master - end + merge_request = create(:merge_request) + MergeRequests::MergeService.new(project, user).execute(merge_request) + deploy_master expect(subject[:production].median).to be_nil end @@ -41,12 +39,10 @@ describe 'CycleAnalytics#production', feature: true do context "when the deployment happens to a non-production environment" do it "returns nil" do - 5.times do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(issue) - MergeRequests::MergeService.new(project, user).execute(merge_request) - deploy_master(environment: 'staging') - end + issue = create(:issue, project: project) + merge_request = create_merge_request_closing_issue(issue) + MergeRequests::MergeService.new(project, user).execute(merge_request) + deploy_master(environment: 'staging') expect(subject[:production].median).to be_nil end diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 33d2c0a7416..febb18c9884 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -23,9 +23,7 @@ describe 'CycleAnalytics#review', feature: true do context "when a regular merge request (that doesn't close the issue) is created and merged" do it "returns nil" do - 5.times do - MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) - end + MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) expect(subject[:review].median).to be_nil end diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index 00693d67475..104e65335dd 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -40,11 +40,9 @@ describe 'CycleAnalytics#staging', feature: true do context "when a regular merge request (that doesn't close the issue) is merged and deployed" do it "returns nil" do - 5.times do - merge_request = create(:merge_request) - MergeRequests::MergeService.new(project, user).execute(merge_request) - deploy_master - end + merge_request = create(:merge_request) + MergeRequests::MergeService.new(project, user).execute(merge_request) + deploy_master expect(subject[:staging].median).to be_nil end @@ -52,12 +50,10 @@ describe 'CycleAnalytics#staging', feature: true do context "when the deployment happens to a non-production environment" do it "returns nil" do - 5.times do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(issue) - MergeRequests::MergeService.new(project, user).execute(merge_request) - deploy_master(environment: 'staging') - end + issue = create(:issue, project: project) + merge_request = create_merge_request_closing_issue(issue) + MergeRequests::MergeService.new(project, user).execute(merge_request) + deploy_master(environment: 'staging') expect(subject[:staging].median).to be_nil end diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index f857ea6cbec..c2ba012a0e6 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -24,16 +24,14 @@ describe 'CycleAnalytics#test', feature: true do context "when the pipeline is for a regular merge request (that doesn't close an issue)" do it "returns nil" do - 5.times do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(issue) - pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) + issue = create(:issue, project: project) + merge_request = create_merge_request_closing_issue(issue) + pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) - pipeline.run! - pipeline.succeed! + pipeline.run! + pipeline.succeed! - merge_merge_requests_closing_issue(issue) - end + merge_merge_requests_closing_issue(issue) expect(subject[:test].median).to be_nil end @@ -41,12 +39,10 @@ describe 'CycleAnalytics#test', feature: true do context "when the pipeline is not for a merge request" do it "returns nil" do - 5.times do - pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha) + pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha) - pipeline.run! - pipeline.succeed! - end + pipeline.run! + pipeline.succeed! expect(subject[:test].median).to be_nil end @@ -54,16 +50,14 @@ describe 'CycleAnalytics#test', feature: true do context "when the pipeline is dropped (failed)" do it "returns nil" do - 5.times do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(issue) - pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) + issue = create(:issue, project: project) + merge_request = create_merge_request_closing_issue(issue) + pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) - pipeline.run! - pipeline.drop! + pipeline.run! + pipeline.drop! - merge_merge_requests_closing_issue(issue) - end + merge_merge_requests_closing_issue(issue) expect(subject[:test].median).to be_nil end @@ -71,16 +65,14 @@ describe 'CycleAnalytics#test', feature: true do context "when the pipeline is cancelled" do it "returns nil" do - 5.times do - issue = create(:issue, project: project) - merge_request = create_merge_request_closing_issue(issue) - pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) + issue = create(:issue, project: project) + merge_request = create_merge_request_closing_issue(issue) + pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha) - pipeline.run! - pipeline.cancel! + pipeline.run! + pipeline.cancel! - merge_merge_requests_closing_issue(issue) - end + merge_merge_requests_closing_issue(issue) expect(subject[:test].median).to be_nil end diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index 10b90b40ba7..19b32c84d81 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -63,22 +63,20 @@ module CycleAnalyticsHelpers # test case. allow(self).to receive(:project) { other_project } - 5.times do - data = data_fn[self] - start_time = Time.now - end_time = rand(1..10).days.from_now + data = data_fn[self] + start_time = Time.now + end_time = rand(1..10).days.from_now - start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } - end - - end_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(end_time) { condition_fn[self, data] } - end - - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + start_time_conditions.each do |condition_name, condition_fn| + Timecop.freeze(start_time) { condition_fn[self, data] } end + end_time_conditions.each do |condition_name, condition_fn| + Timecop.freeze(end_time) { condition_fn[self, data] } + end + + Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + # Turn off the stub before checking assertions allow(self).to receive(:project).and_call_original @@ -114,17 +112,15 @@ module CycleAnalyticsHelpers context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do it "returns nil" do - 5.times do - data = data_fn[self] - end_time = rand(1..10).days.from_now + data = data_fn[self] + end_time = rand(1..10).days.from_now - end_time_conditions.each_with_index do |(condition_name, condition_fn), index| - Timecop.freeze(end_time + index.days) { condition_fn[self, data] } - end - - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + end_time_conditions.each_with_index do |(condition_name, condition_fn), index| + Timecop.freeze(end_time + index.days) { condition_fn[self, data] } end + Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + expect(subject[phase].median).to be_nil end end @@ -133,17 +129,15 @@ module CycleAnalyticsHelpers context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do it "returns nil" do - 5.times do - data = data_fn[self] - start_time = Time.now + data = data_fn[self] + start_time = Time.now - start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } - end - - post_fn[self, data] if post_fn + start_time_conditions.each do |condition_name, condition_fn| + Timecop.freeze(start_time) { condition_fn[self, data] } end + post_fn[self, data] if post_fn + expect(subject[phase].median).to be_nil end end From d751712dba0642d6f86b9c23224b17a34dba516b Mon Sep 17 00:00:00 2001 From: Bryce Johnson <bryce@gitlab.com> Date: Wed, 1 Feb 2017 16:39:27 -0500 Subject: [PATCH 132/174] Vertically center add-diff-note button. --- app/assets/stylesheets/pages/notes.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index da0caa30c26..f310cc72da0 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -467,7 +467,7 @@ ul.notes { } .add-diff-note { - margin-top: -4px; + margin-top: -8px; border-radius: 40px; background: $white-light; padding: 4px; From 21cf64b43140a6e5e9addd514da4304c70bd0815 Mon Sep 17 00:00:00 2001 From: Clement Ho <ClemMakesApps@gmail.com> Date: Wed, 1 Feb 2017 15:40:07 -0600 Subject: [PATCH 133/174] Fix filtered search manager spec teaspoon error --- .../filtered_search/filtered_search_manager_spec.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 index c8b5c2b36ad..a508dacf7f0 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 @@ -23,6 +23,7 @@ `); spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {}); spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); spyOn(gl.utils, 'getParameterByName').and.returnValue(null); From ceb1ebd9590aaddc96cc059735bcf571464a8460 Mon Sep 17 00:00:00 2001 From: Valery Sizov <valery@gitlab.com> Date: Thu, 11 Aug 2016 18:30:18 +0300 Subject: [PATCH 134/174] Active tense test coverage Ports changes from https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/642 back into CE --- spec/features/notes_on_merge_requests_spec.rb | 6 +++--- spec/helpers/diff_helper_spec.rb | 10 +++++----- spec/javascripts/awards_handler_spec.js | 2 +- spec/lib/gitlab/diff/highlight_spec.rb | 8 ++++---- spec/lib/gitlab/diff/parallel_diff_spec.rb | 2 +- spec/lib/gitlab/highlight_spec.rb | 2 +- spec/lib/gitlab/ldap/access_spec.rb | 2 +- spec/requests/api/builds_spec.rb | 2 +- spec/requests/api/groups_spec.rb | 8 ++++---- spec/requests/api/projects_spec.rb | 2 +- spec/requests/ci/api/builds_spec.rb | 4 ++-- spec/services/event_create_service_spec.rb | 6 +++--- spec/services/merge_requests/close_service_spec.rb | 2 +- spec/tasks/gitlab/mail_google_schema_whitelisting.rb | 2 +- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index b785b2f7704..fab2d532e06 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -89,7 +89,7 @@ describe 'Comments', feature: true do end end - it 'should reset the edit note form textarea with the original content of the note if cancelled' do + it 'resets the edit note form textarea with the original content of the note if cancelled' do within('.current-note-edit-form') do fill_in 'note[note]', with: 'Some new content' find('.btn-cancel').click @@ -198,7 +198,7 @@ describe 'Comments', feature: true do end describe 'the note form' do - it "shouldn't add a second form for same row" do + it "does not add a second form for same row" do click_diff_line is_expected. @@ -206,7 +206,7 @@ describe 'Comments', feature: true do count: 1) end - it 'should be removed when canceled' do + it 'is removed when canceled' do is_expected.to have_css('.js-temp-notes-holder') page.within("form[data-line-code='#{line_code}']") do diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 468bcc7badc..eae097126ce 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -134,7 +134,7 @@ describe DiffHelper do let(:new_pos) { 50 } let(:text) { 'some_text' } - it "should generate foldable top match line for inline view with empty text by default" do + it "generates foldable top match line for inline view with empty text by default" do output = diff_match_line old_pos, new_pos expect(output).to be_html_safe @@ -143,7 +143,7 @@ describe DiffHelper do expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: '' end - it "should allow to define text and bottom option" do + it "allows to define text and bottom option" do output = diff_match_line old_pos, new_pos, text: text, bottom: true expect(output).to be_html_safe @@ -152,7 +152,7 @@ describe DiffHelper do expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text end - it "should generate match line for parallel view" do + it "generates match line for parallel view" do output = diff_match_line old_pos, new_pos, text: text, view: :parallel expect(output).to be_html_safe @@ -162,7 +162,7 @@ describe DiffHelper do expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text end - it "should allow to generate only left match line for parallel view" do + it "allows to generate only left match line for parallel view" do output = diff_match_line old_pos, nil, text: text, view: :parallel expect(output).to be_html_safe @@ -171,7 +171,7 @@ describe DiffHelper do expect(output).not_to have_css 'td:nth-child(3)' end - it "should allow to generate only right match line for parallel view" do + it "allows to generate only right match line for parallel view" do output = diff_match_line nil, new_pos, text: text, view: :parallel expect(output).to be_html_safe diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index 71446b9df61..f1bfd529983 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -113,7 +113,7 @@ }); }); describe('::getAwardUrl', function() { - return it('should return the url for request', function() { + return it('returns the url for request', function() { return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); }); }); diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index 1e21270d928..5893485634d 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -12,11 +12,11 @@ describe Gitlab::Diff::Highlight, lib: true do context "with a diff file" do let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight } - it 'should return Gitlab::Diff::Line elements' do + it 'returns Gitlab::Diff::Line elements' do expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line) end - it 'should not modify "match" lines' do + it 'does not modify "match" lines' do expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen') expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen') end @@ -43,11 +43,11 @@ describe Gitlab::Diff::Highlight, lib: true do context "with diff lines" do let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight } - it 'should return Gitlab::Diff::Line elements' do + it 'returns Gitlab::Diff::Line elements' do expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line) end - it 'should not modify "match" lines' do + it 'does not modify "match" lines' do expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen') expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen') end diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index fe5fa048413..0f779339c54 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do subject { described_class.new(diff_file) } describe '#parallelize' do - it 'should return an array of arrays containing the parsed diff' do + it 'returns an array of arrays containing the parsed diff' do diff_lines = diff_file.highlighted_diff_lines expected = [ # Unchanged lines diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index fadfe4d378e..e177d883158 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::Highlight, lib: true do Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb') end - it 'should properly highlight all the lines' do + it 'highlights all the lines properly' do expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n}) expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n}) expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n}) diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index b9d12c3c24c..9dd997aa7dc 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::LDAP::Access, lib: true do it { is_expected.to be_falsey } - it 'should block user in GitLab' do + it 'blocks user in GitLab' do expect(access).to receive(:block_user).with(user, 'does not exist anymore') access.allowed? diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 645e36683bc..bd6e23ee769 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -67,7 +67,7 @@ describe API::Builds, api: true do context 'unauthorized user' do let(:api_user) { nil } - it 'should not return project builds' do + it 'does not return project builds' do expect(response).to have_http_status(401) end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 1187d2e609d..a027c23bb88 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -326,7 +326,7 @@ describe API::Groups, api: true do expect(response).to have_http_status(404) end - it "should only return projects to which user has access" do + it "only returns projects to which user has access" do project3.team << [user3, :developer] get api("/groups/#{group1.id}/projects", user3) @@ -338,7 +338,7 @@ describe API::Groups, api: true do end context "when authenticated as admin" do - it "should return any existing group" do + it "returns any existing group" do get api("/groups/#{group2.id}/projects", admin) expect(response).to have_http_status(200) @@ -346,7 +346,7 @@ describe API::Groups, api: true do expect(json_response.first['name']).to eq(project2.name) end - it "should not return a non existing group" do + it "does not return a non existing group" do get api("/groups/1328/projects", admin) expect(response).to have_http_status(404) @@ -354,7 +354,7 @@ describe API::Groups, api: true do end context 'when using group path in URL' do - it 'should return any existing group' do + it 'returns any existing group' do get api("/groups/#{group1.path}/projects", admin) expect(response).to have_http_status(200) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index a1db81ce18c..753dde0ca3a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -459,7 +459,7 @@ describe API::Projects, api: true do before { project } before { admin } - it 'should create new project without path and return 201' do + it 'creates new project without path and return 201' do expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) expect(response).to have_http_status(201) end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 8dbe5f0b025..1cedaa4ba63 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -458,7 +458,7 @@ describe Ci::API::Builds do before { build.run! } describe "POST /builds/:id/artifacts/authorize" do - context "should authorize posting artifact to running build" do + context "authorizes posting artifact to running build" do it "using token as parameter" do post authorize_url, { token: build.token }, headers @@ -492,7 +492,7 @@ describe Ci::API::Builds do end end - context "should fail to post too large artifact" do + context "fails to post too large artifact" do it "using token as parameter" do stub_application_setting(max_artifacts_size: 0) diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index b7dc99ed887..f2c2009bcbf 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -9,7 +9,7 @@ describe EventCreateService, services: true do it { expect(service.open_issue(issue, issue.author)).to be_truthy } - it "should create new event" do + it "creates new event" do expect { service.open_issue(issue, issue.author) }.to change { Event.count } end end @@ -19,7 +19,7 @@ describe EventCreateService, services: true do it { expect(service.close_issue(issue, issue.author)).to be_truthy } - it "should create new event" do + it "creates new event" do expect { service.close_issue(issue, issue.author) }.to change { Event.count } end end @@ -29,7 +29,7 @@ describe EventCreateService, services: true do it { expect(service.reopen_issue(issue, issue.author)).to be_truthy } - it "should create new event" do + it "creates new event" do expect { service.reopen_issue(issue, issue.author) }.to change { Event.count } end end diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 5f6a7716beb..d55a7657c0e 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -29,7 +29,7 @@ describe MergeRequests::CloseService, services: true do it { expect(@merge_request).to be_valid } it { expect(@merge_request).to be_closed } - it 'should execute hooks with close action' do + it 'executes hooks with close action' do expect(service).to have_received(:execute_hooks). with(@merge_request, 'close') end diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb index 80fc8c48fed..8d1cff7a261 100644 --- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb +++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb @@ -20,7 +20,7 @@ describe 'gitlab:mail_google_schema_whitelisting rake task' do Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting" end - it 'should run the task without errors' do + it 'runs the task without errors' do expect { run_rake_task }.not_to raise_error end end From 80ad1c62991fd5e3fcdc0e9d803d4df19bef9bf2 Mon Sep 17 00:00:00 2001 From: samrose3 <sam@gitlab.com> Date: Mon, 23 Jan 2017 18:30:03 -0500 Subject: [PATCH 135/174] Support non-ASCII characters in GFM autocomplete --- app/assets/javascripts/gfm_auto_complete.js.es6 | 4 ++-- ...ot-suggest-by-non-ascii-characters-in-name.yml | 4 ++++ spec/features/issues/gfm_autocomplete_spec.rb | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 3f23095dad9..7f1f2a5d278 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -83,12 +83,12 @@ _a = decodeURI("%C3%80"); _y = decodeURI("%C3%BF"); - regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)$", 'gi'); + regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi'); match = regexp.exec(subtext); if (match) { - return match[2] || match[1]; + return (match[1] || match[1] === "") ? match[1] : match[2]; } else { return null; } diff --git a/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml new file mode 100644 index 00000000000..1758ed9e9ea --- /dev/null +++ b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml @@ -0,0 +1,4 @@ +--- +title: Support non-ASCII characters in GFM autocomplete +merge_request: 8729 +author: diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 31156fcf994..93139dc9e94 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'GFM autocomplete', feature: true, js: true do include WaitForAjax - let(:user) { create(:user, username: 'someone.special') } + let(:user) { create(:user, name: '💃speciÄ…l someone💃', username: 'someone.special') } let(:project) { create(:project) } let(:label) { create(:label, project: project, title: 'special+') } let(:issue) { create(:issue, project: project) } @@ -59,6 +59,19 @@ feature 'GFM autocomplete', feature: true, js: true do expect(find('#at-view-64')).to have_selector('.cur:first-of-type') end + it 'includes items for assignee dropdowns with non-ASCII characters in name' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys("@#{user.name[0...8]}") + end + + expect(page).to have_selector('.atwho-container') + + wait_for_ajax + + expect(find('#at-view-64')).to have_content(user.name) + end + it 'selects the first item for non-assignee dropdowns if a query is entered' do page.within '.timeline-content-form' do find('#note_note').native.send_keys('') From f31fb929a39b3540a1fb6f4426227e6d388d0765 Mon Sep 17 00:00:00 2001 From: samrose3 <sam@gitlab.com> Date: Mon, 30 Jan 2017 19:22:45 -0500 Subject: [PATCH 136/174] Adjust pipeline graph height so they don't touch --- app/assets/stylesheets/pages/pipelines.scss | 3 ++- ...-many-stages-has-no-line-spacing-in-firefox-and-safari.yml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 47dfc22d533..8304fdc7646 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -201,7 +201,8 @@ .stage-container { display: inline-block; position: relative; - margin-right: 6px; + height: 22px; + margin: 3px 6px 3px 0; .tooltip { white-space: nowrap; diff --git a/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml b/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml new file mode 100644 index 00000000000..79316abbaf7 --- /dev/null +++ b/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml @@ -0,0 +1,4 @@ +--- +title: Fix pipeline graph vertical spacing in Firefox and Safari +merge_request: 8886 +author: From 2916ea82d9e3bdb69e6eeb9544f494d7269214bd Mon Sep 17 00:00:00 2001 From: Sam Rose <sam@gitlab.com> Date: Wed, 28 Dec 2016 11:53:05 -0500 Subject: [PATCH 137/174] Update pipeline and commit URL and text on CI status change --- .../javascripts/merge_request_widget.js.es6 | 22 +++++++++++++++- .../projects/merge_requests_controller.rb | 3 ++- .../merge_requests/widget/_heading.html.haml | 2 +- .../merge_requests/widget/_show.html.haml | 4 +++ ...-update-when-new-pipeline-is-triggered.yml | 4 +++ spec/javascripts/merge_request_widget_spec.js | 26 +++++++++++++++++-- 6 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 7cc319e2f4e..fa782ebbedf 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -154,12 +154,22 @@ return; } if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); - if (data.status !== _this.opts.ci_status && (data.status != null)) { + if (data.status !== _this.opts.ci_status || + data.sha !== _this.opts.ci_sha || + data.pipeline !== _this.opts.ci_pipeline) { _this.opts.ci_status = data.status; _this.showCIStatus(data.status); if (data.coverage) { _this.showCICoverage(data.coverage); } + if (data.pipeline) { + _this.opts.ci_pipeline = data.pipeline; + _this.updatePipelineUrls(data.pipeline); + } + if (data.sha) { + _this.opts.ci_sha = data.sha; + _this.updateCommitUrls(data.sha); + } if (showNotification) { status = _this.ciLabelForStatus(data.status); if (status === "preparing") { @@ -248,6 +258,16 @@ return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class); }; + MergeRequestWidget.prototype.updatePipelineUrls = function(id) { + const pipelineUrl = this.opts.pipeline_path; + $('.pipeline').text(`#${id}`).attr('href', [pipelineUrl, id].join('/')); + }; + + MergeRequestWidget.prototype.updateCommitUrls = function(id) { + const commitsUrl = this.opts.commits_path; + $('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/')); + }; + return MergeRequestWidget; })(); })(window.gl || (window.gl = {})); diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3492502e296..6eb542e4bd8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -434,7 +434,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController title: merge_request.title, sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha), status: status, - coverage: coverage + coverage: coverage, + pipeline: pipeline.try(:id) } render json: response diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 804a4a2473b..0e3af62ebc2 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -10,7 +10,7 @@ = ci_label_for_status(status) for = succeed "." do - = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" + = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace js-commit-link" %span.ci-coverage - elsif @merge_request.has_ci? diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 38328501ffd..f07e6b3ad54 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -24,6 +24,10 @@ preparing: "{{status}} build", normal: "Build {{status}}" }, + ci_sha: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.short_sha : ''}", + ci_pipeline: #{@merge_request.head_pipeline.try(:id).to_json}, + commits_path: "#{project_commits_path(@project)}", + pipeline_path: "#{project_pipelines_path(@project)}", pipelines_path: "#{pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}" }; diff --git a/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml new file mode 100644 index 00000000000..f74e9fa8b6d --- /dev/null +++ b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml @@ -0,0 +1,4 @@ +--- +title: Update pipeline and commit links when CI status is updated +merge_request: 8351 +author: diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index bf45100af03..6f1d6406897 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -1,6 +1,7 @@ /* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */ /*= require merge_request_widget */ +/*= require smart_interval */ /*= require lib/utils/datetime_utility */ (function() { @@ -21,7 +22,11 @@ normal: "Build {{status}}" }, gitlab_icon: "gitlab_logo.png", - builds_path: "http://sampledomain.local/sampleBuildsPath" + ci_pipeline: 80, + ci_sha: "12a34bc5", + builds_path: "http://sampledomain.local/sampleBuildsPath", + commits_path: "http://sampledomain.local/commits", + pipeline_path: "http://sampledomain.local/pipelines" }; this["class"] = new window.gl.MergeRequestWidget(this.opts); }); @@ -118,10 +123,11 @@ }); }); - return describe('getCIStatus', function() { + describe('getCIStatus', function() { beforeEach(function() { this.ciStatusData = { "title": "Sample MR title", + "pipeline": 80, "sha": "12a34bc5", "status": "success", "coverage": 98 @@ -165,6 +171,22 @@ this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); }); + it('should update the pipeline URL when the pipeline changes', function() { + var spy; + spy = spyOn(this["class"], 'updatePipelineUrls').and.stub(); + this["class"].getCIStatus(false); + this.ciStatusData.pipeline += 1; + this["class"].getCIStatus(false); + return expect(spy).toHaveBeenCalled(); + }); + it('should update the commit URL when the sha changes', function() { + var spy; + spy = spyOn(this["class"], 'updateCommitUrls').and.stub(); + this["class"].getCIStatus(false); + this.ciStatusData.sha = "9b50b99a"; + this["class"].getCIStatus(false); + return expect(spy).toHaveBeenCalled(); + }); }); }); }).call(this); From f799585c41d801bc657f992adf3d4b201af927d2 Mon Sep 17 00:00:00 2001 From: Sean McGivern <sean@gitlab.com> Date: Wed, 1 Feb 2017 16:38:52 +0000 Subject: [PATCH 138/174] Keep snippet visibility on error When a snippet is submitted, but there's an error, we didn't keep the visibility level. As the default is private, this means that submitting a public snippet that failed would then fall back to being a private snippet. --- app/helpers/visibility_level_helper.rb | 4 ---- app/models/snippet.rb | 2 +- app/views/projects/snippets/edit.html.haml | 2 +- app/views/projects/snippets/new.html.haml | 2 +- app/views/shared/snippets/_form.html.haml | 3 +-- app/views/snippets/edit.html.haml | 2 +- app/views/snippets/new.html.haml | 2 +- 7 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 3a83ae15dd8..fc93acfe63e 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -93,10 +93,6 @@ module VisibilityLevelHelper current_application_settings.default_project_visibility end - def default_snippet_visibility - current_application_settings.default_snippet_visibility - end - def default_group_visibility current_application_settings.default_group_visibility end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 771a7350556..960f1521be9 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -17,7 +17,7 @@ class Snippet < ActiveRecord::Base default_content_html_invalidator || file_name_changed? end - default_value_for :visibility_level, Snippet::PRIVATE + default_value_for(:visibility_level) { current_application_settings.default_snippet_visibility } belongs_to :author, class_name: 'User' belongs_to :project diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml index 216f70f5605..fb39028529d 100644 --- a/app/views/projects/snippets/edit.html.haml +++ b/app/views/projects/snippets/edit.html.haml @@ -3,4 +3,4 @@ %h3.page-title Edit Snippet %hr -= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level += render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet) diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml index 772a594269c..cfed3a79bc5 100644 --- a/app/views/projects/snippets/new.html.haml +++ b/app/views/projects/snippets/new.html.haml @@ -3,4 +3,4 @@ %h3.page-title New Snippet %hr -= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility += render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet) diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 0c788032020..2d22782eb36 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -11,7 +11,7 @@ .col-sm-10 = f.text_field :title, class: 'form-control', required: true, autofocus: true - = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet + = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet .file-editor .form-group @@ -34,4 +34,3 @@ = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel" - else = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" - diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml index 82f44a9a5c3..915bf98eb3e 100644 --- a/app/views/snippets/edit.html.haml +++ b/app/views/snippets/edit.html.haml @@ -2,4 +2,4 @@ %h3.page-title Edit Snippet %hr -= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level += render 'shared/snippets/form', url: snippet_path(@snippet) diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml index 79e2392490d..ca8afb4bb6a 100644 --- a/app/views/snippets/new.html.haml +++ b/app/views/snippets/new.html.haml @@ -2,4 +2,4 @@ %h3.page-title New Snippet %hr -= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility += render "shared/snippets/form", url: snippets_path(@snippet) From c63194ce6f952173649d7de4038aa96348e90565 Mon Sep 17 00:00:00 2001 From: Sean McGivern <sean@gitlab.com> Date: Wed, 1 Feb 2017 18:15:59 +0000 Subject: [PATCH 139/174] Check public snippets for spam Apply the same spam checks to public snippets (either personal snippets that are public, or public snippets on public projects) as to issues on public projects. --- app/controllers/concerns/spammable_actions.rb | 2 +- .../projects/snippets_controller.rb | 8 +- app/controllers/snippets_controller.rb | 6 +- app/models/concerns/spammable.rb | 8 +- app/models/project_snippet.rb | 4 + app/models/snippet.rb | 12 +++ app/services/create_snippet_service.rb | 9 ++- .../projects/snippets/_actions.html.haml | 5 ++ app/views/snippets/_actions.html.haml | 5 ++ changelogs/unreleased/snippet-spam.yml | 4 + config/routes/project.rb | 1 + config/routes/snippets.rb | 1 + lib/api/project_snippets.rb | 2 +- lib/api/snippets.rb | 2 +- .../projects/snippets_controller_spec.rb | 80 +++++++++++++++++++ spec/controllers/snippets_controller_spec.rb | 59 ++++++++++++++ spec/lib/gitlab/import_export/all_models.yml | 1 + spec/requests/api/project_snippets_spec.rb | 48 ++++++++++- spec/requests/api/snippets_spec.rb | 32 +++++++- 19 files changed, 277 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/snippet-spam.yml diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index 99acd98ae13..562f92bd83c 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -7,7 +7,7 @@ module SpammableActions def mark_as_spam if SpamService.new(spammable).mark_as_spam! - redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully." + redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully." else redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 02a97c1c574..5d193f26a8e 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -1,8 +1,9 @@ class Projects::SnippetsController < Projects::ApplicationController include ToggleAwardEmoji + include SpammableActions before_action :module_enabled - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] # Allow read any snippet before_action :authorize_read_project_snippet!, except: [:new, :create, :index] @@ -36,8 +37,8 @@ class Projects::SnippetsController < Projects::ApplicationController end def create - @snippet = CreateSnippetService.new(@project, current_user, - snippet_params).execute + create_params = snippet_params.merge(request: request) + @snippet = CreateSnippetService.new(@project, current_user, create_params).execute if @snippet.valid? respond_with(@snippet, @@ -88,6 +89,7 @@ class Projects::SnippetsController < Projects::ApplicationController @snippet ||= @project.snippets.find(params[:id]) end alias_method :awardable, :snippet + alias_method :spammable, :snippet def authorize_read_project_snippet! return render_404 unless can?(current_user, :read_project_snippet, @snippet) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index dee57e4a388..b169d993688 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,5 +1,6 @@ class SnippetsController < ApplicationController include ToggleAwardEmoji + include SpammableActions before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] @@ -40,8 +41,8 @@ class SnippetsController < ApplicationController end def create - @snippet = CreateSnippetService.new(nil, current_user, - snippet_params).execute + create_params = snippet_params.merge(request: request) + @snippet = CreateSnippetService.new(nil, current_user, create_params).execute respond_with @snippet.becomes(Snippet) end @@ -96,6 +97,7 @@ class SnippetsController < ApplicationController end end alias_method :awardable, :snippet + alias_method :spammable, :snippet def authorize_read_snippet! authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index 1aa97debe42..1acff093aa1 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -34,7 +34,13 @@ module Spammable end def check_for_spam - self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam? + if spam? + self.errors.add(:base, "Your #{spammable_entity_type} has been recognized as spam and has been discarded.") + end + end + + def spammable_entity_type + self.class.name.underscore end def spam_title diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 25b5d777641..9bb456eee24 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -9,4 +9,8 @@ class ProjectSnippet < Snippet participant :author participant :notes_with_associations + + def check_for_spam? + super && project.public? + end end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 960f1521be9..2665a7249a3 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -7,6 +7,7 @@ class Snippet < ActiveRecord::Base include Sortable include Awardable include Mentionable + include Spammable cache_markdown_field :title, pipeline: :single_line cache_markdown_field :content @@ -46,6 +47,9 @@ class Snippet < ActiveRecord::Base participant :author participant :notes_with_associations + attr_spammable :title, spam_title: true + attr_spammable :content, spam_description: true + def self.reference_prefix '$' end @@ -127,6 +131,14 @@ class Snippet < ActiveRecord::Base notes.includes(:author) end + def check_for_spam? + public? + end + + def spammable_entity_type + 'snippet' + end + class << self # Searches for snippets with a matching title or file name. # diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb index 95cc9baf406..14f5ba064ff 100644 --- a/app/services/create_snippet_service.rb +++ b/app/services/create_snippet_service.rb @@ -1,5 +1,8 @@ class CreateSnippetService < BaseService def execute + request = params.delete(:request) + api = params.delete(:api) + snippet = if project project.snippets.build(params) else @@ -12,8 +15,12 @@ class CreateSnippetService < BaseService end snippet.author = current_user + snippet.spam = SpamService.new(snippet, request).check(api) + + if snippet.save + UserAgentDetailService.new(snippet, request).create + end - snippet.save snippet end end diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index 068a6610350..e2a5107a883 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -8,6 +8,8 @@ - if can?(current_user, :create_project_snippet, @project) = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do New snippet + - if @snippet.submittable_as_spam? && current_user.admin? + = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -27,3 +29,6 @@ %li = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do Edit + - if @snippet.submittable_as_spam? && current_user.admin? + %li + = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 95fc7198104..9a9a3ff9220 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -8,6 +8,8 @@ - if current_user = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do New snippet + - if @snippet.submittable_as_spam? && current_user.admin? + = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' - if current_user .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -26,3 +28,6 @@ %li = link_to edit_snippet_path(@snippet) do Edit + - if @snippet.submittable_as_spam? && current_user.admin? + %li + = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post diff --git a/changelogs/unreleased/snippet-spam.yml b/changelogs/unreleased/snippet-spam.yml new file mode 100644 index 00000000000..4867f088953 --- /dev/null +++ b/changelogs/unreleased/snippet-spam.yml @@ -0,0 +1,4 @@ +--- +title: Check public snippets for spam +merge_request: +author: diff --git a/config/routes/project.rb b/config/routes/project.rb index f36febc6e04..efe2fbc521d 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -64,6 +64,7 @@ constraints(ProjectUrlConstrainer.new) do resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do member do get 'raw' + post :mark_as_spam end end diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb index 3ca096f31ba..ce0d1314292 100644 --- a/config/routes/snippets.rb +++ b/config/routes/snippets.rb @@ -2,6 +2,7 @@ resources :snippets, concerns: :awardable do member do get 'raw' get 'download' + post :mark_as_spam end end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 9d8c5b63685..dcc0c82ee27 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -58,7 +58,7 @@ module API end post ":id/snippets" do authorize! :create_project_snippet, user_project - snippet_params = declared_params + snippet_params = declared_params.merge(request: request, api: true) snippet_params[:content] = snippet_params.delete(:code) snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index e096e636806..eb9ece49e7f 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -64,7 +64,7 @@ module API desc: 'The visibility level of the snippet' end post do - attrs = declared_params(include_missing: false) + attrs = declared_params(include_missing: false).merge(request: request, api: true) snippet = CreateSnippetService.new(nil, current_user, attrs).execute if snippet.persisted? diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 32b0e42c3cd..88e4f81f232 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -69,6 +69,86 @@ describe Projects::SnippetsController do end end + describe 'POST #create' do + def create_snippet(project, snippet_params = {}) + sign_in(user) + + project.team << [user, :developer] + + post :create, { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params) + } + end + + context 'when the snippet is spam' do + before do + allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) + end + + context 'when the project is private' do + let(:private_project) { create(:project_empty_repo, :private) } + + context 'when the snippet is public' do + it 'creates the snippet' do + expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }. + to change { Snippet.count }.by(1) + end + end + end + + context 'when the project is public' do + context 'when the snippet is private' do + it 'creates the snippet' do + expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }. + to change { Snippet.count }.by(1) + end + end + + context 'when the snippet is public' do + it 'rejects the shippet' do + expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }. + not_to change { Snippet.count } + expect(response).to render_template(:new) + end + + it 'creates a spam log' do + expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }. + to change { SpamLog.count }.by(1) + end + end + end + end + end + + describe 'POST #mark_as_spam' do + let(:snippet) { create(:project_snippet, :private, project: project, author: user) } + + before do + allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true) + stub_application_setting(akismet_enabled: true) + end + + def mark_as_spam + admin = create(:admin) + create(:user_agent_detail, subject: snippet) + project.team << [admin, :master] + sign_in(admin) + + post :mark_as_spam, + namespace_id: project.namespace.path, + project_id: project.path, + id: snippet.id + end + + it 'updates the snippet' do + mark_as_spam + + expect(snippet.reload).not_to be_submittable_as_spam + end + end + %w[show raw].each do |action| describe "GET ##{action}" do context 'when the project snippet is private' do diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index d76fe9f580f..dadcb90cfc2 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -138,6 +138,65 @@ describe SnippetsController do end end + describe 'POST #create' do + def create_snippet(snippet_params = {}) + sign_in(user) + + post :create, { + personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params) + } + end + + context 'when the snippet is spam' do + before do + allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) + end + + context 'when the snippet is private' do + it 'creates the snippet' do + expect { create_snippet(visibility_level: Snippet::PRIVATE) }. + to change { Snippet.count }.by(1) + end + end + + context 'when the snippet is public' do + it 'rejects the shippet' do + expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + not_to change { Snippet.count } + expect(response).to render_template(:new) + end + + it 'creates a spam log' do + expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + to change { SpamLog.count }.by(1) + end + end + end + end + + describe 'POST #mark_as_spam' do + let(:snippet) { create(:personal_snippet, :public, author: user) } + + before do + allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true) + stub_application_setting(akismet_enabled: true) + end + + def mark_as_spam + admin = create(:admin) + create(:user_agent_detail, subject: snippet) + sign_in(admin) + + post :mark_as_spam, id: snippet.id + end + + it 'updates the snippet' do + mark_as_spam + + expect(snippet.reload).not_to be_submittable_as_spam + end + end + %w(raw download).each do |action| describe "GET #{action}" do context 'when the personal snippet is private' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 7fb6829f582..20241d4d63e 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -52,6 +52,7 @@ snippets: - project - notes - award_emoji +- user_agent_detail releases: - project project_members: diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 01032c0929b..9e25e30bc86 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -4,6 +4,7 @@ describe API::ProjectSnippets, api: true do include ApiHelpers let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } let(:admin) { create(:admin) } describe 'GET /projects/:project_id/snippets/:id' do @@ -50,7 +51,7 @@ describe API::ProjectSnippets, api: true do title: 'Test Title', file_name: 'test.rb', code: 'puts "hello world"', - visibility_level: Gitlab::VisibilityLevel::PUBLIC + visibility_level: Snippet::PUBLIC } end @@ -72,6 +73,51 @@ describe API::ProjectSnippets, api: true do expect(response).to have_http_status(400) end + + context 'when the snippet is spam' do + def create_snippet(project, snippet_params = {}) + project.team << [user, :developer] + + post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params) + end + + before do + allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) + end + + context 'when the project is private' do + let(:private_project) { create(:project_empty_repo, :private) } + + context 'when the snippet is public' do + it 'creates the snippet' do + expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }. + to change { Snippet.count }.by(1) + end + end + end + + context 'when the project is public' do + context 'when the snippet is private' do + it 'creates the snippet' do + expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }. + to change { Snippet.count }.by(1) + end + end + + context 'when the snippet is public' do + it 'rejects the shippet' do + expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }. + not_to change { Snippet.count } + expect(response).to have_http_status(400) + end + + it 'creates a spam log' do + expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }. + to change { SpamLog.count }.by(1) + end + end + end + end end describe 'PUT /projects/:project_id/snippets/:id/' do diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index f6fb6ea5506..6b9a739b439 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -80,7 +80,7 @@ describe API::Snippets, api: true do title: 'Test Title', file_name: 'test.rb', content: 'puts "hello world"', - visibility_level: Gitlab::VisibilityLevel::PUBLIC + visibility_level: Snippet::PUBLIC } end @@ -101,6 +101,36 @@ describe API::Snippets, api: true do expect(response).to have_http_status(400) end + + context 'when the snippet is spam' do + def create_snippet(snippet_params = {}) + post api('/snippets', user), params.merge(snippet_params) + end + + before do + allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) + end + + context 'when the snippet is private' do + it 'creates the snippet' do + expect { create_snippet(visibility_level: Snippet::PRIVATE) }. + to change { Snippet.count }.by(1) + end + end + + context 'when the snippet is public' do + it 'rejects the shippet' do + expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + not_to change { Snippet.count } + expect(response).to have_http_status(400) + end + + it 'creates a spam log' do + expect { create_snippet(visibility_level: Snippet::PUBLIC) }. + to change { SpamLog.count }.by(1) + end + end + end end describe 'PUT /snippets/:id' do From 19dda1606b4dc76160bf2198ab95f2998eccaec8 Mon Sep 17 00:00:00 2001 From: George Andrinopoulos <geoandri@gmail.com> Date: Thu, 2 Feb 2017 12:46:14 +0200 Subject: [PATCH 140/174] Force new password after password reset via API --- .../unreleased/24606-force-password-reset-on-next-login.yml | 4 ++++ lib/api/users.rb | 2 ++ spec/requests/api/users_spec.rb | 6 ++++++ 3 files changed, 12 insertions(+) create mode 100644 changelogs/unreleased/24606-force-password-reset-on-next-login.yml diff --git a/changelogs/unreleased/24606-force-password-reset-on-next-login.yml b/changelogs/unreleased/24606-force-password-reset-on-next-login.yml new file mode 100644 index 00000000000..fd671d04a9f --- /dev/null +++ b/changelogs/unreleased/24606-force-password-reset-on-next-login.yml @@ -0,0 +1,4 @@ +--- +title: Force new password after password reset via API +merge_request: +author: George Andrinopoulos diff --git a/lib/api/users.rb b/lib/api/users.rb index 11a7368b4c0..0ed468626b7 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -160,6 +160,8 @@ module API end end + user_params.merge!(password_expires_at: Time.now) if user_params[:password].present? + if user.update_attributes(user_params.except(:extern_uid, :provider)) present user, with: Entities::UserPublic else diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 5bf5bf0739e..f9127096953 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -305,6 +305,12 @@ describe API::Users, api: true do expect(user.reload.bio).to eq('new test bio') end + it "updates user with new password and forces reset on next login" do + put api("/users/#{user.id}", admin), { password: '12345678' } + expect(response).to have_http_status(200) + expect(user.reload.password_expires_at).to be < Time.now + end + it "updates user with organization" do put api("/users/#{user.id}", admin), { organization: 'GitLab' } From 21ea4863ea42a83566193862579329d865176ff9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Thu, 2 Feb 2017 11:09:24 +0000 Subject: [PATCH 141/174] Fixes broken build: Use jquery to get the element position in the page --- spec/features/merge_requests/toggler_behavior_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb index 6958f6a2c9f..44a9b545ff8 100644 --- a/spec/features/merge_requests/toggler_behavior_spec.rb +++ b/spec/features/merge_requests/toggler_behavior_spec.rb @@ -18,11 +18,10 @@ feature 'toggler_behavior', js: true, feature: true do it 'should be scrolled down to fragment' do page_height = page.current_window.size[1] page_scroll_y = page.evaluate_script("window.scrollY") - fragment_position_top = page.evaluate_script("document.querySelector('#{fragment_id}').getBoundingClientRect().top") - + fragment_position_top = page.evaluate_script("$('#{fragment_id}').offset().top") expect(find('.js-toggle-content').visible?).to eq true expect(find(fragment_id).visible?).to eq true - expect(fragment_position_top).to be > page_scroll_y + expect(fragment_position_top).to be >= page_scroll_y expect(fragment_position_top).to be < (page_scroll_y + page_height) end end From b329a4675ab3641e5b0526da40ed4f47d61b53d4 Mon Sep 17 00:00:00 2001 From: Nur Rony <pro.nmrony@gmail.com> Date: Thu, 2 Feb 2017 18:22:32 +0600 Subject: [PATCH 142/174] removes old css class from everywhere --- app/assets/stylesheets/framework/buttons.scss | 4 ---- app/helpers/blob_helper.rb | 4 ++-- .../projects/merge_requests/conflicts/_file_actions.html.haml | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index bb6129158d9..cda46223492 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -330,10 +330,6 @@ } } -.btn-file-option { - background: linear-gradient(180deg, $white-light 25%, $gray-light 100%); -} - .btn-build { margin-left: 10px; diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index c3508443d8a..311a70725ab 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -21,7 +21,7 @@ module BlobHelper options[:link_opts]) if !on_top_of_branch?(project, ref) - button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } + button_tag "Edit", class: "btn disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } elsif can_edit_blob?(blob, project, ref) link_to "Edit", edit_path, class: 'btn btn-sm' elsif can?(current_user, :fork_project, project) @@ -32,7 +32,7 @@ module BlobHelper } fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) - link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post + link_to "Edit", fork_path, class: 'btn', method: :post end end diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml index 2595ce74ac0..0839880713f 100644 --- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -8,5 +8,5 @@ '@click' => "onClickResolveModeButton(file, 'edit')", type: 'button' } Edit inline - %a.btn.view-file.btn-file-option{ ":href" => "file.blobPath" } + %a.btn.view-file{ ":href" => "file.blobPath" } View file @{{conflictsData.shortCommitSha}} From 34918d94c011e8f81bd962d43d67fe8bd9f21e3e Mon Sep 17 00:00:00 2001 From: Sean McGivern <sean@gitlab.com> Date: Thu, 2 Feb 2017 13:10:42 +0000 Subject: [PATCH 143/174] Use `add_$role` helper in snippets specs --- spec/controllers/projects/snippets_controller_spec.rb | 8 ++++---- spec/requests/api/project_snippets_spec.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 88e4f81f232..19e948d8fb8 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -6,8 +6,8 @@ describe Projects::SnippetsController do let(:user2) { create(:user) } before do - project.team << [user, :master] - project.team << [user2, :master] + project.add_master(user) + project.add_master(user2) end describe 'GET #index' do @@ -73,7 +73,7 @@ describe Projects::SnippetsController do def create_snippet(project, snippet_params = {}) sign_in(user) - project.team << [user, :developer] + project.add_developer(user) post :create, { namespace_id: project.namespace.to_param, @@ -133,7 +133,7 @@ describe Projects::SnippetsController do def mark_as_spam admin = create(:admin) create(:user_agent_detail, subject: snippet) - project.team << [admin, :master] + project.add_master(admin) sign_in(admin) post :mark_as_spam, diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 9e25e30bc86..45d5ae267c5 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -23,7 +23,7 @@ describe API::ProjectSnippets, api: true do let(:user) { create(:user) } it 'returns all snippets available to team member' do - project.team << [user, :developer] + project.add_developer(user) public_snippet = create(:project_snippet, :public, project: project) internal_snippet = create(:project_snippet, :internal, project: project) private_snippet = create(:project_snippet, :private, project: project) @@ -76,7 +76,7 @@ describe API::ProjectSnippets, api: true do context 'when the snippet is spam' do def create_snippet(project, snippet_params = {}) - project.team << [user, :developer] + project.add_developer(user) post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params) end From 161d74f1a6e58dae29ad6ee37b7d70cdb4999cc4 Mon Sep 17 00:00:00 2001 From: Phil Hughes <me@iamphill.com> Date: Thu, 2 Feb 2017 09:31:09 +0000 Subject: [PATCH 144/174] Fixed group label in issuable sidebar Group label link was pointing to group#issues rather than the projects issues. This fixes that by sending the correct subject to the link_to_label helper method. Closes #27253 --- app/views/shared/issuable/_sidebar.html.haml | 2 +- .../unreleased/group-label-sidebar-link.yml | 4 ++++ .../issues/group_label_sidebar_spec.rb | 21 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/group-label-sidebar-link.yml create mode 100644 spec/features/issues/group_label_sidebar_spec.rb diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index ec9bcaf63dd..10fa7901874 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -130,7 +130,7 @@ .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) } - if selected_labels.any? - selected_labels.each do |label| - = link_to_label(label, type: issuable.to_ability_name) + = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name) - else %span.no-value None .selectbox.hide-collapsed diff --git a/changelogs/unreleased/group-label-sidebar-link.yml b/changelogs/unreleased/group-label-sidebar-link.yml new file mode 100644 index 00000000000..c11c2d4ede1 --- /dev/null +++ b/changelogs/unreleased/group-label-sidebar-link.yml @@ -0,0 +1,4 @@ +--- +title: Fixed group label links in issue/merge request sidebar +merge_request: +author: diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb new file mode 100644 index 00000000000..fc8515cfe9b --- /dev/null +++ b/spec/features/issues/group_label_sidebar_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe 'Group label on issue', :feature do + it 'renders link to the project issues page' do + group = create(:group) + project = create(:empty_project, :public, namespace: group) + feature = create(:group_label, group: group, title: 'feature') + issue = create(:labeled_issue, project: project, labels: [feature]) + label_link = namespace_project_issues_path( + project.namespace, + project, + label_name: [feature.name] + ) + + visit namespace_project_issue_path(project.namespace, project, issue) + + link = find('.issuable-show-labels a') + + expect(link[:href]).to eq(label_link) + end +end From d796e4fc371a8613e77c70f5571813818c6a35ad Mon Sep 17 00:00:00 2001 From: George Andrinopoulos <geoandri@gmail.com> Date: Thu, 2 Feb 2017 17:15:02 +0200 Subject: [PATCH 145/174] Update api docs and minor changes --- doc/api/users.md | 1 + spec/requests/api/users_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 28b6c7bd491..fea9bdf9639 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -271,6 +271,7 @@ Parameters: - `can_create_group` (optional) - User can create groups - true or false - `external` (optional) - Flags the user as external - true or false(default) +On password update, user will be forced to change it upon next login. Note, at the moment this method does only return a `404` error, even in cases where a `409` (Conflict) would be more appropriate, e.g. when renaming the email address to some existing one. diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f9127096953..8692f9da976 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -306,9 +306,10 @@ describe API::Users, api: true do end it "updates user with new password and forces reset on next login" do - put api("/users/#{user.id}", admin), { password: '12345678' } + put api("/users/#{user.id}", admin), password: '12345678' + expect(response).to have_http_status(200) - expect(user.reload.password_expires_at).to be < Time.now + expect(user.reload.password_expires_at).to be <= Time.now end it "updates user with organization" do From 6dcfc4002e24c54c2f60b53bb2761a300bf68735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me> Date: Thu, 2 Feb 2017 10:21:14 +0100 Subject: [PATCH 146/174] Don't require lib/gitlab/request_profiler/middleware.rb in config/initializers/request_profiler.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable <remy@rymai.me> --- ...20452-remove-require-from-request_profiler-initializer.yml | 4 ++++ config/initializers/request_profiler.rb | 2 -- lib/gitlab/request_profiler/middleware.rb | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml diff --git a/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml b/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml new file mode 100644 index 00000000000..965d0648adf --- /dev/null +++ b/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml @@ -0,0 +1,4 @@ +--- +title: Don't require lib/gitlab/request_profiler/middleware.rb in config/initializers/request_profiler.rb +merge_request: +author: diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb index a9aa802681a..fb5a7b8372e 100644 --- a/config/initializers/request_profiler.rb +++ b/config/initializers/request_profiler.rb @@ -1,5 +1,3 @@ -require 'gitlab/request_profiler/middleware' - Rails.application.configure do |config| config.middleware.use(Gitlab::RequestProfiler::Middleware) end diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb index 786e1d49f5e..ef42b0557e0 100644 --- a/lib/gitlab/request_profiler/middleware.rb +++ b/lib/gitlab/request_profiler/middleware.rb @@ -1,5 +1,4 @@ require 'ruby-prof' -require_dependency 'gitlab/request_profiler' module Gitlab module RequestProfiler @@ -20,7 +19,7 @@ module Gitlab header_token = env['HTTP_X_PROFILE_TOKEN'] return unless header_token.present? - profile_token = RequestProfiler.profile_token + profile_token = Gitlab::RequestProfiler.profile_token return unless profile_token.present? header_token == profile_token From 32b3a82d81b76a0748ea316405a38b4abeb3a512 Mon Sep 17 00:00:00 2001 From: Sean McGivern <sean@mcgivern.me.uk> Date: Thu, 2 Feb 2017 17:22:11 +0000 Subject: [PATCH 147/174] Fix Ruby verification command --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 425c5d93efb..b2d5d51d37d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -124,7 +124,7 @@ Download Ruby and compile it: mkdir /tmp/ruby && cd /tmp/ruby curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.3.tar.gz - echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz + echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz cd ruby-2.3.3 ./configure --disable-install-rdoc make From 4fc0a6e943cedbf41ced19ef1c094e1c56f5ca9b Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 12:40:49 -0600 Subject: [PATCH 148/174] ignore node_modules in rubocop --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index bf2b2d8afc2..cfff42e5c99 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -17,6 +17,7 @@ AllCops: # Exclude some GitLab files Exclude: - 'vendor/**/*' + - 'node_modules/**/*' - 'db/*' - 'db/fixtures/**/*' - 'tmp/**/*' From 4fb757407d7ebae64ba73a3ce94bf33302cad500 Mon Sep 17 00:00:00 2001 From: Cindy Pallares <cindy@gitlab.com> Date: Thu, 2 Feb 2017 12:30:07 -0600 Subject: [PATCH 149/174] Update installation docs to include Docker, others As per this comment https://gitlab.com/gitlab-com/support/issues/374#note_22056587 we're moving documentation around from the support workflows. --- doc/install/README.md | 3 + doc/install/digitaloceandocker.md | 136 ++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 doc/install/digitaloceandocker.md diff --git a/doc/install/README.md b/doc/install/README.md index 239f5f301ec..2d2fd8cb380 100644 --- a/doc/install/README.md +++ b/doc/install/README.md @@ -4,3 +4,6 @@ - [Requirements](requirements.md) - [Structure](structure.md) - [Database MySQL](database_mysql.md) +- [Digital Ocean and Docker](digitaloceandocker.md) +- [Docker](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/docker) +- [All installation methods](https://about.gitlab.com/installation/) diff --git a/doc/install/digitaloceandocker.md b/doc/install/digitaloceandocker.md new file mode 100644 index 00000000000..820060a489b --- /dev/null +++ b/doc/install/digitaloceandocker.md @@ -0,0 +1,136 @@ +# Digital Ocean and Docker + +## Initial setup + +In this guide you'll configure a Digital Ocean droplet and set up Docker +locally on either macOS or Linux. + +### On macOS + +#### Install Docker Toolbox + +1. [https://www.docker.com/products/docker-toolbox](https://www.docker.com/products/docker-toolbox) + +### On Linux + +#### Install Docker Engine + +1. [https://docs.docker.com/engine/installation/linux](https://docs.docker.com/engine/installation/linux/) + +#### Install Docker Machine + +1. [https://docs.docker.com/machine/install-machine](https://docs.docker.com/machine/install-machine/) + +_The rest of the steps are identical for macOS and Linux_ + +### Create new docker host + +1. Login to Digital Ocean +1. Generate a new API token at https://cloud.digitalocean.com/settings/api/tokens + + +This command will create a new DO droplet called `gitlab-test-env-do` that will act as a docker host. + +**Note: 4GB is the minimum requirement for a Docker host that will run more then one GitLab instance** + ++ RAM: 4GB ++ Name: `gitlab-test-env-do` ++ Driver: `digitalocean` + + +**Set the DO token** - Replace the string below with your generated token + +``` +export DOTOKEN=cf3dfd0662933203005c4a73396214b7879d70aabc6352573fe178d340a80248 +``` + +**Create the machine** + +``` +docker-machine create \ + --driver digitalocean \ + --digitalocean-access-token=$DOTOKEN \ + --digitalocean-size "4gb" \ + gitlab-test-env-do +``` + ++ Resource: https://docs.docker.com/machine/drivers/digital-ocean/ + + +### Creating GitLab test instance + + +#### Connect your shell to the new machine + + +In this example we'll create a GitLab EE 8.10.8 instance. + + +First connect the docker client to the docker host you created previously. + +``` +eval "$(docker-machine env gitlab-test-env-do)" +``` + +You can add this to your `~/.bash_profile` file to ensure the `docker` client uses the `gitlab-test-env-do` docker host + + +#### Create new GitLab container + ++ HTTP port: `8888` ++ SSH port: `2222` + + Set `gitlab_shell_ssh_port` using `--env GITLAB_OMNIBUS_CONFIG ` ++ Hostname: IP of docker host ++ Container name: `gitlab-test-8.10` ++ GitLab version: **EE** `8.10.8-ee.0` + +##### Setup container settings + +``` +export SSH_PORT=2222 +export HTTP_PORT=8888 +export VERSION=8.10.8-ee.0 +export NAME=gitlab-test-8.10 +``` + +##### Create container +``` +docker run --detach \ +--env GITLAB_OMNIBUS_CONFIG="external_url 'http://$(docker-machine ip gitlab-test-env-do):$HTTP_PORT'; gitlab_rails['gitlab_shell_ssh_port'] = $SSH_PORT;" \ +--hostname $(docker-machine ip gitlab-test-env-do) \ +-p $HTTP_PORT:$HTTP_PORT -p $SSH_PORT:22 \ +--name $NAME \ +gitlab/gitlab-ee:$VERSION +``` + +#### Connect to the GitLab container + +##### Retrieve the docker host IP + +``` +docker-machine ip gitlab-test-env-do +# example output: 192.168.151.134 +``` + + ++ Browse to: http://192.168.151.134:8888/ + + +##### Execute interactive shell/edit configuration + + +``` +docker exec -it $NAME /bin/bash +``` + +``` +# example commands +root@192:/# vi /etc/gitlab/gitlab.rb +root@192:/# gitlab-ctl reconfigure +``` + +#### Resources + ++ [https://docs.gitlab.com/omnibus/docker/](https://docs.gitlab.com/omnibus/docker/) ++ [https://docs.docker.com/machine/get-started/](https://docs.docker.com/machine/get-started/) ++ [https://docs.docker.com/machine/reference/ip/](https://docs.docker.com/machine/reference/ip/)+ From 42086615e5bede8b0720b410b18017d12c87adfa Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 12:55:01 -0600 Subject: [PATCH 150/174] fix intermittant errors in merge_commit_message_toggle_spec.rb --- app/assets/javascripts/merge_request.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 09ee8dbe9d7..37af422a09e 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -110,9 +110,8 @@ }; MergeRequest.prototype.initCommitMessageListeners = function() { - var textarea = $('textarea.js-commit-message'); - - $('a.js-with-description-link').on('click', function(e) { + $(document).on('click', 'a.js-with-description-link', function(e) { + var textarea = $('textarea.js-commit-message'); e.preventDefault(); textarea.val(textarea.data('messageWithDescription')); @@ -120,7 +119,8 @@ $('p.js-without-description-hint').show(); }); - $('a.js-without-description-link').on('click', function(e) { + $(document).on('click', 'a.js-without-description-link', function(e) { + var textarea = $('textarea.js-commit-message'); e.preventDefault(); textarea.val(textarea.data('messageWithoutDescription')); From bccd791d4d698b6011ecea781d0ebc7c9ec1ddf8 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 12:57:28 -0600 Subject: [PATCH 151/174] fix errors within gl_dropdown_spec.js when running in Karma --- app/assets/javascripts/gl_dropdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d2f66cf5249..5c86e98567a 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -249,7 +249,7 @@ _this.fullData = data; _this.parseData(_this.fullData); _this.focusTextInput(); - if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val().trim() !== '') { + if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') { return _this.filter.input.trigger('input'); } }; From 36025b67d90633e2c64c182727424dd8dce1b03f Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:00:42 -0600 Subject: [PATCH 152/174] fix broken reference to formatDate in a CommonJS environment --- app/assets/javascripts/users/calendar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index e7280d643d3..e0e40ad3adb 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -33,7 +33,7 @@ date.setDate(date.getDate() + i); var day = date.getDay(); - var count = timestamps[dateFormat(date, 'yyyy-mm-dd')]; + var count = timestamps[date.format('yyyy-mm-dd')]; // Create a new group array if this is the first day of the week // or if is first object @@ -122,7 +122,7 @@ if (stamp.count > 0) { contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : ''); } - dateText = dateFormat(date, 'mmm d, yyyy'); + dateText = date.format('mmm d, yyyy'); return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText; }; })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) { From 96bbe965d2bfd564d82067d1e21beac17434f525 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:02:26 -0600 Subject: [PATCH 153/174] remove redundant "data-toggle" attribute so Vue doesn't complain --- .../javascripts/vue_pipelines_index/pipeline_actions.js.es6 | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index b195b0ef3ba..a7176e27ea1 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -28,7 +28,6 @@ data-toggle="dropdown" title="Manual build" data-placement="top" - data-toggle="dropdown" aria-label="Manual build" > <span v-html='svgs.iconPlay' aria-hidden="true"></span> @@ -54,7 +53,6 @@ data-toggle="dropdown" title="Artifacts" data-placement="top" - data-toggle="dropdown" aria-label="Artifacts" > <i class="fa fa-download" aria-hidden="true"></i> From 030e766d29703baec8690b7dd54bf213f0d93d14 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:05:51 -0600 Subject: [PATCH 154/174] allow console.xxx in tests, reorder eslint rules alphabetically --- spec/javascripts/.eslintrc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc index 3cd419b37c9..fbd9bb9f0ff 100644 --- a/spec/javascripts/.eslintrc +++ b/spec/javascripts/.eslintrc @@ -22,9 +22,10 @@ }, "plugins": ["jasmine"], "rules": { - "prefer-arrow-callback": 0, "func-names": 0, "jasmine/no-suite-dupes": [1, "branch"], - "jasmine/no-spec-dupes": [1, "branch"] + "jasmine/no-spec-dupes": [1, "branch"], + "no-console": 0, + "prefer-arrow-callback": 0 } } From 86f4166e3151bec9b5903bd77c6373ca9ee2fc62 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:08:52 -0600 Subject: [PATCH 155/174] fix fixture references in environments_spec --- spec/javascripts/environments/environment_spec.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 index 20e11ca3738..239cd69dd3a 100644 --- a/spec/javascripts/environments/environment_spec.js.es6 +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -8,12 +8,12 @@ //= require ./mock_data describe('Environment', () => { - preloadFixtures('environments/environments'); + preloadFixtures('static/environments/environments.html.raw'); let component; beforeEach(() => { - loadFixtures('environments/environments'); + loadFixtures('static/environments/environments.html.raw'); }); describe('successfull request', () => { From 7e7875b10c80ed19a3286ee1c544038e9306da4f Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:11:40 -0600 Subject: [PATCH 156/174] preload projects.json fixture --- spec/javascripts/gl_dropdown_spec.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 index 06fa64b1b4e..4e7eed2767c 100644 --- a/spec/javascripts/gl_dropdown_spec.js.es6 +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -44,6 +44,7 @@ describe('Dropdown', function describeDropdown() { preloadFixtures('static/gl_dropdown.html.raw'); + loadJSONFixtures('projects.json'); function initDropDown(hasRemote, isFilterable) { this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({ From b7ddbf24011381a5ce0f3c0a98d3ef3e93d07769 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:17:38 -0600 Subject: [PATCH 157/174] ensure helper classes and constants are exposed globally --- spec/javascripts/boards/mock_data.js.es6 | 5 +++++ spec/javascripts/environments/mock_data.js.es6 | 6 +++++- spec/javascripts/helpers/class_spec_helper.js.es6 | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index 8d3e2237fda..7a399b307ad 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -56,3 +56,8 @@ const boardsMockInterceptor = (request, next) => { status: 200 })); }; + +window.listObj = listObj; +window.listObjDuplicate = listObjDuplicate; +window.BoardsMockData = BoardsMockData; +window.boardsMockInterceptor = boardsMockInterceptor; diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6 index 8ecd01f9a83..58f6fb96afb 100644 --- a/spec/javascripts/environments/mock_data.js.es6 +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-vars */ + const environmentsList = [ { id: 31, @@ -134,6 +134,8 @@ const environmentsList = [ }, ]; +window.environmentsList = environmentsList; + const environment = { id: 4, name: 'production', @@ -147,3 +149,5 @@ const environment = { created_at: '2016-12-16T11:51:04.690Z', updated_at: '2016-12-16T12:04:51.133Z', }; + +window.environment = environment; diff --git a/spec/javascripts/helpers/class_spec_helper.js.es6 b/spec/javascripts/helpers/class_spec_helper.js.es6 index 92a20687ec5..d3c37d39431 100644 --- a/spec/javascripts/helpers/class_spec_helper.js.es6 +++ b/spec/javascripts/helpers/class_spec_helper.js.es6 @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ - class ClassSpecHelper { static itShouldBeAStaticMethod(base, method) { return it('should be a static method', () => { @@ -7,3 +5,5 @@ class ClassSpecHelper { }); } } + +window.ClassSpecHelper = ClassSpecHelper; From f38b7cee7bdc261892c3b9354c56d54633c46054 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:18:21 -0600 Subject: [PATCH 158/174] use setFixtures instead of fixture.set --- spec/javascripts/issuable_time_tracker_spec.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/issuable_time_tracker_spec.js.es6 b/spec/javascripts/issuable_time_tracker_spec.js.es6 index a1e979e8d09..c5671af235e 100644 --- a/spec/javascripts/issuable_time_tracker_spec.js.es6 +++ b/spec/javascripts/issuable_time_tracker_spec.js.es6 @@ -4,7 +4,7 @@ //= require issuable/time_tracking/components/time_tracker function initTimeTrackingComponent(opts) { - fixture.set(` + setFixtures(` <div> <div id="mock-container"></div> </div> From 48a053f334f289ab5283fed9c000594c16fbd27f Mon Sep 17 00:00:00 2001 From: Bryce Johnson <bryce@gitlab.com> Date: Wed, 1 Feb 2017 15:55:55 -0500 Subject: [PATCH 159/174] Only render hr when user can't archive project. --- app/views/projects/edit.html.haml | 2 +- .../27321-double-separator-line-in-edit-projects-settings.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index ec944d4ffb7..4a0ce995165 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -185,8 +185,8 @@ %li Container registry images %li CI variables %li Any encrypted tokens - %hr - if can? current_user, :archive_project, @project + %hr .row.prepend-top-default .col-lg-3 %h4.warning-title.prepend-top-0 diff --git a/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml b/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml new file mode 100644 index 00000000000..502927cd160 --- /dev/null +++ b/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml @@ -0,0 +1,4 @@ +--- +title: Only render hr when user can't archive project. +merge_request: !8917 +author: From 9e8762f898b9bc424600969b8d25eeb6549fe159 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:20:23 -0600 Subject: [PATCH 160/174] rework tests which rely on teaspoon-specific behavior --- .../javascripts/lib/utils/common_utils_spec.js.es6 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6 index 1ce8f28e568..32c96e2a088 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js.es6 +++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6 @@ -10,9 +10,9 @@ // IE11 will return a relative pathname while other browsers will return a full pathname. // parseUrl uses an anchor element for parsing an url. With relative urls, the anchor // element will create an absolute url relative to the current execution context. - // The JavaScript test suite is executed at '/teaspoon' which will lead to an absolute - // url starting with '/teaspoon'. - expect(gl.utils.parseUrl('" test="asf"').pathname).toEqual('/teaspoon/%22%20test=%22asf%22'); + // The JavaScript test suite is executed at '/' which will lead to an absolute url + // starting with '/'. + expect(gl.utils.parseUrl('" test="asf"').pathname).toContain('/%22%20test=%22asf%22'); }); }); @@ -42,9 +42,13 @@ }); describe('gl.utils.getParameterByName', () => { + beforeEach(() => { + window.history.pushState({}, null, '?scope=all&p=2'); + }); + it('should return valid parameter', () => { - const value = gl.utils.getParameterByName('reporter'); - expect(value).toBe('Console'); + const value = gl.utils.getParameterByName('scope'); + expect(value).toBe('all'); }); it('should return invalid parameter', () => { From ed8b6ecb9d18ea53074b9c0402ee87c361661e16 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:22:57 -0600 Subject: [PATCH 161/174] preload projects.json fixture --- spec/javascripts/project_title_spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index 0202c9ba85e..e562385a6c6 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -17,6 +17,8 @@ describe('Project Title', function() { preloadFixtures('static/project_title.html.raw'); + loadJSONFixtures('projects.json'); + beforeEach(function() { loadFixtures('static/project_title.html.raw'); return this.project = new Project(); From 66f9086fbc8f68574d9754367a25157480b23e0e Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:24:41 -0600 Subject: [PATCH 162/174] preload projects.json fixture --- spec/javascripts/right_sidebar_spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 942778229b5..3a01a534557 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -37,6 +37,8 @@ describe('RightSidebar', function() { var fixtureName = 'issues/open-issue.html.raw'; preloadFixtures(fixtureName); + loadJSONFixtures('todos.json'); + beforeEach(function() { loadFixtures(fixtureName); this.sidebar = new Sidebar; From f4fca2de924a05cccfebc23229f43884ec0b1048 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:25:56 -0600 Subject: [PATCH 163/174] simplify test for focus state --- spec/javascripts/shortcuts_issuable_spec.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index db11c2516a6..e0a5a7927bb 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -59,12 +59,8 @@ expect(triggered).toBe(true); }); it('triggers `focus`', function() { - var focused = false; - $(this.selector).on('focus', function() { - focused = true; - }); this.shortcut.replyWithSelectedText(); - expect(focused).toBe(true); + expect(document.activeElement).toBe(document.querySelector(this.selector)); }); }); describe('with a one-line selection', function() { From 85f2dcf535dba851a0e7690d358dac7f68379734 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:28:35 -0600 Subject: [PATCH 164/174] prevent u2f tests from triggering a form submission while testing --- spec/javascripts/u2f/authenticate_spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index 80163fd72d3..0e2fb07ba7f 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -25,19 +25,20 @@ document.querySelector('#js-login-2fa-device'), document.querySelector('.js-2fa-form') ); + + // bypass automatic form submission within renderAuthenticated + spyOn(this.component, 'renderAuthenticated').and.returnValue(true); + return this.component.start(); }); it('allows authenticating via a U2F device', function() { - var authenticatedMessage, deviceResponse, inProgressMessage; + var inProgressMessage; inProgressMessage = this.container.find("p"); expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); this.u2fDevice.respondToAuthenticateRequest({ deviceData: "this is data from the device" }); - authenticatedMessage = this.container.find("p"); - deviceResponse = this.container.find('#js-device-response'); - expect(authenticatedMessage.text()).toContain('We heard back from your U2F device. You have been authenticated.'); - return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}'); + expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); }); return describe("errors", function() { it("displays an error message", function() { @@ -51,7 +52,7 @@ return expect(errorMessage.text()).toContain("There was a problem communicating with your device"); }); return it("allows retrying authentication after an error", function() { - var authenticatedMessage, retryButton, setupButton; + var retryButton, setupButton; setupButton = this.container.find("#js-login-u2f-device"); setupButton.trigger('click'); this.u2fDevice.respondToAuthenticateRequest({ @@ -64,8 +65,7 @@ this.u2fDevice.respondToAuthenticateRequest({ deviceData: "this is data from the device" }); - authenticatedMessage = this.container.find("p"); - return expect(authenticatedMessage.text()).toContain("We heard back from your U2F device. You have been authenticated."); + expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); }); }); }); From 99f2e9a1f536afa862390dae003fd86a34a7ad54 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:29:41 -0600 Subject: [PATCH 165/174] use setFixtures instead of fixture.set --- .../vue_pagination/pagination_spec.js.es6 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6 index 1a7f2bb5fb8..efb11211ce2 100644 --- a/spec/javascripts/vue_pagination/pagination_spec.js.es6 +++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6 @@ -1,7 +1,6 @@ //= require vue //= require lib/utils/common_utils //= require vue_pagination/index -/* global fixture, gl */ describe('Pagination component', () => { let component; @@ -17,7 +16,7 @@ describe('Pagination component', () => { }; it('should render and start at page 1', () => { - fixture.set('<div class="test-pagination-container"></div>'); + setFixtures('<div class="test-pagination-container"></div>'); component = new window.gl.VueGlPagination({ el: document.querySelector('.test-pagination-container'), @@ -40,7 +39,7 @@ describe('Pagination component', () => { }); it('should go to the previous page', () => { - fixture.set('<div class="test-pagination-container"></div>'); + setFixtures('<div class="test-pagination-container"></div>'); component = new window.gl.VueGlPagination({ el: document.querySelector('.test-pagination-container'), @@ -61,7 +60,7 @@ describe('Pagination component', () => { }); it('should go to the next page', () => { - fixture.set('<div class="test-pagination-container"></div>'); + setFixtures('<div class="test-pagination-container"></div>'); component = new window.gl.VueGlPagination({ el: document.querySelector('.test-pagination-container'), @@ -82,7 +81,7 @@ describe('Pagination component', () => { }); it('should go to the last page', () => { - fixture.set('<div class="test-pagination-container"></div>'); + setFixtures('<div class="test-pagination-container"></div>'); component = new window.gl.VueGlPagination({ el: document.querySelector('.test-pagination-container'), @@ -103,7 +102,7 @@ describe('Pagination component', () => { }); it('should go to the first page', () => { - fixture.set('<div class="test-pagination-container"></div>'); + setFixtures('<div class="test-pagination-container"></div>'); component = new window.gl.VueGlPagination({ el: document.querySelector('.test-pagination-container'), @@ -124,7 +123,7 @@ describe('Pagination component', () => { }); it('should do nothing', () => { - fixture.set('<div class="test-pagination-container"></div>'); + setFixtures('<div class="test-pagination-container"></div>'); component = new window.gl.VueGlPagination({ el: document.querySelector('.test-pagination-container'), From b00f53bea1dc68756f19d8bbfe4682d395e85280 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 13:31:30 -0600 Subject: [PATCH 166/174] fix relative paths to xterm.js within fit.js --- vendor/assets/javascripts/xterm/fit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/assets/javascripts/xterm/fit.js b/vendor/assets/javascripts/xterm/fit.js index 7e24fd9b36e..55438452cad 100644 --- a/vendor/assets/javascripts/xterm/fit.js +++ b/vendor/assets/javascripts/xterm/fit.js @@ -16,12 +16,12 @@ /* * CommonJS environment */ - module.exports = fit(require('../../xterm')); + module.exports = fit(require('./xterm')); } else if (typeof define == 'function') { /* * Require.js is available */ - define(['../../xterm'], fit); + define(['./xterm'], fit); } else { /* * Plain browser environment From 4be73c9fe06104c425dc0e860c9c28225a51531a Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 2 Feb 2017 14:12:16 -0600 Subject: [PATCH 167/174] remove dateFormat global exception --- app/assets/javascripts/users/calendar.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index e0e40ad3adb..6e40dfdf3d8 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -1,6 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */ /* global d3 */ -/* global dateFormat */ (function() { var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; From d9e9ad2211c06159914e0183e4842abc16e5666f Mon Sep 17 00:00:00 2001 From: Horacio Sanson <horacio@allm.net> Date: Mon, 16 Jan 2017 09:07:02 +0900 Subject: [PATCH 168/174] PlantUML support for Markdown Allow rendering of PlantUML diagrams in Markdown documents using fenced blocks: ```plantuml Bob -> Sara : Hello Sara -> Bob : Go away ``` Closes: #4048 --- Gemfile | 2 +- Gemfile.lock | 4 +- changelogs/unreleased/markdown-plantuml.yml | 4 ++ config/initializers/plantuml_lexer.rb | 2 + doc/administration/integration/plantuml.md | 18 ++++++--- lib/banzai/filter/plantuml_filter.rb | 39 +++++++++++++++++++ lib/banzai/pipeline/gfm_pipeline.rb | 1 + lib/rouge/lexers/plantuml.rb | 21 ++++++++++ .../lib/banzai/filter/plantuml_filter_spec.rb | 32 +++++++++++++++ 9 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/markdown-plantuml.yml create mode 100644 config/initializers/plantuml_lexer.rb create mode 100644 lib/banzai/filter/plantuml_filter.rb create mode 100644 lib/rouge/lexers/plantuml.rb create mode 100644 spec/lib/banzai/filter/plantuml_filter_spec.rb diff --git a/Gemfile b/Gemfile index dd7c93c5a75..479df411881 100644 --- a/Gemfile +++ b/Gemfile @@ -109,7 +109,7 @@ gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' -gem 'asciidoctor-plantuml', '0.0.6' +gem 'asciidoctor-plantuml', '0.0.7' gem 'rouge', '~> 2.0' gem 'truncato', '~> 0.7.8' diff --git a/Gemfile.lock b/Gemfile.lock index 3b207d19d1f..f6b889dcca4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -54,7 +54,7 @@ GEM faraday_middleware-multi_json (~> 0.0) oauth2 (~> 1.0) asciidoctor (1.5.3) - asciidoctor-plantuml (0.0.6) + asciidoctor-plantuml (0.0.7) asciidoctor (~> 1.5) ast (2.3.0) attr_encrypted (3.0.3) @@ -841,7 +841,7 @@ DEPENDENCIES allocations (~> 1.0) asana (~> 0.4.0) asciidoctor (~> 1.5.2) - asciidoctor-plantuml (= 0.0.6) + asciidoctor-plantuml (= 0.0.7) attr_encrypted (~> 3.0.0) awesome_print (~> 1.2.0) babosa (~> 1.0.2) diff --git a/changelogs/unreleased/markdown-plantuml.yml b/changelogs/unreleased/markdown-plantuml.yml new file mode 100644 index 00000000000..c855f0cbcf7 --- /dev/null +++ b/changelogs/unreleased/markdown-plantuml.yml @@ -0,0 +1,4 @@ +--- +title: PlantUML support for Markdown +merge_request: 8588 +author: Horacio Sanson diff --git a/config/initializers/plantuml_lexer.rb b/config/initializers/plantuml_lexer.rb new file mode 100644 index 00000000000..e8a77b146fa --- /dev/null +++ b/config/initializers/plantuml_lexer.rb @@ -0,0 +1,2 @@ +# Touch the lexers so it is registered with Rouge +Rouge::Lexers::Plantuml diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md index e5cf592e0a6..6515b1a264a 100644 --- a/doc/administration/integration/plantuml.md +++ b/doc/administration/integration/plantuml.md @@ -3,8 +3,8 @@ > [Introduced][ce-7810] in GitLab 8.16. When [PlantUML](http://plantuml.com) integration is enabled and configured in -GitLab we are able to create simple diagrams in AsciiDoc documents created in -snippets, wikis, and repos. +GitLab we are able to create simple diagrams in AsciiDoc and Markdown documents +created in snippets, wikis, and repos. ## PlantUML Server @@ -54,7 +54,7 @@ that, login with an Admin account and do following: ## Creating Diagrams With PlantUML integration enabled and configured, we can start adding diagrams to -our AsciiDoc snippets, wikis and repos using blocks: +our AsciiDoc snippets, wikis and repos using delimited blocks: ``` [plantuml, format="png", id="myDiagram", width="200px"] @@ -64,7 +64,14 @@ Alice -> Bob : Go Away -- ``` -The above block will be converted to an HTML img tag with source pointing to the +And in Markdown using fenced code blocks: + + ```plantuml + Bob -> Alice : hello + Alice -> Bob : Go Away + ``` + +The above blocks will be converted to an HTML img tag with source pointing to the PlantUML instance. If the PlantUML server is correctly configured, this should render a nice diagram instead of the block: @@ -77,7 +84,7 @@ Inside the block you can add any of the supported diagrams by PlantUML such as and [Object](http://plantuml.com/object-diagram) diagrams. You do not need to use the PlantUML diagram delimiters `@startuml`/`@enduml` as these are replaced by the AsciiDoc `plantuml` block. -Some parameters can be added to the block definition: +Some parameters can be added to the AsciiDoc block definition: - *format*: Can be either `png` or `svg`. Note that `svg` is not supported by all browsers so use with care. The default is `png`. @@ -85,3 +92,4 @@ Some parameters can be added to the block definition: - *width*: Width attribute added to the img tag. - *height*: Height attribute added to the img tag. +Markdown does not support any parameters and will always use PNG format. diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb new file mode 100644 index 00000000000..e194cf59275 --- /dev/null +++ b/lib/banzai/filter/plantuml_filter.rb @@ -0,0 +1,39 @@ +require "nokogiri" +require "asciidoctor-plantuml/plantuml" + +module Banzai + module Filter + # HTML that replaces all `code plantuml` tags with PlantUML img tags. + # + class PlantumlFilter < HTML::Pipeline::Filter + def call + return doc unless doc.at('pre.plantuml') and settings.plantuml_enabled + + plantuml_setup + + doc.css('pre.plantuml').each do |el| + img_tag = Nokogiri::HTML::DocumentFragment.parse( + Asciidoctor::PlantUml::Processor.plantuml_content(el.content, {})) + el.replace img_tag + end + + doc + end + + private + + def settings + ApplicationSetting.current || ApplicationSetting.create_from_defaults + end + + def plantuml_setup + Asciidoctor::PlantUml.configure do |conf| + conf.url = settings.plantuml_url + conf.png_enable = settings.plantuml_enabled + conf.svg_enable = false + conf.txt_enable = false + end + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index ac95a79009b..b25d6f18d59 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -10,6 +10,7 @@ module Banzai def self.filters @filters ||= FilterArray[ Filter::SyntaxHighlightFilter, + Filter::PlantumlFilter, Filter::SanitizationFilter, Filter::MathFilter, diff --git a/lib/rouge/lexers/plantuml.rb b/lib/rouge/lexers/plantuml.rb new file mode 100644 index 00000000000..7d5700b7f6d --- /dev/null +++ b/lib/rouge/lexers/plantuml.rb @@ -0,0 +1,21 @@ +module Rouge + module Lexers + class Plantuml < Lexer + title "A passthrough lexer used for PlantUML input" + desc "A boring lexer that doesn't highlight anything" + + tag 'plantuml' + mimetypes 'text/plain' + + default_options token: 'Text' + + def token + @token ||= Token[option :token] + end + + def stream_tokens(string, &b) + yield self.token, string + end + end + end +end diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb new file mode 100644 index 00000000000..f85a5dcbd8b --- /dev/null +++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Banzai::Filter::PlantumlFilter, lib: true do + include FilterSpecHelper + + it 'should replace plantuml pre tag with img tag' do + stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") + input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>' + output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>' + doc = filter(input) + + expect(doc.to_s).to eq output + end + + it 'should not replace plantuml pre tag with img tag if disabled' do + stub_application_setting(plantuml_enabled: false) + input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>' + output = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre></pre></pre>' + doc = filter(input) + + expect(doc.to_s).to eq output + end + + it 'should not replace plantuml pre tag with img tag if url is invalid' do + stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid") + input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>' + output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> PlantUML Error: cannot connect to PlantUML server at "invalid"</pre></div></div>' + doc = filter(input) + + expect(doc.to_s).to eq output + end +end From a0586dbc165cc09422412149712a218938137308 Mon Sep 17 00:00:00 2001 From: Adam Pahlevi <adam.pahlevi@gmail.com> Date: Fri, 3 Feb 2017 06:43:19 +0700 Subject: [PATCH 169/174] replace `find_with_namespace` with `find_by_full_path` add complete changelog for !8949 --- app/controllers/admin/projects_controller.rb | 2 +- app/controllers/admin/runner_projects_controller.rb | 2 +- app/controllers/projects/application_controller.rb | 2 +- app/controllers/projects/git_http_client_controller.rb | 2 +- app/controllers/projects/uploads_controller.rb | 2 +- app/helpers/application_helper.rb | 2 +- app/models/project.rb | 6 +----- .../auth/container_registry_authentication_service.rb | 2 +- changelogs/unreleased/fwn-to-find-by-full-path.yml | 4 ++++ db/fixtures/development/10_merge_requests.rb | 2 +- lib/api/helpers.rb | 2 +- lib/api/helpers/internal_helpers.rb | 4 ++-- lib/banzai/cross_project_reference.rb | 2 +- lib/constraints/project_url_constrainer.rb | 2 +- lib/gitlab/email/handler/create_issue_handler.rb | 2 +- lib/gitlab/git_post_receive.rb | 4 ++-- lib/tasks/gitlab/cleanup.rake | 2 +- lib/tasks/gitlab/import.rake | 2 +- lib/tasks/gitlab/sidekiq.rake | 2 +- spec/lib/banzai/cross_project_reference_spec.rb | 2 +- spec/routing/project_routing_spec.rb | 8 ++++---- spec/workers/post_receive_spec.rb | 6 +++--- 22 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 changelogs/unreleased/fwn-to-find-by-full-path.yml diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index b09ae423096..39c8c6d8a0c 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -45,7 +45,7 @@ class Admin::ProjectsController < Admin::ApplicationController protected def project - @project = Project.find_with_namespace( + @project = Project.find_by_full_path( [params[:namespace_id], '/', params[:id]].join('') ) @project || render_404 diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb index bc65dcc33d3..70ac6a75434 100644 --- a/app/controllers/admin/runner_projects_controller.rb +++ b/app/controllers/admin/runner_projects_controller.rb @@ -24,7 +24,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController private def project - @project = Project.find_with_namespace( + @project = Project.find_by_full_path( [params[:namespace_id], '/', params[:project_id]].join('') ) @project || render_404 diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index b2ff36f6538..ba523b190bf 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -24,7 +24,7 @@ class Projects::ApplicationController < ApplicationController end project_path = "#{namespace}/#{id}" - @project = Project.find_with_namespace(project_path) + @project = Project.find_by_full_path(project_path) if can?(current_user, :read_project, @project) && !@project.pending_delete? if @project.path_with_namespace != project_path diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 70845617d3c..216c158e41e 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -79,7 +79,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController if project_id.blank? @project = nil else - @project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}") + @project = Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}") end end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index e617be8f9fb..50ba33ed570 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -36,7 +36,7 @@ class Projects::UploadsController < Projects::ApplicationController namespace = params[:namespace_id] id = params[:project_id] - file_project = Project.find_with_namespace("#{namespace}/#{id}") + file_project = Project.find_by_full_path("#{namespace}/#{id}") if file_project.nil? @uploader = nil diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a112928c6de..bee323993a0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -37,7 +37,7 @@ module ApplicationHelper if project_id.is_a?(Project) project_id else - Project.find_with_namespace(project_id) + Project.find_by_full_path(project_id) end if project.avatar_url diff --git a/app/models/project.rb b/app/models/project.rb index 59faf35e051..c3eced65e28 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -369,10 +369,6 @@ class Project < ActiveRecord::Base def group_ids joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id) end - - # Add alias for Routable method for compatibility with old code. - # In future all calls `find_with_namespace` should be replaced with `find_by_full_path` - alias_method :find_with_namespace, :find_by_full_path end def lfs_enabled? @@ -1345,6 +1341,6 @@ class Project < ActiveRecord::Base def pending_delete_twin return false unless path - Project.unscoped.where(pending_delete: true).find_with_namespace(path_with_namespace) + Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace) end end diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index c00c5aebf57..5cb7a86a5ee 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -61,7 +61,7 @@ module Auth end def process_repository_access(type, name, actions) - requested_project = Project.find_with_namespace(name) + requested_project = Project.find_by_full_path(name) return unless requested_project actions = actions.select do |action| diff --git a/changelogs/unreleased/fwn-to-find-by-full-path.yml b/changelogs/unreleased/fwn-to-find-by-full-path.yml new file mode 100644 index 00000000000..1427e4e7624 --- /dev/null +++ b/changelogs/unreleased/fwn-to-find-by-full-path.yml @@ -0,0 +1,4 @@ +--- +title: replace `find_with_namespace` with `find_by_full_path` +merge_request: 8949 +author: Adam Pahlevi diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index c04afe97277..c304e0706dc 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -26,7 +26,7 @@ Gitlab::Seeder.quiet do end end - project = Project.find_with_namespace('gitlab-org/gitlab-test') + project = Project.find_by_full_path('gitlab-org/gitlab-test') params = { source_branch: 'feature', diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index a1d7b323f4f..eb5b947172a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -45,7 +45,7 @@ module API if id =~ /^\d+$/ Project.find_by(id: id) else - Project.find_with_namespace(id) + Project.find_by_full_path(id) end end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index e8975eb57e0..080a6274957 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -30,7 +30,7 @@ module API def wiki? @wiki ||= project_path.end_with?('.wiki') && - !Project.find_with_namespace(project_path) + !Project.find_by_full_path(project_path) end def project @@ -41,7 +41,7 @@ module API # the wiki repository as well. project_path.chomp!('.wiki') if wiki? - Project.find_with_namespace(project_path) + Project.find_by_full_path(project_path) end end diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb index 0257848b6bc..e2b57adf611 100644 --- a/lib/banzai/cross_project_reference.rb +++ b/lib/banzai/cross_project_reference.rb @@ -14,7 +14,7 @@ module Banzai def project_from_ref(ref) return context[:project] unless ref - Project.find_with_namespace(ref) + Project.find_by_full_path(ref) end end end diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb index 730b05bed97..a10b4657d7d 100644 --- a/lib/constraints/project_url_constrainer.rb +++ b/lib/constraints/project_url_constrainer.rb @@ -8,6 +8,6 @@ class ProjectUrlConstrainer return false end - Project.find_with_namespace(full_path).present? + Project.find_by_full_path(full_path).present? end end diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 127fae159d5..b8ec9138c10 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -34,7 +34,7 @@ module Gitlab end def project - @project ||= Project.find_with_namespace(project_path) + @project ||= Project.find_by_full_path(project_path) end private diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb index d32bdd86427..6babea144c7 100644 --- a/lib/gitlab/git_post_receive.rb +++ b/lib/gitlab/git_post_receive.rb @@ -30,11 +30,11 @@ module Gitlab def retrieve_project_and_type @type = :project - @project = Project.find_with_namespace(@repo_path) + @project = Project.find_by_full_path(@repo_path) if @repo_path.end_with?('.wiki') && !@project @type = :wiki - @project = Project.find_with_namespace(@repo_path.gsub(/\.wiki\z/, '')) + @project = Project.find_by_full_path(@repo_path.gsub(/\.wiki\z/, '')) end end diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index 4a696a52b4d..967f630ef20 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -58,7 +58,7 @@ namespace :gitlab do sub(%r{^/*}, ''). chomp('.git'). chomp('.wiki') - next if Project.find_with_namespace(repo_with_namespace) + next if Project.find_by_full_path(repo_with_namespace) new_path = path + move_suffix puts path.inspect + ' -> ' + new_path.inspect File.rename(path, new_path) diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index a2eca74a3c8..690899b9ff4 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -29,7 +29,7 @@ namespace :gitlab do next end - project = Project.find_with_namespace(path) + project = Project.find_by_full_path(path) if project puts " * #{project.name} (#{repo_path}) exists" diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake index 7e2a6668e59..f2e12d85045 100644 --- a/lib/tasks/gitlab/sidekiq.rake +++ b/lib/tasks/gitlab/sidekiq.rake @@ -7,7 +7,7 @@ namespace :gitlab do unless args.project.present? abort "Please specify the project you want to drop PostReceive jobs for:\n rake gitlab:sidekiq:drop_post_receive[group/project]" end - project_path = Project.find_with_namespace(args.project).repository.path_to_repo + project_path = Project.find_by_full_path(args.project).repository.path_to_repo Sidekiq.redis do |redis| unless redis.exists(QUEUE) diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb index 81b9a513ce3..deaabceef1c 100644 --- a/spec/lib/banzai/cross_project_reference_spec.rb +++ b/spec/lib/banzai/cross_project_reference_spec.rb @@ -24,7 +24,7 @@ describe Banzai::CrossProjectReference, lib: true do it 'returns the referenced project' do project2 = double('referenced project') - expect(Project).to receive(:find_with_namespace). + expect(Project).to receive(:find_by_full_path). with('cross/reference').and_return(project2) expect(project_from_ref('cross/reference')).to eq project2 diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 77549db2927..96889abee79 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe 'project routing' do before do - allow(Project).to receive(:find_with_namespace).and_return(false) - allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq').and_return(true) + allow(Project).to receive(:find_by_full_path).and_return(false) + allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq').and_return(true) end # Shared examples for a resource inside a Project @@ -86,13 +86,13 @@ describe 'project routing' do end context 'name with dot' do - before { allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq.keys').and_return(true) } + before { allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq.keys').and_return(true) } it { expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys') } end context 'with nested group' do - before { allow(Project).to receive(:find_with_namespace).with('gitlab/subgroup/gitlabhq').and_return(true) } + before { allow(Project).to receive(:find_by_full_path).with('gitlab/subgroup/gitlabhq').and_return(true) } it { expect(get('/gitlab/subgroup/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlabhq') } end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 984acdade36..5919b99a6ed 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -74,7 +74,7 @@ describe PostReceive do context "webhook" do it "fetches the correct project" do - expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project) + expect(Project).to receive(:find_by_full_path).with(project.path_with_namespace).and_return(project) PostReceive.new.perform(pwd(project), key_id, base64_changes) end @@ -89,7 +89,7 @@ describe PostReceive do end it "asks the project to trigger all hooks" do - allow(Project).to receive(:find_with_namespace).and_return(project) + allow(Project).to receive(:find_by_full_path).and_return(project) expect(project).to receive(:execute_hooks).twice expect(project).to receive(:execute_services).twice @@ -97,7 +97,7 @@ describe PostReceive do end it "enqueues a UpdateMergeRequestsWorker job" do - allow(Project).to receive(:find_with_namespace).and_return(project) + allow(Project).to receive(:find_by_full_path).and_return(project) expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args) PostReceive.new.perform(pwd(project), key_id, base64_changes) From b5e2422939440cb2015558f26f9d361a569f2164 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Wed, 25 Jan 2017 15:51:11 +0000 Subject: [PATCH 170/174] Change "Build" to "Job" in builds show page header and sidebar Change "Builds" to "Job" in Builds table Change "Get started with Builds" to "Get started with CI/CD Pipelines" in builds list view Change "Build" to "Jobs" in builds show view --- app/views/projects/builds/_header.html.haml | 2 +- app/views/projects/builds/_sidebar.html.haml | 8 ++++---- app/views/projects/builds/_table.html.haml | 4 ++-- app/views/projects/builds/index.html.haml | 4 ++-- app/views/projects/builds/show.html.haml | 14 +++++++------- app/views/projects/pipelines/_head.html.haml | 2 +- app/views/projects/pipelines/_with_tabs.html.haml | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index 736b485bf06..a904bce065d 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,7 +1,7 @@ .content-block.build-header .header-content = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false - Build + Job %strong.js-build-id ##{@build.id} in pipeline = link_to pipeline_path(@build.pipeline) do diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 37bf085130a..65869538ab2 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -2,7 +2,7 @@ %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default - Build + Job %strong ##{@build.id} %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" } = icon('angle-double-right') @@ -17,7 +17,7 @@ - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) .block{ class: ("block-first" if !@build.coverage) } .title - Build artifacts + Job artifacts - if @build.artifacts_expired? %p.build-detail-row The artifacts were removed @@ -42,7 +42,7 @@ .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) } .title - Build details + Job details - if can?(current_user, :update_build, @build) && @build.retryable? = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post - if @build.merge_request @@ -136,4 +136,4 @@ - else = build.id - if build.retried? - %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Build was retried' } + %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index 028664f5bba..acfdb250aff 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -2,14 +2,14 @@ - if builds.blank? %div - .nothing-here-block No builds to show + .nothing-here-block No jobs to show - else .table-holder %table.table.ci-table.builds-page %thead %tr %th Status - %th Build + %th Job %th Pipeline - if admin %th Project diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index c623e39b21f..5ffc0e20d10 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- page_title "Builds" +- page_title "Jobs" = render "projects/pipelines/head" %div{ class: container_class } @@ -14,7 +14,7 @@ data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - unless @repository.gitlab_ci_yml - = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info' + = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index c613e473e4c..351f9c9067f 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- page_title "#{@build.name} (##{@build.id})", "Builds" +- page_title "#{@build.name} (##{@build.id})", "Jobs" - trace_with_state = @build.trace_with_state = render "projects/pipelines/head", build_subnav: true @@ -37,14 +37,14 @@ - environment = environment_for_build(@build.project, @build) - if @build.success? && @build.last_deployment.present? - if @build.last_deployment.last? - This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}. + This job is the most recent deployment to #{environment_link_for_build(@build.project, @build)}. - else - This build is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}. + This job is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}. View the most recent deployment #{deployment_link(environment.last_deployment)}. - elsif @build.complete? && !@build.success? - The deployment of this build to #{environment_link_for_build(@build.project, @build)} did not succeed. + The deployment of this job to #{environment_link_for_build(@build.project, @build)} did not succeed. - else - This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} + This job is creating a deployment to #{environment_link_for_build(@build.project, @build)} - if environment.try(:last_deployment) and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')} @@ -52,9 +52,9 @@ - if @build.erased? .erased.alert.alert-warning - if @build.erased_by_user? - Build has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)} + Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)} - else - Build has been erased #{time_ago_with_tooltip(@build.erased_at)} + Job has been erased #{time_ago_with_tooltip(@build.erased_at)} - else #js-build-scroll.scroll-controls .scroll-step diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index b10dd47709f..2f766a828a9 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -13,7 +13,7 @@ = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do %span - Builds + Jobs - if project_nav_tab? :environments = nav_link(controller: %w(environments)) do diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 88af41aa835..53067cdcba4 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -5,7 +5,7 @@ Pipeline %li.js-builds-tab-link = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do - Builds + Jobs %span.badge.js-builds-counter= pipeline.statuses.count @@ -33,7 +33,7 @@ %thead %tr %th Status - %th Build ID + %th Job ID %th Name %th - if pipeline.project.build_coverage_enabled? From 5b0f492b0de31e7f608527a85da9e28f9f92276f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Wed, 25 Jan 2017 16:14:58 +0000 Subject: [PATCH 171/174] Adds changelog entry Replace "builds" in project settings Replace "builds" in admin area --- app/views/admin/builds/index.html.haml | 2 +- app/views/admin/dashboard/_head.html.haml | 4 ++-- app/views/admin/runners/index.html.haml | 10 +++++----- app/views/admin/runners/show.html.haml | 8 ++++---- .../projects/_merge_request_merge_settings.html.haml | 4 ++-- app/views/projects/edit.html.haml | 4 ++-- app/views/projects/graphs/ci/_builds.haml | 8 ++++---- app/views/projects/pipelines_settings/show.html.haml | 2 +- app/views/projects/runners/index.html.haml | 6 +++--- app/views/projects/triggers/index.html.haml | 2 +- app/views/shared/web_hooks/_form.html.haml | 2 +- changelogs/unreleased/17662-rename-builds.yml | 4 ++++ 12 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 changelogs/unreleased/17662-rename-builds.yml diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 5e3f105d41f..66d633119c2 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -12,7 +12,7 @@ = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post .row-content-block.second-block - #{(@scope || 'all').capitalize} builds + #{(@scope || 'all').capitalize} jobs %ul.content-list.builds-content-list.admin-builds-table = render "projects/builds/table", builds: @builds, admin: true diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml index b5f96363230..7893c1dee97 100644 --- a/app/views/admin/dashboard/_head.html.haml +++ b/app/views/admin/dashboard/_head.html.haml @@ -20,9 +20,9 @@ %span Groups = nav_link path: 'builds#index' do - = link_to admin_builds_path, title: 'Builds' do + = link_to admin_builds_path, title: 'Jobs' do %span - Builds + Jobs = nav_link path: ['runners#index', 'runners#show'] do = link_to admin_runners_path, title: 'Runners' do %span diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 124f970524e..721bc77cc2f 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -26,7 +26,7 @@ .bs-callout %p - A 'Runner' is a process which runs a build. + A 'Runner' is a process which runs a job. You can setup as many Runners as you need. %br Runners can be placed on separate users, servers, even on your local machine. @@ -37,16 +37,16 @@ %ul %li %span.label.label-success shared - \- Runner runs builds from all unassigned projects + \- Runner runs jobs from all unassigned projects %li %span.label.label-info specific - \- Runner runs builds from assigned projects + \- Runner runs jobs from assigned projects %li %span.label.label-warning locked \- Runner cannot be assigned to other projects %li %span.label.label-danger paused - \- Runner will not receive any new builds + \- Runner will not receive any new jobs .append-bottom-20.clearfix .pull-left @@ -68,7 +68,7 @@ %th Runner token %th Description %th Projects - %th Builds + %th Jobs %th Tags %th Last contact %th diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 39e103e3062..dc4116e1ce0 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -11,13 +11,13 @@ - if @runner.shared? .bs-callout.bs-callout-success - %h4 This Runner will process builds from ALL UNASSIGNED projects + %h4 This Runner will process jobs from ALL UNASSIGNED projects %p If you want Runners to build only specific projects, enable them in the table below. Keep in mind that this is a one way transition. - else .bs-callout.bs-callout-info - %h4 This Runner will process builds only from ASSIGNED projects + %h4 This Runner will process jobs only from ASSIGNED projects %p You can't make this a shared Runner. %hr @@ -70,11 +70,11 @@ = paginate @projects, theme: "gitlab" .col-md-6 - %h4 Recent builds served by this Runner + %h4 Recent jobs served by this Runner %table.table.ci-table.runner-builds %thead %tr - %th Build + %th Job %th Status %th Project %th Commit diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml index 1a1327fb53c..27d25a6b682 100644 --- a/app/views/projects/_merge_request_merge_settings.html.haml +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -4,10 +4,10 @@ .checkbox.builds-feature = form.label :only_allow_merge_if_build_succeeds do = form.check_box :only_allow_merge_if_build_succeeds - %strong Only allow merge requests to be merged if the build succeeds + %strong Only allow merge requests to be merged if the pipeline succeeds %br %span.descr - Builds need to be configured to enable this feature. + Pipelines need to be configured to enable this feature. = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds') .checkbox = form.label :only_allow_merge_if_all_discussions_are_resolved do diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 4a0ce995165..7a2dacdb1e7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -63,7 +63,7 @@ .row .col-md-9.project-feature.nested - = feature_fields.label :builds_access_level, "Builds", class: 'label-light' + = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light' %span.help-block Submit, test and deploy your changes before merge .col-md-3 = project_feature_access_select(:builds_access_level) @@ -180,7 +180,7 @@ %p The following items will NOT be exported: %ul - %li Build traces and artifacts + %li Job traces and artifacts %li LFS objects %li Container registry images %li CI variables diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml index 431657c4dcb..b6f453b9736 100644 --- a/app/views/projects/graphs/ci/_builds.haml +++ b/app/views/projects/graphs/ci/_builds.haml @@ -1,4 +1,4 @@ -%h4 Build charts +%h4 Pipelines charts %p   %span.cgreen @@ -11,19 +11,19 @@ .prepend-top-default %p.light - Builds for last week + Jobs for last week (#{date_from_to(Date.today - 7.days, Date.today)}) %canvas#weekChart{ height: 200 } .prepend-top-default %p.light - Builds for last month + Jobs for last month (#{date_from_to(Date.today - 30.days, Date.today)}) %canvas#monthChart{ height: 200 } .prepend-top-default %p.light - Builds for last year + Jobs for last year %canvas#yearChart.padded{ height: 250 } - [:week, :month, :year].each do |scope| diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index 1f698558bce..18328c67f02 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -66,7 +66,7 @@ %span.input-group-addon / %p.help-block A regular expression that will be used to find the test coverage - output in the build trace. Leave blank to disable + output in the job trace. Leave blank to disable = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing') .bs-callout.bs-callout-info %p Below are examples of regex for existing tools: diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml index 92957470070..9d41b18b934 100644 --- a/app/views/projects/runners/index.html.haml +++ b/app/views/projects/runners/index.html.haml @@ -12,14 +12,14 @@ %ul %li %span.label.label-success active - \- Runner is active and can process any new builds + \- Runner is active and can process any new jobs %li %span.label.label-danger paused - \- Runner is paused and will not receive any new builds + \- Runner is paused and will not receive any new jobs %hr -%p.lead To start serving your builds you can either add specific Runners to your project or use shared Runners +%p.lead To start serving your jobs you can either add specific Runners to your project or use shared Runners .row .col-sm-6 = render 'specific_runners' diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml index 6e5dd1b196d..10243fe9fd0 100644 --- a/app/views/projects/triggers/index.html.haml +++ b/app/views/projects/triggers/index.html.haml @@ -86,7 +86,7 @@ :plain #{builds_trigger_url(@project.id, ref: 'REF_NAME')}?token=TOKEN %h5.prepend-top-default - Pass build variables + Pass job variables %p.light Add diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index 13586a5a12a..3a1a6a8fb7a 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -68,7 +68,7 @@ = f.label :build_events, class: 'list-label' do %strong Build events %p.light - This URL will be triggered when the build status changes + This URL will be triggered when the job status changes %li = f.check_box :pipeline_events, class: 'pull-left' .prepend-left-20 diff --git a/changelogs/unreleased/17662-rename-builds.yml b/changelogs/unreleased/17662-rename-builds.yml new file mode 100644 index 00000000000..12f2998d1c8 --- /dev/null +++ b/changelogs/unreleased/17662-rename-builds.yml @@ -0,0 +1,4 @@ +--- +title: Rename Builds to Pipelines, CI/CD Pipelines, or Jobs everywhere +merge_request: 8787 +author: From c5f5ce8807bf7cbf81b43d0caf1df089d39b880e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Thu, 26 Jan 2017 11:52:58 +0000 Subject: [PATCH 172/174] Fix broken tests Rename Build to Job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace "Builds" by "Jobs" and fix broken specs Replace "Builds" by "Jobs" Fix broken spinach test Fix broken test Remove `Ë™` at the beginning of the file Fix broken spinach test Fix broken tests Changes after review --- .../components/environment.js.es6 | 2 +- .../pipeline_actions.js.es6 | 4 +- .../application_settings/_form.html.haml | 2 +- app/views/help/_shortcuts.html.haml | 2 +- app/views/layouts/nav/_project.html.haml | 4 +- app/views/notify/build_fail_email.html.haml | 4 +- app/views/notify/build_fail_email.text.erb | 2 +- .../notify/build_success_email.html.haml | 4 +- app/views/notify/build_success_email.text.erb | 2 +- .../notify/links/ci/builds/_build.text.erb | 2 +- .../_generic_commit_status.text.erb | 2 +- .../projects/_customize_workflow.html.haml | 2 +- app/views/projects/artifacts/browse.html.haml | 2 +- app/views/projects/builds/_header.html.haml | 2 +- app/views/projects/builds/_sidebar.html.haml | 2 +- app/views/projects/builds/show.html.haml | 6 +- app/views/projects/ci/builds/_build.html.haml | 4 +- .../projects/ci/pipelines/_pipeline.html.haml | 4 +- app/views/projects/commit/_pipeline.html.haml | 4 +- .../projects/environments/show.html.haml | 2 +- .../merge_requests/widget/_heading.html.haml | 2 +- .../merge_requests/widget/_show.html.haml | 8 +-- .../widget/open/_build_failed.html.haml | 4 +- app/views/projects/pipelines/_head.html.haml | 2 +- app/views/projects/pipelines/_info.html.haml | 2 +- app/views/projects/runners/_form.html.haml | 2 +- app/views/projects/runners/index.html.haml | 2 +- app/views/projects/triggers/index.html.haml | 4 +- .../projects/variables/_content.html.haml | 2 +- app/views/shared/web_hooks/_form.html.haml | 2 +- features/steps/project/builds/summary.rb | 2 +- features/steps/project/graph.rb | 6 +- features/steps/shared/builds.rb | 4 +- lib/api/builds.rb | 2 +- spec/features/admin/admin_builds_spec.rb | 34 +++++------ ...only_allow_merge_if_build_succeeds_spec.rb | 4 +- spec/features/projects/builds_spec.rb | 44 +++++++------- .../projects/pipelines/pipeline_spec.rb | 42 +++++++------- .../settings/merge_requests_settings_spec.rb | 18 +++--- .../fixtures/environments/table.html.haml | 2 +- spec/models/ci/build_spec.rb | 4 +- spec/requests/api/builds_spec.rb | 58 +++++++++---------- spec/requests/ci/api/builds_spec.rb | 2 +- .../projects/builds/show.html.haml_spec.rb | 40 ++++++------- 44 files changed, 175 insertions(+), 175 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 971be04e2d2..558828e1bc9 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -180,7 +180,7 @@ <tr> <th class="environments-name">Environment</th> <th class="environments-deploy">Last deployment</th> - <th class="environments-build">Build</th> + <th class="environments-build">Job</th> <th class="environments-commit">Commit</th> <th class="environments-date">Updated</th> <th class="hidden-xs environments-actions"></th> diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index a7176e27ea1..01f8b6519a4 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -26,9 +26,9 @@ v-if='actions' class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions" data-toggle="dropdown" - title="Manual build" + title="Manual job" data-placement="top" - aria-label="Manual build" + aria-label="Manual job" > <span v-html='svgs.iconPlay' aria-hidden="true"></span> <i class="fa fa-caret-down" aria-hidden="true"></i> diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 558bbe07b16..e7701d75a6e 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -204,7 +204,7 @@ .col-sm-10 = f.number_field :max_artifacts_size, class: 'form-control' .help-block - Set the maximum file size each build's artifacts can have + Set the maximum file size each jobs's artifacts can have = link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size") - if Gitlab.config.registry.enabled diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index b74cc822295..da2df0d8080 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -143,7 +143,7 @@ .key g .key b %td - Go to builds + Go to jobs %tr %td.shortcut .key g diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index a8bbd67de80..7883823b21e 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -96,8 +96,8 @@ -# Shortcut to builds page - if project_nav_tab? :builds %li.hidden - = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do - Builds + = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do + Jobs -# Shortcut to commits page - if project_nav_tab? :commits diff --git a/app/views/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml index a744c4be9d6..060b50ffc69 100644 --- a/app/views/notify/build_fail_email.html.haml +++ b/app/views/notify/build_fail_email.html.haml @@ -1,6 +1,6 @@ - content_for :header do %h1{ style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" } - GitLab (build failed) + GitLab (job failed) %h3 Project: @@ -21,4 +21,4 @@ Message: #{@build.pipeline.git_commit_message} %p - Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)} + Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)} diff --git a/app/views/notify/build_fail_email.text.erb b/app/views/notify/build_fail_email.text.erb index 9d497983498..2a94688a6b0 100644 --- a/app/views/notify/build_fail_email.text.erb +++ b/app/views/notify/build_fail_email.text.erb @@ -1,4 +1,4 @@ -Build failed for <%= @project.name %> +Job failed for <%= @project.name %> Status: <%= @build.status %> Commit: <%= @build.pipeline.short_sha %> diff --git a/app/views/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml index 8c2e6db1426..ca0eaa96a9d 100644 --- a/app/views/notify/build_success_email.html.haml +++ b/app/views/notify/build_success_email.html.haml @@ -1,6 +1,6 @@ - content_for :header do %h1{ style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" } - GitLab (build successful) + GitLab (job successful) %h3 Project: @@ -21,4 +21,4 @@ Message: #{@build.pipeline.git_commit_message} %p - Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)} + Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)} diff --git a/app/views/notify/build_success_email.text.erb b/app/views/notify/build_success_email.text.erb index c5ed4f84861..445cd46e64f 100644 --- a/app/views/notify/build_success_email.text.erb +++ b/app/views/notify/build_success_email.text.erb @@ -1,4 +1,4 @@ -Build successful for <%= @project.name %> +Job successful for <%= @project.name %> Status: <%= @build.status %> Commit: <%= @build.pipeline.short_sha %> diff --git a/app/views/notify/links/ci/builds/_build.text.erb b/app/views/notify/links/ci/builds/_build.text.erb index f495a2e5486..741c7f344c8 100644 --- a/app/views/notify/links/ci/builds/_build.text.erb +++ b/app/views/notify/links/ci/builds/_build.text.erb @@ -1 +1 @@ -Build #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> ) +Job #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> ) diff --git a/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb b/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb index 8e89c52a1f3..af8924bad57 100644 --- a/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb +++ b/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb @@ -1 +1 @@ -Build #<%= build.id %> +Job #<%= build.id %> diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml index e2b73cee5a9..a41791f0eca 100644 --- a/app/views/projects/_customize_workflow.html.haml +++ b/app/views/projects/_customize_workflow.html.haml @@ -3,6 +3,6 @@ %h4 Customize your workflow! %p - Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production! + Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and pipelines, GitLab can help manage your workflow from idea to production! - if can?(current_user, :admin_project, @project) = link_to "Get started", edit_project_path(@project), class: "btn btn-success" diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index d0ff14e45e6..edf55d59f28 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -1,4 +1,4 @@ -- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds' +- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs' .top-block.row-content-block.clearfix .pull-right diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index a904bce065d..27e81c2bec3 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -17,6 +17,6 @@ = render "user" = time_ago_with_tooltip(@build.created_at) - if can?(current_user, :update_build, @build) && @build.retryable? - = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post + = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post %button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" } = icon('angle-double-left') diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 65869538ab2..56fc5f5e68b 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -44,7 +44,7 @@ .title Job details - if can?(current_user, :update_build, @build) && @build.retryable? - = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post + = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post - if @build.merge_request %p.build-detail-row %span.build-light-text Merge Request: diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 351f9c9067f..228dad528ab 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -12,14 +12,14 @@ .bs-callout.bs-callout-warning %p - if no_runners_for_project?(@build.project) - This build is stuck, because the project doesn't have any runners online assigned to it. + This job is stuck, because the project doesn't have any runners online assigned to it. - elsif @build.tags.any? - This build is stuck, because you don't have any active runners online with any of these tags assigned to them: + This job is stuck, because you don't have any active runners online with any of these tags assigned to them: - @build.tags.each do |tag| %span.label.label-primary = tag - else - This build is stuck, because you don't have any active runners that can run this build. + This job is stuck, because you don't have any active runners that can run this job. %br Go to diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index c1e496455d1..5ea85f9fd4c 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -32,10 +32,10 @@ = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - if build.stuck? - = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') + = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.') - if retried - = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried') + = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried') .label-container - if build.tags.any? diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 818a70f38f1..cdab1e1b1a6 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -15,7 +15,7 @@ - else %span.api.monospace API - if pipeline.latest? - %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest + %span.label.label-success.has-tooltip{ title: 'Latest job for this branch' } latest - if pipeline.triggered? %span.label.label-primary triggered - if pipeline.yaml_errors.present? @@ -78,7 +78,7 @@ .btn-group.inline - if actions.any? .btn-group - %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual build', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual build' } + %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual job', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual job' } = custom_icon('icon_play') = icon('caret-down', 'aria-hidden' => 'true') %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 08d3443b3d0..6abff6aaf95 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -13,7 +13,7 @@ Pipeline = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" with - = pluralize pipeline.statuses.count(:id), "build" + = pluralize pipeline.statuses.count(:id), "job" - if pipeline.ref for = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" @@ -44,7 +44,7 @@ %thead %tr %th Status - %th Build ID + %th Job ID %th Name %th - if pipeline.project.build_coverage_enabled? diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index f3179dce5f2..7800d6ac382 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -32,7 +32,7 @@ %tr %th ID %th Commit - %th Build + %th Job %th Created %th.hidden-xs diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 0e3af62ebc2..ae134563ead 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -21,7 +21,7 @@ .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" } = ci_icon_for_status(status) %span - CI build + CI job = ci_label_for_status(status) for - commit = @merge_request.diff_head_commit diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index f07e6b3ad54..5de59473840 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -16,13 +16,13 @@ gitlab_icon: "#{asset_path 'gitlab_logo.png'}", ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_message: { - normal: "Build {{status}} for \"{{title}}\"", - preparing: "{{status}} build for \"{{title}}\"" + normal: "Job {{status}} for \"{{title}}\"", + preparing: "{{status}} job for \"{{title}}\"" }, ci_enable: #{@project.ci_service ? "true" : "false"}, ci_title: { - preparing: "{{status}} build", - normal: "Build {{status}}" + preparing: "{{status}} job", + normal: "Job {{status}}" }, ci_sha: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.short_sha : ''}", ci_pipeline: #{@merge_request.head_pipeline.try(:id).to_json}, diff --git a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml index 14f51af5360..a18c2ad768f 100644 --- a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml +++ b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml @@ -1,6 +1,6 @@ %h4 = icon('exclamation-triangle') - The build for this merge request failed + The job for this merge request failed %p - Please retry the build or push a new commit to fix the failure. + Please retry the job or push a new commit to fix the failure. diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 2f766a828a9..721a9b6beb5 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -11,7 +11,7 @@ - if project_nav_tab? :builds = nav_link(controller: %w(builds)) do - = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do + = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do %span Jobs diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 6caa5f16dc6..a6cd2d83bd5 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -25,7 +25,7 @@ .well-segment.pipeline-info .icon-container = icon('clock-o') - = pluralize @pipeline.statuses.count(:id), "build" + = pluralize @pipeline.statuses.count(:id), "job" - if @pipeline.ref from = link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace" diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 33a9a96183c..98e72f6c547 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -5,7 +5,7 @@ .col-sm-10 .checkbox = f.check_box :active - %span.light Paused Runners don't accept new builds + %span.light Paused Runners don't accept new jobs .form-group = label :run_untagged, 'Run untagged jobs', class: 'control-label' .col-sm-10 diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml index 9d41b18b934..d6f691d9c24 100644 --- a/app/views/projects/runners/index.html.haml +++ b/app/views/projects/runners/index.html.haml @@ -2,7 +2,7 @@ .light.prepend-top-default %p - A 'Runner' is a process which runs a build. + A 'Runner' is a process which runs a job. You can setup as many Runners as you need. %br Runners can be placed on separate users, servers, and even on your local machine. diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml index 10243fe9fd0..b9c4e323430 100644 --- a/app/views/projects/triggers/index.html.haml +++ b/app/views/projects/triggers/index.html.haml @@ -67,7 +67,7 @@ In the %code .gitlab-ci.yml of another project, include the following snippet. - The project will be rebuilt at the end of the build. + The project will be rebuilt at the end of the job. %pre :plain @@ -91,7 +91,7 @@ %p.light Add %code variables[VARIABLE]=VALUE - to an API request. Variable values can be used to distinguish between triggered builds and normal builds. + to an API request. Variable values can be used to distinguish between triggered jobs and normal jobs. With cURL: diff --git a/app/views/projects/variables/_content.html.haml b/app/views/projects/variables/_content.html.haml index 0249e0c1bf1..06477aba103 100644 --- a/app/views/projects/variables/_content.html.haml +++ b/app/views/projects/variables/_content.html.haml @@ -5,4 +5,4 @@ %p So you can use them for passwords, secret keys or whatever you want. %p - The value of the variable can be visible in build log if explicitly asked to do so. + The value of the variable can be visible in job log if explicitly asked to do so. diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index 3a1a6a8fb7a..c212d1c86bf 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -66,7 +66,7 @@ = f.check_box :build_events, class: 'pull-left' .prepend-left-20 = f.label :build_events, class: 'list-label' do - %strong Build events + %strong Jobs events %p.light This URL will be triggered when the job status changes %li diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb index 374eb0b0e07..19ff92f6dc6 100644 --- a/features/steps/project/builds/summary.rb +++ b/features/steps/project/builds/summary.rb @@ -33,7 +33,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps step 'recent build summary contains information saying that build has been erased' do page.within('.erased') do - expect(page).to have_content 'Build has been erased' + expect(page).to have_content 'Job has been erased' end end diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb index 7490d2bc6e7..48ac7a98f0d 100644 --- a/features/steps/project/graph.rb +++ b/features/steps/project/graph.rb @@ -34,9 +34,9 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps step 'page should have CI graphs' do expect(page).to have_content 'Overall' - expect(page).to have_content 'Builds for last week' - expect(page).to have_content 'Builds for last month' - expect(page).to have_content 'Builds for last year' + expect(page).to have_content 'Jobs for last week' + expect(page).to have_content 'Jobs for last month' + expect(page).to have_content 'Jobs for last year' expect(page).to have_content 'Commit duration in minutes for last 30 commits' end diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb index 70e6d4836b2..d008a8a26af 100644 --- a/features/steps/shared/builds.rb +++ b/features/steps/shared/builds.rb @@ -47,7 +47,7 @@ module SharedBuilds end step 'recent build has a build trace' do - @build.trace = 'build trace' + @build.trace = 'job trace' end step 'download of build artifacts archive starts' do @@ -60,7 +60,7 @@ module SharedBuilds end step 'I see details of a build' do - expect(page).to have_content "Build ##{@build.id}" + expect(page).to have_content "Job ##{@build.id}" end step 'I see build trace' do diff --git a/lib/api/builds.rb b/lib/api/builds.rb index af61be343be..44fe0fc4a95 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -209,7 +209,7 @@ module API build = get_build!(params[:build_id]) - bad_request!("Unplayable Build") unless build.playable? + bad_request!("Unplayable Job") unless build.playable? build.play(current_user) diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb index e177059d959..9d5ce876c29 100644 --- a/spec/features/admin/admin_builds_spec.rb +++ b/spec/features/admin/admin_builds_spec.rb @@ -9,8 +9,8 @@ describe 'Admin Builds' do let(:pipeline) { create(:ci_pipeline) } context 'All tab' do - context 'when have builds' do - it 'shows all builds' do + context 'when have jobs' do + it 'shows all jobs' do create(:ci_build, pipeline: pipeline, status: :pending) create(:ci_build, pipeline: pipeline, status: :running) create(:ci_build, pipeline: pipeline, status: :success) @@ -19,26 +19,26 @@ describe 'Admin Builds' do visit admin_builds_path expect(page).to have_selector('.nav-links li.active', text: 'All') - expect(page).to have_selector('.row-content-block', text: 'All builds') + expect(page).to have_selector('.row-content-block', text: 'All jobs') expect(page.all('.build-link').size).to eq(4) expect(page).to have_link 'Cancel all' end end - context 'when have no builds' do + context 'when have no jobs' do it 'shows a message' do visit admin_builds_path expect(page).to have_selector('.nav-links li.active', text: 'All') - expect(page).to have_content 'No builds to show' + expect(page).to have_content 'No jobs to show' expect(page).not_to have_link 'Cancel all' end end end context 'Pending tab' do - context 'when have pending builds' do - it 'shows pending builds' do + context 'when have pending jobs' do + it 'shows pending jobs' do build1 = create(:ci_build, pipeline: pipeline, status: :pending) build2 = create(:ci_build, pipeline: pipeline, status: :running) build3 = create(:ci_build, pipeline: pipeline, status: :success) @@ -55,22 +55,22 @@ describe 'Admin Builds' do end end - context 'when have no builds pending' do + context 'when have no jobs pending' do it 'shows a message' do create(:ci_build, pipeline: pipeline, status: :success) visit admin_builds_path(scope: :pending) expect(page).to have_selector('.nav-links li.active', text: 'Pending') - expect(page).to have_content 'No builds to show' + expect(page).to have_content 'No jobs to show' expect(page).not_to have_link 'Cancel all' end end end context 'Running tab' do - context 'when have running builds' do - it 'shows running builds' do + context 'when have running jobs' do + it 'shows running jobs' do build1 = create(:ci_build, pipeline: pipeline, status: :running) build2 = create(:ci_build, pipeline: pipeline, status: :success) build3 = create(:ci_build, pipeline: pipeline, status: :failed) @@ -87,22 +87,22 @@ describe 'Admin Builds' do end end - context 'when have no builds running' do + context 'when have no jobs running' do it 'shows a message' do create(:ci_build, pipeline: pipeline, status: :success) visit admin_builds_path(scope: :running) expect(page).to have_selector('.nav-links li.active', text: 'Running') - expect(page).to have_content 'No builds to show' + expect(page).to have_content 'No jobs to show' expect(page).not_to have_link 'Cancel all' end end end context 'Finished tab' do - context 'when have finished builds' do - it 'shows finished builds' do + context 'when have finished jobs' do + it 'shows finished jobs' do build1 = create(:ci_build, pipeline: pipeline, status: :pending) build2 = create(:ci_build, pipeline: pipeline, status: :running) build3 = create(:ci_build, pipeline: pipeline, status: :success) @@ -117,14 +117,14 @@ describe 'Admin Builds' do end end - context 'when have no builds finished' do + context 'when have no jobs finished' do it 'shows a message' do create(:ci_build, pipeline: pipeline, status: :running) visit admin_builds_path(scope: :finished) expect(page).to have_selector('.nav-links li.active', text: 'Finished') - expect(page).to have_content 'No builds to show' + expect(page).to have_content 'No jobs to show' expect(page).to have_link 'Cancel all' end end diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 7e2907cd26f..d2f5c4afc93 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -50,7 +50,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: visit_merge_request(merge_request) expect(page).not_to have_button 'Accept Merge Request' - expect(page).to have_content('Please retry the build or push a new commit to fix the failure.') + expect(page).to have_content('Please retry the job or push a new commit to fix the failure.') end end @@ -61,7 +61,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: visit_merge_request(merge_request) expect(page).not_to have_button 'Accept Merge Request' - expect(page).to have_content('Please retry the build or push a new commit to fix the failure.') + expect(page).to have_content('Please retry the job or push a new commit to fix the failure.') end end diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index 11d27feab0b..f7e0115643e 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -27,7 +27,7 @@ feature 'Builds', :feature do visit namespace_project_builds_path(project.namespace, project, scope: :pending) end - it "shows Pending tab builds" do + it "shows Pending tab jobs" do expect(page).to have_link 'Cancel running' expect(page).to have_selector('.nav-links li.active', text: 'Pending') expect(page).to have_content build.short_sha @@ -42,7 +42,7 @@ feature 'Builds', :feature do visit namespace_project_builds_path(project.namespace, project, scope: :running) end - it "shows Running tab builds" do + it "shows Running tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'Running') expect(page).to have_link 'Cancel running' expect(page).to have_content build.short_sha @@ -57,20 +57,20 @@ feature 'Builds', :feature do visit namespace_project_builds_path(project.namespace, project, scope: :finished) end - it "shows Finished tab builds" do + it "shows Finished tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'Finished') - expect(page).to have_content 'No builds to show' + expect(page).to have_content 'No jobs to show' expect(page).to have_link 'Cancel running' end end - context "All builds" do + context "All jobs" do before do project.builds.running_or_pending.each(&:success) visit namespace_project_builds_path(project.namespace, project) end - it "shows All tab builds" do + it "shows All tab jobs" do expect(page).to have_selector('.nav-links li.active', text: 'All') expect(page).to have_content build.short_sha expect(page).to have_content build.ref @@ -98,7 +98,7 @@ feature 'Builds', :feature do end describe "GET /:project/builds/:id" do - context "Build from project" do + context "Job from project" do before do visit namespace_project_build_path(project.namespace, project, build) end @@ -111,7 +111,7 @@ feature 'Builds', :feature do end end - context "Build from other project" do + context "Job from other project" do before do visit namespace_project_build_path(project.namespace, project, build2) end @@ -149,7 +149,7 @@ feature 'Builds', :feature do context 'when expire date is defined' do let(:expire_at) { Time.now + 7.days } - context 'when user has ability to update build' do + context 'when user has ability to update job' do it 'keeps artifacts when keep button is clicked' do expect(page).to have_content 'The artifacts will be removed' @@ -160,7 +160,7 @@ feature 'Builds', :feature do end end - context 'when user does not have ability to update build' do + context 'when user does not have ability to update job' do let(:user_access_level) { :guest } it 'does not have keep button' do @@ -197,8 +197,8 @@ feature 'Builds', :feature do visit namespace_project_build_path(project.namespace, project, build) end - context 'when build has an initial trace' do - it 'loads build trace' do + context 'when job has an initial trace' do + it 'loads job trace' do expect(page).to have_content 'BUILD TRACE' build.append_trace(' and more trace', 11) @@ -242,32 +242,32 @@ feature 'Builds', :feature do end end - context 'when build starts environment' do + context 'when job starts environment' do let(:environment) { create(:environment, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) } - context 'build is successfull and has deployment' do + context 'job is successfull and has deployment' do let(:deployment) { create(:deployment) } let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) } - it 'shows a link for the build' do + it 'shows a link for the job' do visit namespace_project_build_path(project.namespace, project, build) expect(page).to have_link environment.name end end - context 'build is complete and not successfull' do + context 'job is complete and not successfull' do let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) } - it 'shows a link for the build' do + it 'shows a link for the job' do visit namespace_project_build_path(project.namespace, project, build) expect(page).to have_link environment.name end end - context 'build creates a new deployment' do + context 'job creates a new deployment' do let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } @@ -281,7 +281,7 @@ feature 'Builds', :feature do end describe "POST /:project/builds/:id/cancel" do - context "Build from project" do + context "Job from project" do before do build.run! visit namespace_project_build_path(project.namespace, project, build) @@ -295,7 +295,7 @@ feature 'Builds', :feature do end end - context "Build from other project" do + context "Job from other project" do before do build.run! visit namespace_project_build_path(project.namespace, project, build) @@ -307,13 +307,13 @@ feature 'Builds', :feature do end describe "POST /:project/builds/:id/retry" do - context "Build from project" do + context "Job from project" do before do build.run! visit namespace_project_build_path(project.namespace, project, build) click_link 'Cancel' page.within('.build-header') do - click_link 'Retry build' + click_link 'Retry job' end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 917b545e98b..0b5ccc8c515 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -91,10 +91,10 @@ describe 'Pipeline', :feature, :js do end end - it 'should be possible to retry the success build' do + it 'should be possible to retry the success job' do find('#ci-badge-build .ci-action-icon-container').trigger('click') - expect(page).not_to have_content('Retry build') + expect(page).not_to have_content('Retry job') end end @@ -113,11 +113,11 @@ describe 'Pipeline', :feature, :js do it 'should be possible to retry the failed build' do find('#ci-badge-test .ci-action-icon-container').trigger('click') - expect(page).not_to have_content('Retry build') + expect(page).not_to have_content('Retry job') end end - context 'when pipeline has manual builds' do + context 'when pipeline has manual jobs' do it 'shows the skipped icon and a play action for the manual build' do page.within('#ci-badge-manual-build') do expect(page).to have_selector('.js-ci-status-icon-manual') @@ -129,14 +129,14 @@ describe 'Pipeline', :feature, :js do end end - it 'should be possible to play the manual build' do + it 'should be possible to play the manual job' do find('#ci-badge-manual-build .ci-action-icon-container').trigger('click') - expect(page).not_to have_content('Play build') + expect(page).not_to have_content('Play job') end end - context 'when pipeline has external build' do + context 'when pipeline has external job' do it 'shows the success icon and the generic comit status build' do expect(page).to have_selector('.js-ci-status-icon-success') expect(page).to have_content('jenkins') @@ -146,12 +146,12 @@ describe 'Pipeline', :feature, :js do end context 'page tabs' do - it 'shows Pipeline and Builds tabs with link' do + it 'shows Pipeline and Jobs tabs with link' do expect(page).to have_link('Pipeline') - expect(page).to have_link('Builds') + expect(page).to have_link('Jobs') end - it 'shows counter in Builds tab' do + it 'shows counter in Jobs tab' do expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) end @@ -160,7 +160,7 @@ describe 'Pipeline', :feature, :js do end end - context 'retrying builds' do + context 'retrying jobs' do it { expect(page).not_to have_content('retried') } context 'when retrying' do @@ -170,7 +170,7 @@ describe 'Pipeline', :feature, :js do end end - context 'canceling builds' do + context 'canceling jobs' do it { expect(page).not_to have_selector('.ci-canceled') } context 'when canceling' do @@ -191,7 +191,7 @@ describe 'Pipeline', :feature, :js do visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline) end - it 'shows a list of builds' do + it 'shows a list of jobs' do expect(page).to have_content('Test') expect(page).to have_content(build_passed.id) expect(page).to have_content('Deploy') @@ -203,26 +203,26 @@ describe 'Pipeline', :feature, :js do expect(page).to have_link('Play') end - it 'shows Builds tab pane as active' do + it 'shows jobs tab pane as active' do expect(page).to have_css('#js-tab-builds.active') end context 'page tabs' do - it 'shows Pipeline and Builds tabs with link' do + it 'shows Pipeline and Jobs tabs with link' do expect(page).to have_link('Pipeline') - expect(page).to have_link('Builds') + expect(page).to have_link('Jobs') end - it 'shows counter in Builds tab' do + it 'shows counter in Jobs tab' do expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) end - it 'shows Builds tab as active' do + it 'shows Jobs tab as active' do expect(page).to have_css('li.js-builds-tab-link.active') end end - context 'retrying builds' do + context 'retrying jobs' do it { expect(page).not_to have_content('retried') } context 'when retrying' do @@ -233,7 +233,7 @@ describe 'Pipeline', :feature, :js do end end - context 'canceling builds' do + context 'canceling jobs' do it { expect(page).not_to have_selector('.ci-canceled') } context 'when canceling' do @@ -244,7 +244,7 @@ describe 'Pipeline', :feature, :js do end end - context 'playing manual build' do + context 'playing manual job' do before do within '.pipeline-holder' do click_link('Play') diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb index 4bfaa499272..034b75c2e51 100644 --- a/spec/features/projects/settings/merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -11,41 +11,41 @@ feature 'Project settings > Merge Requests', feature: true, js: true do login_as(user) end - context 'when Merge Request and Builds are initially enabled' do + context 'when Merge Request and Pipelines are initially enabled' do before do project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::ENABLED) end - context 'when Builds are initially enabled' do + context 'when Pipelines are initially enabled' do before do project.project_feature.update_attribute('builds_access_level', ProjectFeature::ENABLED) visit edit_project_path(project) end scenario 'shows the Merge Requests settings' do - expect(page).to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level" - expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved') end end - context 'when Builds are initially disabled' do + context 'when Pipelines are initially disabled' do before do project.project_feature.update_attribute('builds_access_level', ProjectFeature::DISABLED) visit edit_project_path(project) end scenario 'shows the Merge Requests settings that do not depend on Builds feature' do - expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level" - expect(page).to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') end end @@ -58,12 +58,12 @@ feature 'Project settings > Merge Requests', feature: true, js: true do end scenario 'does not show the Merge Requests settings' do - expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved') select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level" - expect(page).to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds') expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') end end diff --git a/spec/javascripts/fixtures/environments/table.html.haml b/spec/javascripts/fixtures/environments/table.html.haml index 1ea1725c561..59edc0396d2 100644 --- a/spec/javascripts/fixtures/environments/table.html.haml +++ b/spec/javascripts/fixtures/environments/table.html.haml @@ -3,7 +3,7 @@ %tr %th Environment %th Last deployment - %th Build + %th Job %th Commit %th %th diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index e20b394c525..4080092405d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -484,11 +484,11 @@ describe Ci::Build, :models do let!(:build) { create(:ci_build, :trace, :success, :artifacts) } subject { build.erased? } - context 'build has not been erased' do + context 'job has not been erased' do it { is_expected.to be_falsey } end - context 'build has been erased' do + context 'job has been erased' do before do build.erase end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index bd6e23ee769..f197fadebab 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -86,7 +86,7 @@ describe API::Builds, api: true do context 'when commit exists in repository' do context 'when user is authorized' do - context 'when pipeline has builds' do + context 'when pipeline has jobs' do before do create(:ci_pipeline, project: project, sha: project.commit.id) create(:ci_build, pipeline: pipeline) @@ -95,7 +95,7 @@ describe API::Builds, api: true do get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user) end - it 'returns project builds for specific commit' do + it 'returns project jobs for specific commit' do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq 2 @@ -111,7 +111,7 @@ describe API::Builds, api: true do end end - context 'when pipeline has no builds' do + context 'when pipeline has no jobs' do before do branch_head = project.commit('feature').id get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user) @@ -133,7 +133,7 @@ describe API::Builds, api: true do get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil) end - it 'does not return project builds' do + it 'does not return project jobs' do expect(response).to have_http_status(401) expect(json_response.except('message')).to be_empty end @@ -147,7 +147,7 @@ describe API::Builds, api: true do end context 'authorized user' do - it 'returns specific build data' do + it 'returns specific job data' do expect(response).to have_http_status(200) expect(json_response['name']).to eq('test') end @@ -165,7 +165,7 @@ describe API::Builds, api: true do context 'unauthorized user' do let(:api_user) { nil } - it 'does not return specific build data' do + it 'does not return specific job data' do expect(response).to have_http_status(401) end end @@ -176,7 +176,7 @@ describe API::Builds, api: true do get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) end - context 'build with artifacts' do + context 'job with artifacts' do let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } context 'authorized user' do @@ -185,7 +185,7 @@ describe API::Builds, api: true do 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } end - it 'returns specific build artifacts' do + it 'returns specific job artifacts' do expect(response).to have_http_status(200) expect(response.headers).to include(download_headers) end @@ -194,13 +194,13 @@ describe API::Builds, api: true do context 'unauthorized user' do let(:api_user) { nil } - it 'does not return specific build artifacts' do + it 'does not return specific job artifacts' do expect(response).to have_http_status(401) end end end - it 'does not return build artifacts if not uploaded' do + it 'does not return job artifacts if not uploaded' do expect(response).to have_http_status(404) end end @@ -241,7 +241,7 @@ describe API::Builds, api: true do end end - context 'non-existing build' do + context 'non-existing job' do shared_examples 'not found' do it { expect(response).to have_http_status(:not_found) } end @@ -254,7 +254,7 @@ describe API::Builds, api: true do it_behaves_like 'not found' end - context 'has no such build' do + context 'has no such job' do before do get path_for_ref(pipeline.ref, 'NOBUILD') end @@ -263,7 +263,7 @@ describe API::Builds, api: true do end end - context 'find proper build' do + context 'find proper job' do shared_examples 'a valid file' do let(:download_headers) do { 'Content-Transfer-Encoding' => 'binary', @@ -311,7 +311,7 @@ describe API::Builds, api: true do end context 'authorized user' do - it 'returns specific build trace' do + it 'returns specific job trace' do expect(response).to have_http_status(200) expect(response.body).to eq(build.trace) end @@ -320,7 +320,7 @@ describe API::Builds, api: true do context 'unauthorized user' do let(:api_user) { nil } - it 'does not return specific build trace' do + it 'does not return specific job trace' do expect(response).to have_http_status(401) end end @@ -333,7 +333,7 @@ describe API::Builds, api: true do context 'authorized user' do context 'user with :update_build persmission' do - it 'cancels running or pending build' do + it 'cancels running or pending job' do expect(response).to have_http_status(201) expect(project.builds.first.status).to eq('canceled') end @@ -342,7 +342,7 @@ describe API::Builds, api: true do context 'user without :update_build permission' do let(:api_user) { reporter.user } - it 'does not cancel build' do + it 'does not cancel job' do expect(response).to have_http_status(403) end end @@ -351,7 +351,7 @@ describe API::Builds, api: true do context 'unauthorized user' do let(:api_user) { nil } - it 'does not cancel build' do + it 'does not cancel job' do expect(response).to have_http_status(401) end end @@ -366,7 +366,7 @@ describe API::Builds, api: true do context 'authorized user' do context 'user with :update_build permission' do - it 'retries non-running build' do + it 'retries non-running job' do expect(response).to have_http_status(201) expect(project.builds.first.status).to eq('canceled') expect(json_response['status']).to eq('pending') @@ -376,7 +376,7 @@ describe API::Builds, api: true do context 'user without :update_build permission' do let(:api_user) { reporter.user } - it 'does not retry build' do + it 'does not retry job' do expect(response).to have_http_status(403) end end @@ -385,7 +385,7 @@ describe API::Builds, api: true do context 'unauthorized user' do let(:api_user) { nil } - it 'does not retry build' do + it 'does not retry job' do expect(response).to have_http_status(401) end end @@ -396,23 +396,23 @@ describe API::Builds, api: true do post api("/projects/#{project.id}/builds/#{build.id}/erase", user) end - context 'build is erasable' do + context 'job is erasable' do let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) } - it 'erases build content' do + it 'erases job content' do expect(response.status).to eq 201 expect(build.trace).to be_empty expect(build.artifacts_file.exists?).to be_falsy expect(build.artifacts_metadata.exists?).to be_falsy end - it 'updates build' do + it 'updates job' do expect(build.reload.erased_at).to be_truthy expect(build.reload.erased_by).to eq user end end - context 'build is not erasable' do + context 'job is not erasable' do let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) } it 'responds with forbidden' do @@ -452,20 +452,20 @@ describe API::Builds, api: true do post api("/projects/#{project.id}/builds/#{build.id}/play", user) end - context 'on an playable build' do + context 'on an playable job' do let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) } - it 'plays the build' do + it 'plays the job' do expect(response).to have_http_status 200 expect(json_response['user']['id']).to eq(user.id) expect(json_response['id']).to eq(build.id) end end - context 'on a non-playable build' do + context 'on a non-playable job' do it 'returns a status code 400, Bad Request' do expect(response).to have_http_status 400 - expect(response.body).to match("Unplayable Build") + expect(response.body).to match("Unplayable Job") end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 1cedaa4ba63..d85afdeab42 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -288,7 +288,7 @@ describe Ci::API::Builds do expect(build.reload.trace).to eq 'BUILD TRACE' end - context 'build has been erased' do + context 'job has been erased' do let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) } it 'responds with forbidden' do diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 44870cfcfb3..b6f6e7b7a2b 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -15,7 +15,7 @@ describe 'projects/builds/show', :view do allow(view).to receive(:can?).and_return(true) end - describe 'build information in header' do + describe 'job information in header' do let(:build) do create(:ci_build, :success, environment: 'staging') end @@ -28,11 +28,11 @@ describe 'projects/builds/show', :view do expect(rendered).to have_css('.ci-status.ci-success', text: 'passed') end - it 'does not render a link to the build' do + it 'does not render a link to the job' do expect(rendered).not_to have_link('passed') end - it 'shows build id' do + it 'shows job id' do expect(rendered).to have_css('.js-build-id', text: build.id) end @@ -45,8 +45,8 @@ describe 'projects/builds/show', :view do end end - describe 'environment info in build view' do - context 'build with latest deployment' do + describe 'environment info in job view' do + context 'job with latest deployment' do let(:build) do create(:ci_build, :success, environment: 'staging') end @@ -57,7 +57,7 @@ describe 'projects/builds/show', :view do end it 'shows deployment message' do - expected_text = 'This build is the most recent deployment' + expected_text = 'This job is the most recent deployment' render expect(rendered).to have_css( @@ -65,7 +65,7 @@ describe 'projects/builds/show', :view do end end - context 'build with outdated deployment' do + context 'job with outdated deployment' do let(:build) do create(:ci_build, :success, environment: 'staging', pipeline: pipeline) end @@ -87,7 +87,7 @@ describe 'projects/builds/show', :view do end it 'shows deployment message' do - expected_text = 'This build is an out-of-date deployment ' \ + expected_text = 'This job is an out-of-date deployment ' \ "to staging.\nView the most recent deployment ##{second_deployment.iid}." render @@ -95,7 +95,7 @@ describe 'projects/builds/show', :view do end end - context 'build failed to deploy' do + context 'job failed to deploy' do let(:build) do create(:ci_build, :failed, environment: 'staging', pipeline: pipeline) end @@ -105,7 +105,7 @@ describe 'projects/builds/show', :view do end it 'shows deployment message' do - expected_text = 'The deployment of this build to staging did not succeed.' + expected_text = 'The deployment of this job to staging did not succeed.' render expect(rendered).to have_css( @@ -113,7 +113,7 @@ describe 'projects/builds/show', :view do end end - context 'build will deploy' do + context 'job will deploy' do let(:build) do create(:ci_build, :running, environment: 'staging', pipeline: pipeline) end @@ -124,7 +124,7 @@ describe 'projects/builds/show', :view do end it 'shows deployment message' do - expected_text = 'This build is creating a deployment to staging' + expected_text = 'This job is creating a deployment to staging' render expect(rendered).to have_css( @@ -137,7 +137,7 @@ describe 'projects/builds/show', :view do end it 'shows that deployment will be overwritten' do - expected_text = 'This build is creating a deployment to staging' + expected_text = 'This job is creating a deployment to staging' render expect(rendered).to have_css( @@ -150,7 +150,7 @@ describe 'projects/builds/show', :view do context 'when environment does not exist' do it 'shows deployment message' do - expected_text = 'This build is creating a deployment to staging' + expected_text = 'This job is creating a deployment to staging' render expect(rendered).to have_css( @@ -161,7 +161,7 @@ describe 'projects/builds/show', :view do end end - context 'build that failed to deploy and environment has not been created' do + context 'job that failed to deploy and environment has not been created' do let(:build) do create(:ci_build, :failed, environment: 'staging', pipeline: pipeline) end @@ -171,7 +171,7 @@ describe 'projects/builds/show', :view do end it 'shows deployment message' do - expected_text = 'The deployment of this build to staging did not succeed' + expected_text = 'The deployment of this job to staging did not succeed' render expect(rendered).to have_css( @@ -179,7 +179,7 @@ describe 'projects/builds/show', :view do end end - context 'build that will deploy and environment has not been created' do + context 'job that will deploy and environment has not been created' do let(:build) do create(:ci_build, :running, environment: 'staging', pipeline: pipeline) end @@ -189,7 +189,7 @@ describe 'projects/builds/show', :view do end it 'shows deployment message' do - expected_text = 'This build is creating a deployment to staging' + expected_text = 'This job is creating a deployment to staging' render expect(rendered).to have_css( @@ -200,7 +200,7 @@ describe 'projects/builds/show', :view do end end - context 'when build is running' do + context 'when job is running' do before do build.run! render @@ -211,7 +211,7 @@ describe 'projects/builds/show', :view do end end - context 'when build is not running' do + context 'when job is not running' do before do build.success! render From 7550f60ddeecebac3f84e8690ab3f42428600ff7 Mon Sep 17 00:00:00 2001 From: Sean McGivern <sean@gitlab.com> Date: Wed, 1 Feb 2017 14:28:04 +0000 Subject: [PATCH 173/174] Backport changes from EE squash Backport changes from the EE-only squash implementation, which would otherwise conflict when merge CE into EE. <https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1024> --- .../stylesheets/pages/merge_requests.scss | 19 ++++++++++++++----- app/helpers/merge_requests_helper.rb | 12 ++++++++++++ app/models/repository.rb | 4 ++-- app/services/merge_requests/merge_service.rb | 14 ++++++++++---- .../widget/open/_accept.html.haml | 4 ++-- .../open/_merge_when_build_succeeds.html.haml | 2 +- app/views/shared/issuable/_form.html.haml | 2 ++ .../issuable/form/_branch_chooser.html.haml | 9 --------- .../issuable/form/_merge_params.html.haml | 16 ++++++++++++++++ spec/lib/gitlab/diff/position_tracer_spec.rb | 4 +++- spec/models/repository_spec.rb | 14 +++++++++++--- .../merge_requests/refresh_service_spec.rb | 2 +- spec/support/test_env.rb | 3 ++- 13 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 app/views/shared/issuable/form/_merge_params.html.haml diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index ab68b360f93..0c013915a63 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -56,15 +56,24 @@ &.right { float: right; padding-right: 0; - - a { - color: $gl-text-color; - } } - .remove_source_checkbox { + .modify-merge-commit-link { + color: $gl-text-color; + } + + .merge-param-checkbox { margin: 0; } + + a .fa-question-circle { + color: $gl-text-color-secondary; + + &:hover, + &:focus { + color: $link-hover-color; + } + } } } diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 8c2c4e8833b..83ff898e68a 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -143,4 +143,16 @@ module MergeRequestsHelper def different_base?(version1, version2) version1 && version2 && version1.base_commit_sha != version2.base_commit_sha end + + def merge_params(merge_request) + { + merge_when_build_succeeds: true, + should_remove_source_branch: true, + sha: merge_request.diff_head_sha + }.merge(merge_params_ee(merge_request)) + end + + def merge_params_ee(merge_request) + {} + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index a54d75f7019..7cf09c52bf4 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -913,11 +913,11 @@ class Repository end end - def merge(user, merge_request, options = {}) + def merge(user, source, merge_request, options = {}) GitOperationService.new(user, self).with_branch( merge_request.target_branch) do |start_commit| our_commit = start_commit.sha - their_commit = merge_request.diff_head_sha + their_commit = source raise 'Invalid merge target' unless our_commit raise 'Invalid merge source' unless their_commit diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index ab9056a3250..5ca6fec962d 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -6,13 +6,17 @@ module MergeRequests # Executed when you do merge via GitLab UI # class MergeService < MergeRequests::BaseService - attr_reader :merge_request + attr_reader :merge_request, :source def execute(merge_request) @merge_request = merge_request return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable? + @source = find_merge_source + + return log_merge_error('No source for merge', true) unless @source + merge_request.in_locked_state do if commit after_merge @@ -34,7 +38,7 @@ module MergeRequests committer: committer } - commit_id = repository.merge(current_user, merge_request, options) + commit_id = repository.merge(current_user, source, merge_request, options) if commit_id merge_request.update(merge_commit_sha: commit_id) @@ -73,9 +77,11 @@ module MergeRequests end def merge_request_info - project = merge_request.project + merge_request.to_reference(full: true) + end - "#{project.to_reference}#{merge_request.to_reference}" + def find_merge_source + merge_request.diff_head_sha end end end diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 7809e9c8c72..39cb0ca9cdc 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -35,10 +35,10 @@ The source branch will be removed. - elsif @merge_request.can_remove_source_branch?(current_user) .accept-control.checkbox - = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do + = label_tag :should_remove_source_branch, class: "merge-param-checkbox" do = check_box_tag :should_remove_source_branch Remove source branch - .accept-control.right + .accept-control = link_to "#", class: "modify-merge-commit-link js-toggle-button" do = icon('edit') Modify commit message diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index f70cd09c5f4..304b0afcf93 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -19,7 +19,7 @@ - if remove_source_branch_button || user_can_cancel_automatic_merge .clearfix.prepend-top-10 - if remove_source_branch_button - = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.diff_head_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do + = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_params(@merge_request)), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = icon('times') Remove Source Branch When Merged diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 0a4de709fcd..cb92b2e97a7 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -43,6 +43,8 @@ = render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form += render 'shared/issuable/form/merge_params', issuable: issuable + - if @merge_request_for_resolving_discussions .form-group .col-sm-10.col-sm-offset-2 diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml index b757893ea04..2793e7bcff4 100644 --- a/app/views/shared/issuable/form/_branch_chooser.html.haml +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -19,12 +19,3 @@ - if issuable.new_record?   = link_to 'Change branches', mr_change_branches_path(issuable) - -- if issuable.can_remove_source_branch?(current_user) - .form-group - .col-sm-10.col-sm-offset-2 - .checkbox - = label_tag 'merge_request[force_remove_source_branch]' do - = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil - = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch? - Remove source branch when merge request is accepted. diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml new file mode 100644 index 00000000000..03309722326 --- /dev/null +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -0,0 +1,16 @@ +- issuable = local_assigns.fetch(:issuable) + +- return unless issuable.is_a?(MergeRequest) +- return if issuable.closed_without_fork? + +-# This check is duplicated below, to avoid conflicts with EE. +- return unless issuable.can_remove_source_branch?(current_user) + +.form-group + .col-sm-10.col-sm-offset-2 + - if issuable.can_remove_source_branch?(current_user) + .checkbox + = label_tag 'merge_request[force_remove_source_branch]' do + = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil + = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch? + Remove source branch when merge request is accepted. diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index ffb9ba86fbb..8e3e4034c8f 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -1640,7 +1640,9 @@ describe Gitlab::Diff::PositionTracer, lib: true do } merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project) - repository.merge(current_user, merge_request, options) + + repository.merge(current_user, merge_request.diff_head_sha, merge_request, options) + project.commit(branch_name) end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 901cfb907f2..53b98ba05f8 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -15,7 +15,12 @@ describe Repository, models: true do let(:merge_commit) do merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) - merge_commit_id = repository.merge(user, merge_request, commit_options) + + merge_commit_id = repository.merge(user, + merge_request.diff_head_sha, + merge_request, + commit_options) + repository.commit(merge_commit_id) end @@ -1082,8 +1087,11 @@ describe Repository, models: true do it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) - merge_commit_id = repository.merge(user, merge_request, commit_options) - repository.commit(merge_commit_id) + + merge_commit_id = repository.merge(user, + merge_request.diff_head_sha, + merge_request, + commit_options) expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id) end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 314ea670a71..2cc21acab7b 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -89,7 +89,7 @@ describe MergeRequests::RefreshService, services: true do # Merge master -> feature branch author = { email: 'test@gitlab.com', time: Time.now, name: "Me" } commit_options = { message: 'Test message', committer: author, author: author } - @project.repository.merge(@user, @merge_request, commit_options) + @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, commit_options) commit = @project.repository.commit('feature') service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature') reload_mrs diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 90f1a9c8798..b87232a350b 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -36,7 +36,8 @@ module TestEnv 'conflict-non-utf8' => 'd0a293c', 'conflict-too-large' => '39fa04f', 'deleted-image-test' => '6c17798', - 'wip' => 'b9238ee' + 'wip' => 'b9238ee', + 'csv' => '3dd0896' } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily From 3d7ace470b1f4c0f4cf90db6b1f7f1243c112de9 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira <oswaldo@gitlab.com> Date: Thu, 2 Feb 2017 21:16:23 -0200 Subject: [PATCH 174/174] Remove unnecessary queries for .atom and .json in Dashboard::ProjectsController#index --- app/controllers/dashboard/projects_controller.rb | 4 +--- ...ecessary-queries-from-projects-dashboard-atom-and-json.yml | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index c08eb811532..3ba8c2f8bb9 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -10,10 +10,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]) - @last_push = current_user.recent_push - respond_to do |format| - format.html + format.html { @last_push = current_user.recent_push } format.atom do event_filter load_events diff --git a/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml b/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml new file mode 100644 index 00000000000..7b307b501f4 --- /dev/null +++ b/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml @@ -0,0 +1,4 @@ +--- +title: Remove unnecessary queries for .atom and .json in Dashboard::ProjectsController#index +merge_request: 8956 +author: