From cb13980db88c1d1ae8a5cd766ced4629c657010b Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 8 Oct 2015 17:12:00 +0200 Subject: Let gitlab-git-http-server handle archive downloads This change relies on changes in gitlab_git and gitlab-git-http-server. --- .../projects/repositories_controller.rb | 17 ++------ app/services/archive_repository_service.rb | 45 ++-------------------- lib/api/repositories.rb | 13 +------ lib/gitlab/backend/grack_auth.rb | 9 ++++- lib/support/nginx/gitlab | 20 +++++++++- lib/support/nginx/gitlab-ssl | 20 +++++++++- 6 files changed, 54 insertions(+), 70 deletions(-) diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index c4a5e2d6359..ba9aea1c165 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - begin - file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute - rescue - return head :not_found - end - - if file_path - # Send file to user - response.headers["Content-Length"] = File.open(file_path).size.to_s - send_file file_path - else - redirect_to request.fullpath - end + render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute + rescue => ex + logger.error("#{self.class.name}: #{ex}") + return git_not_found! end end diff --git a/app/services/archive_repository_service.rb b/app/services/archive_repository_service.rb index e1b41527d8d..6414b5a0184 100644 --- a/app/services/archive_repository_service.rb +++ b/app/services/archive_repository_service.rb @@ -9,17 +9,10 @@ class ArchiveRepositoryService def execute(options = {}) project.repository.clean_old_archives - raise "No archive file path" unless file_path + metadata = project.repository.archive_metadata(ref, storage_path, format) + raise "Repository or ref not found" if metadata.empty? - return file_path if archived? - - unless archiving? - RepositoryArchiveWorker.perform_async(project.id, ref, format) - end - - archived = wait_until_archived(options[:timeout] || 5.0) - - file_path if archived + metadata end private @@ -27,36 +20,4 @@ class ArchiveRepositoryService def storage_path Gitlab.config.gitlab.repository_downloads_path end - - def file_path - @file_path ||= project.repository.archive_file_path(ref, storage_path, format) - end - - def pid_file_path - @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format) - end - - def archived? - File.exist?(file_path) - end - - def archiving? - File.exist?(pid_file_path) - end - - def wait_until_archived(timeout = 5.0) - return archived? if timeout == 0.0 - - t1 = Time.now - - begin - sleep 0.1 - - success = archived? - - t2 = Time.now - end until success || t2 - t1 >= timeout - - success - end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 2d96c9666d2..20d568cf462 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -133,7 +133,7 @@ module API authorize! :download_code, user_project begin - file_path = ArchiveRepositoryService.new( + ArchiveRepositoryService.new( user_project, params[:sha], params[:format] @@ -141,17 +141,6 @@ module API rescue not_found!('File') end - - if file_path && File.exists?(file_path) - data = File.open(file_path, 'rb').read - basename = File.basename(file_path) - header['Content-Disposition'] = "attachment; filename=\"#{basename}\"" - content_type MIME::Types.type_for(file_path).first.content_type - env['api.format'] = :binary - present data - else - redirect request.fullpath - end end # Compare two branches, tags or commits diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 0353b3b7ed3..6830a916bcb 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -193,7 +193,14 @@ module Grack end def render_grack_auth_ok - [200, { "Content-Type" => "application/json" }, [JSON.dump({ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user) })]] + [ + 200, + { "Content-Type" => "application/json" }, + [JSON.dump({ + 'GL_ID' => Gitlab::ShellEnv.gl_id(@user), + 'RepoPath' => project.repository.path_to_repo, + })] + ] end def render_not_found diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 7218a4d2f20..ffc0eb0585c 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -113,7 +113,25 @@ server { proxy_pass http://gitlab; } - location ~ [-\/\w\.]+\.git\/ { + location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive { + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + location ~ ^/api/v3/projects/[0-9]+/repository/archive { + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + location @gitlab-git-http-server { ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. # gzip off; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 7dabfba87e2..c2e9f8864f8 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -160,7 +160,25 @@ server { proxy_pass http://gitlab; } - location ~ [-\/\w\.]+\.git\/ { + location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive { + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + location ~ ^/api/v3/projects/[0-9]+/repository/archive { + # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block + error_page 418 = @gitlab-git-http-server; + return 418; + } + + location @gitlab-git-http-server { ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. gzip off; -- cgit v1.2.1 From f4cd4086815c6ec85d4714d037a09faa903f572b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 8 Oct 2015 18:12:43 -0400 Subject: Remove guard-rspec and its supporting gems --- Gemfile | 16 ---------------- Gemfile.lock | 23 ----------------------- 2 files changed, 39 deletions(-) diff --git a/Gemfile b/Gemfile index 044dc30ecd4..7ed5e11a1b9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,5 @@ source "https://rubygems.org" -def darwin_only(require_as) - RUBY_PLATFORM.include?('darwin') && require_as -end - -def linux_only(require_as) - RUBY_PLATFORM.include?('linux') && require_as -end - gem 'rails', '4.1.12' # Specify a sprockets version due to security issue @@ -308,11 +300,3 @@ gem 'oauth2', '~> 1.0.0' # Soft deletion gem "paranoia", "~> 2.0" - -group :development, :test do - gem 'guard-rspec', '~> 4.2.0' - - gem 'rb-fsevent', require: darwin_only('rb-fsevent') - gem 'growl', require: darwin_only('growl') - gem 'rb-inotify', require: linux_only('rb-inotify') -end diff --git a/Gemfile.lock b/Gemfile.lock index f716c0254ec..873583b4b5f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -315,19 +315,6 @@ GEM grape-entity (0.4.8) activesupport multi_json (>= 1.3.2) - growl (1.0.3) - guard (2.13.0) - formatador (>= 0.2.4) - listen (>= 2.7, <= 4.0) - lumberjack (~> 1.0) - nenv (~> 0.1) - notiffany (~> 0.0) - pry (>= 0.9.12) - shellany (~> 0.0) - thor (>= 0.18.1) - guard-rspec (4.2.10) - guard (~> 2.1) - rspec (>= 2.14, < 4.0) haml (4.0.7) tilt haml-rails (0.9.0) @@ -388,7 +375,6 @@ GEM celluloid (~> 0.16.0) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) - lumberjack (1.0.9) macaddr (1.7.1) systemu (~> 2.6.2) mail (2.6.3) @@ -404,7 +390,6 @@ GEM multi_xml (0.5.5) multipart-post (2.0.0) mysql2 (0.3.20) - nenv (0.2.0) nested_form (0.3.2) net-ldap (0.11) net-scp (1.2.1) @@ -417,9 +402,6 @@ GEM newrelic_rpm (3.9.4.245) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) - notiffany (0.0.7) - nenv (~> 0.1) - shellany (~> 0.0) nprogress-rails (0.1.2.3) oauth (0.4.7) oauth2 (1.0.0) @@ -648,7 +630,6 @@ GEM sexp_processor (4.6.0) sham_rack (1.3.6) rack - shellany (0.0.1) shoulda-matchers (2.8.0) activesupport (>= 3.0.0) sidekiq (3.3.0) @@ -849,8 +830,6 @@ DEPENDENCIES gon (~> 5.0.0) grape (~> 0.6.1) grape-entity (~> 0.4.2) - growl - guard-rspec (~> 4.2.0) haml-rails (~> 0.9.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) @@ -894,8 +873,6 @@ DEPENDENCIES rack-oauth2 (~> 1.0.5) rails (= 4.1.12) raphael-rails (~> 2.1.2) - rb-fsevent - rb-inotify rdoc (~> 3.6) redcarpet (~> 3.3.2) redis-rails (~> 4.0.0) -- cgit v1.2.1 From a972ce17caadc64fc6fa1888301e99c149848f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A7=D0=B8=D0=BD=D0=B3=D0=B8=D0=B7=20=D0=90=D1=83=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D0=BE=D0=B2?= Date: Sat, 10 Oct 2015 14:07:12 +0600 Subject: + and Titleize New Project button on dashboard --- app/views/dashboard/projects/_projects.html.haml | 2 +- app/views/groups/_projects.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml index e09e032a7f1..30372e98430 100644 --- a/app/views/dashboard/projects/_projects.html.haml +++ b/app/views/dashboard/projects/_projects.html.haml @@ -5,6 +5,6 @@ - if current_user.can_create_project? %span.input-group-btn = link_to new_project_path, class: 'btn btn-green' do - New project + + New Project = render 'shared/projects/list', projects: @projects, ci: true diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 2b27a88794d..9c73f7a0ef7 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -5,6 +5,6 @@ - if can? current_user, :create_projects, @group %span.input-group-btn = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do - New project + + New Project = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false -- cgit v1.2.1 From 2df573dac3859034fcb90566a8ebc270a7e6088a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 9 Oct 2015 09:14:35 -0700 Subject: Fix bug where merge request comments created by API would not trigger notifications Closes https://github.com/gitlabhq/gitlabhq/issues/9715 --- CHANGELOG | 1 + lib/api/merge_requests.rb | 12 ++++++++++-- spec/requests/api/merge_requests_spec.rb | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f9a4d17fa16..e7e7e7becfd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.1.0 (unreleased) - Make diff file view easier to use on mobile screens (Stan Hu) + - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu) - Add support for creating directories from Files page (Stan Hu) - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 63ea2f05438..8cfc8ee6713 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -249,8 +249,16 @@ module API required_attributes! [:note] merge_request = user_project.merge_requests.find(params[:merge_request_id]) - note = merge_request.notes.new(note: params[:note], project_id: user_project.id) - note.author = current_user + + authorize! :create_note, merge_request + + opts = { + note: params[:note], + noteable_type: 'MergeRequest', + noteable_id: merge_request.id + } + + note = ::Notes::CreateService.new(user_project, current_user, opts).execute if note.save present note, with: Entities::MRNote diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 35b3d3e296a..a68c7b1e461 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -379,9 +379,14 @@ describe API::API, api: true do describe "POST /projects/:id/merge_request/:merge_request_id/comments" do it "should return comment" do + original_count = merge_request.notes.size + post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment" expect(response.status).to eq(201) expect(json_response['note']).to eq('My comment') + expect(json_response['author']['name']).to eq(user.name) + expect(json_response['author']['username']).to eq(user.username) + expect(merge_request.notes.size).to eq(original_count + 1) end it "should return 400 if note is missing" do -- cgit v1.2.1 From b0164771ec693ff58504ece560371ffec11f9ca9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 12 Oct 2015 11:54:46 +0200 Subject: Simplify code around (cross)-references --- app/controllers/projects/issues_controller.rb | 2 +- .../projects/merge_requests_controller.rb | 2 +- app/models/concerns/mentionable.rb | 38 ++++++++-------- app/models/concerns/participable.rb | 16 +++---- app/models/note.rb | 2 +- app/services/git_push_service.rb | 50 +++++++--------------- app/services/issues/create_service.rb | 2 +- app/services/issues/update_service.rb | 2 +- app/services/merge_requests/create_service.rb | 2 +- app/services/merge_requests/update_service.rb | 2 +- lib/gitlab/reference_extractor.rb | 2 + spec/models/concerns/mentionable_spec.rb | 2 +- spec/services/git_push_service_spec.rb | 2 +- spec/support/mentionable_shared_examples.rb | 10 ++--- 14 files changed, 58 insertions(+), 76 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 0f89f2e88cc..4612abcbae8 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -55,7 +55,7 @@ class Projects::IssuesController < Projects::ApplicationController end def show - @participants = @issue.participants(current_user, @project) + @participants = @issue.participants(current_user) @note = @project.notes.new(noteable: @issue) @notes = @issue.notes.inc_author.fresh @noteable = @issue diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 7570934e727..98df6984bf7 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -246,7 +246,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_show_vars - @participants = @merge_request.participants(current_user, @project) + @participants = @merge_request.participants(current_user) # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 5b0ae411642..694403949c1 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -43,53 +43,53 @@ module Mentionable # Determine whether or not a cross-reference Note has already been created between this Mentionable and # the specified target. - def has_mentioned?(target) + def cross_reference_exists?(target) SystemNoteService.cross_reference_exists?(target, local_reference) end - def mentioned_users(current_user = nil) - return [] if mentionable_text.blank? - + def all_references(current_user = self.author, text = self.mentionable_text) ext = Gitlab::ReferenceExtractor.new(self.project, current_user) - ext.analyze(mentionable_text) - ext.users.uniq + ext.analyze(text) + ext + end + + def mentioned_users(current_user = nil) + all_references(current_user).users.uniq end # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def references(p = project, current_user = self.author, text = mentionable_text) + def referenced_mentionables(current_user = self.author, text = self.mentionable_text) return [] if text.blank? - ext = Gitlab::ReferenceExtractor.new(p, current_user) - ext.analyze(text) - - (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference] + refs = all_references(current_user, text) + (refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference] end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. - def create_cross_references!(p = project, a = author, without = []) - refs = references(p) - + def create_cross_references!(author = self.author, without = []) + refs = referenced_mentionables(author) + # We're using this method instead of Array diffing because that requires # both of the object's `hash` values to be the same, which may not be the # case for otherwise identical Commit objects. - refs.reject! { |ref| without.include?(ref) } + refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) } refs.each do |ref| - SystemNoteService.cross_reference(ref, local_reference, a) + SystemNoteService.cross_reference(ref, local_reference, author) end end # When a mentionable field is changed, creates cross-reference notes that # don't already exist - def create_new_cross_references!(p = project, a = author) + def create_new_cross_references!(author = self.author) changes = detect_mentionable_changes return if changes.empty? original_text = changes.collect { |_, vals| vals.first }.join(' ') - preexisting = references(p, self.author, original_text) - create_cross_references!(p, a, preexisting) + preexisting = referenced_mentionables(author, original_text) + create_cross_references!(author, preexisting) end private diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 7c9597333dd..d697c125c7d 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -37,7 +37,7 @@ module Participable # Be aware that this method makes a lot of sql queries. # Save result into variable if you are going to reuse it inside same request - def participants(current_user = self.author, project = self.project) + def participants(current_user = self.author) participants = self.class.participant_attrs.flat_map do |attr| meth = method(attr) @@ -48,13 +48,11 @@ module Participable meth.call end - participants_for(value, current_user, project) + participants_for(value, current_user) end.compact.uniq - if project - participants.select! do |user| - user.can?(:read_project, project) - end + participants.select! do |user| + user.can?(:read_project, self.project) end participants @@ -62,14 +60,14 @@ module Participable private - def participants_for(value, current_user = nil, project = nil) + def participants_for(value, current_user = nil) case value when User [value] when Enumerable, ActiveRecord::Relation - value.flat_map { |v| participants_for(v, current_user, project) } + value.flat_map { |v| participants_for(v, current_user) } when Participable - value.participants(current_user, project) + value.participants(current_user) end end end diff --git a/app/models/note.rb b/app/models/note.rb index de3b6df88f7..13eb28f5718 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -358,7 +358,7 @@ class Note < ActiveRecord::Base end def set_references - create_new_cross_references!(project, author) + create_new_cross_references! end def system? diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f9a8265d2d4..81d47602f13 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -74,48 +74,30 @@ class GitPushService def process_commit_messages(ref) is_default_branch = is_default_branch?(ref) - @push_commits.each do |commit| - # Close issues if these commits were pushed to the project's default branch and the commit message matches the - # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to - # a different branch. - issues_to_close = commit.closes_issues(user) + authors = Hash.new do |hash, commit| + email = commit.author_email + return hash[email] if hash.has_key?(email) - # Load commit author only if needed. - # For push with 1k commits it prevents 900+ requests in database - author = nil + hash[email] = commit_user(commit) + end + @push_commits.each do |commit| # Keep track of the issues that will be actually closed because they are on a default branch. # Hence, when creating cross-reference notes, the not-closed issues (on non-default branches) # will also have cross-reference. - actually_closed_issues = [] - - if issues_to_close.present? && is_default_branch - author ||= commit_user(commit) - actually_closed_issues = issues_to_close - issues_to_close.each do |issue| - Issues::CloseService.new(project, author, {}).execute(issue, commit) + closed_issues = [] + + if is_default_branch + # Close issues if these commits were pushed to the project's default branch and the commit message matches the + # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to + # a different branch. + closed_issues = commit.closes_issues(user) + closed_issues.each do |issue| + Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) end end - if project.default_issues_tracker? - create_cross_reference_notes(commit, actually_closed_issues) - end - end - end - - def create_cross_reference_notes(commit, issues_to_close) - # Create cross-reference notes for any other references than those given in issues_to_close. - # Omit any issues that were referenced in an issue-closing phrase, or have already been - # mentioned from this commit (probably from this commit being pushed to a different branch). - refs = commit.references(project, user) - issues_to_close - refs.reject! { |r| commit.has_mentioned?(r) } - - if refs.present? - author ||= commit_user(commit) - - refs.each do |r| - SystemNoteService.cross_reference(r, commit, author) - end + commit.create_cross_references!(authors[commit], closed_issues) end end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 1ea4b72216c..bcb380d3215 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -10,7 +10,7 @@ module Issues issue.update_attributes(label_ids: label_params) notification_service.new_issue(issue, current_user) event_service.open_issue(issue, current_user) - issue.create_cross_references!(issue.project, current_user) + issue.create_cross_references!(current_user) execute_hooks(issue, 'open') end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 2fc6ef7f356..2b5426ad452 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -35,7 +35,7 @@ module Issues create_title_change_note(issue, issue.previous_changes['title'].first) end - issue.create_new_cross_references!(issue.project, current_user) + issue.create_new_cross_references! execute_hooks(issue, 'update') end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 9651b16462c..009d5a6867e 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -18,7 +18,7 @@ module MergeRequests merge_request.update_attributes(label_ids: label_params) event_service.open_mr(merge_request, current_user) notification_service.new_merge_request(merge_request, current_user) - merge_request.create_cross_references!(merge_request.project, current_user) + merge_request.create_cross_references!(current_user) execute_hooks(merge_request) end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 25d79e22e39..ebbe0af803b 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -59,7 +59,7 @@ module MergeRequests merge_request.mark_as_unchecked end - merge_request.create_new_cross_references!(merge_request.project, current_user) + merge_request.create_new_cross_references! execute_hooks(merge_request, 'update') end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 0961bd80421..30497e274c2 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -39,6 +39,8 @@ module Gitlab # # Returns the results Array for the requested filter type def pipeline_result(filter_type) + return [] if @text.blank? + klass = filter_type.to_s.camelize + 'ReferenceFilter' filter = Gitlab::Markdown.const_get(klass) diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 2d6fe003215..6179882e935 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -25,7 +25,7 @@ describe Issue, "Mentionable" do it 'correctly removes already-mentioned Commits' do expect(SystemNoteService).not_to receive(:cross_reference) - issue.create_cross_references!(project, author, [commit2]) + issue.create_cross_references!(author, [commit2]) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index c483060fd73..bb620783410 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -155,7 +155,7 @@ describe GitPushService do before do allow(commit).to receive_messages( - safe_message: "this commit \n mentions ##{issue.id}", + safe_message: "this commit \n mentions ##{issue.iid}", references: [issue], author_name: commit_author.name, author_email: commit_author.email diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index e3de0afb448..220566a22b6 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -65,7 +65,7 @@ shared_examples 'a mentionable' do it "extracts references from its reference property" do # De-duplicate and omit itself - refs = subject.references(project) + refs = subject.referenced_mentionables expect(refs.size).to eq(6) expect(refs).to include(mentioned_issue) expect(refs).to include(mentioned_mr) @@ -84,14 +84,14 @@ shared_examples 'a mentionable' do with(referenced, subject.local_reference, author) end - subject.create_cross_references!(project, author) + subject.create_cross_references! end it 'detects existing cross-references' do SystemNoteService.cross_reference(mentioned_issue, subject.local_reference, author) - expect(subject).to have_mentioned(mentioned_issue) - expect(subject).not_to have_mentioned(mentioned_mr) + expect(subject.cross_reference_exists?(mentioned_issue)).to be_truthy + expect(subject.cross_reference_exists?(mentioned_mr)).to be_falsey end end @@ -143,6 +143,6 @@ shared_examples 'an editable mentionable' do end set_mentionable_text.call(new_text) - subject.create_new_cross_references!(project, author) + subject.create_new_cross_references!(author) end end -- cgit v1.2.1 From 27d952b1197f2dc615c383c21eb287313d81c74c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 12 Oct 2015 14:30:44 +0200 Subject: Fix cross-references originating from notes --- app/models/concerns/mentionable.rb | 4 ++-- app/models/note.rb | 13 +++---------- app/services/notes/create_service.rb | 8 +------- app/services/notes/update_service.rb | 2 +- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 694403949c1..5f53ea25630 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -66,8 +66,8 @@ module Mentionable end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. - def create_cross_references!(author = self.author, without = []) - refs = referenced_mentionables(author) + def create_cross_references!(author = self.author, without = [], text = self.mentionable_text) + refs = referenced_mentionables(author, text) # We're using this method instead of Array diffing because that requires # both of the object's `hash` values to be the same, which may not be the diff --git a/app/models/note.rb b/app/models/note.rb index 13eb28f5718..ee0c14598f3 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -62,7 +62,6 @@ class Note < ActiveRecord::Base serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } - after_update :set_references class << self def discussions_from_notes(notes) @@ -333,15 +332,13 @@ class Note < ActiveRecord::Base end def noteable_type_name - if noteable_type.present? - noteable_type.downcase - end + noteable_type.downcase if noteable_type.present? end # FIXME: Hack for polymorphic associations with STI # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations - def noteable_type=(sType) - super(sType.to_s.classify.constantize.base_class.to_s) + def noteable_type=(noteable_type) + super(noteable_type.to_s.classify.constantize.base_class.to_s) end # Reset notes events cache @@ -357,10 +354,6 @@ class Note < ActiveRecord::Base Event.reset_event_cache_for(self) end - def set_references - create_new_cross_references! - end - def system? read_attribute(:system) end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 482c0444049..2001dc89c33 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -11,13 +11,7 @@ module Notes # Skip system notes, like status changes and cross-references. unless note.system event_service.leave_note(note, note.author) - - # Create a cross-reference note if this Note contains GFM that names an - # issue, merge request, or commit. - note.references.each do |mentioned| - SystemNoteService.cross_reference(mentioned, note.noteable, note.author) - end - + note.create_cross_references! execute_hooks(note) end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index c22a9333ef6..6c2f08e5963 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -4,7 +4,7 @@ module Notes return note unless note.editable? note.update_attributes(params.merge(updated_by: current_user)) - + note.create_new_cross_references! note.reset_events_cache note -- cgit v1.2.1 From 41075bad8daeed07bceaa44cbfe54ab1b470ca87 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 12 Oct 2015 14:39:33 +0200 Subject: Chaining ftw --- app/models/concerns/participable.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index d697c125c7d..22182445978 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -49,13 +49,9 @@ module Participable end participants_for(value, current_user) - end.compact.uniq - - participants.select! do |user| + end.compact.uniq.select do |user| user.can?(:read_project, self.project) end - - participants end private -- cgit v1.2.1 From 024e34e94d973842cf02d9177e9ec52bd587ceee Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Mon, 12 Oct 2015 15:24:00 +0200 Subject: Hide passwords to non-admin users in the services API In order to be consistent with !1490 doing it for the web interface --- CHANGELOG | 1 + lib/api/entities.rb | 12 ++++++++++++ lib/api/services.rb | 2 +- spec/requests/api/services_spec.rb | 33 ++++++++++++++++++++++++++++++++- spec/support/services_shared_context.rb | 8 +++++++- 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a3d796bea66..10ea52a12f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.1.0 (unreleased) - Fix position of hamburger in header for smaller screens (Han Loong Liauw) - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) - Persist filters when sorting on admin user page (Jerry Lukins) + - Hide passwords from services API (Alex Lossent) v 8.0.4 - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9620d36ac41..7a1e702c755 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -255,6 +255,18 @@ module API expose :notification_level end + class ProjectService < Grape::Entity + expose :id, :title, :created_at, :updated_at, :active + expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events + # Expose serialized properties + expose :properties do |service, options| + field_names = service.fields. + select { |field| options[:include_passwords] || field[:type] != 'password' }. + map { |field| field[:name] } + service.properties.slice(*field_names) + end + end + class ProjectWithAccess < Project expose :permissions do expose :project_access, using: Entities::ProjectAccess do |project, options| diff --git a/lib/api/services.rb b/lib/api/services.rb index 6727e80ac1e..203f04a6259 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -57,7 +57,7 @@ module API # GET /project/:id/services/gitlab-ci # get ':id/services/:service_slug' do - present project_service + present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin? end end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 9aa60826f21..c0226605a23 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } + let(:admin) { create(:admin) } + let(:user2) { create(:user) } let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } Service.available_services_names.each do |service| @@ -51,11 +53,40 @@ describe API::API, api: true do describe "GET /projects/:id/services/#{service.dasherize}" do include_context service - it "should get #{service} settings" do + # inject some properties into the service + before do + project.build_missing_services + service_object = project.send(service_method) + service_object.properties = service_attrs + service_object.save + end + + it 'should return authentication error when unauthenticated' do + get api("/projects/#{project.id}/services/#{dashed_service}") + expect(response.status).to eq(401) + end + + it "should return all properties of service #{service} when authenticated as admin" do + get api("/projects/#{project.id}/services/#{dashed_service}", admin) + + expect(response.status).to eq(200) + expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map) + end + + it "should return properties of service #{service} other than passwords when authenticated as project owner" do get api("/projects/#{project.id}/services/#{dashed_service}", user) expect(response.status).to eq(200) + expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords) end + + it "should return error when authenticated but not a project owner" do + project.team << [user2, :developer] + get api("/projects/#{project.id}/services/#{dashed_service}", user2) + + expect(response.status).to eq(403) + end + end end end diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb index 4d007ae55ee..d1c999cad4d 100644 --- a/spec/support/services_shared_context.rb +++ b/spec/support/services_shared_context.rb @@ -3,7 +3,13 @@ Service.available_services_names.each do |service| let(:dashed_service) { service.dasherize } let(:service_method) { "#{service}_service".to_sym } let(:service_klass) { "#{service}_service".classify.constantize } - let(:service_attrs_list) { service_klass.new.fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } } + let(:service_fields) { service_klass.new.fields } + let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } } + let(:service_attrs_list_without_passwords) do + service_fields. + select { |field| field[:type] != 'password' }. + map { |field| field[:name].to_sym} + end let(:service_attrs) do service_attrs_list.inject({}) do |hash, k| if k =~ /^(token*|.*_token|.*_key)/ -- cgit v1.2.1 From 7a0cc665ff5ad3969f36082baa162a2169c34612 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 12 Oct 2015 15:34:08 +0200 Subject: Remove useless assignment --- app/models/concerns/participable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 22182445978..ffc874357fd 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -38,7 +38,7 @@ module Participable # Be aware that this method makes a lot of sql queries. # Save result into variable if you are going to reuse it inside same request def participants(current_user = self.author) - participants = self.class.participant_attrs.flat_map do |attr| + self.class.participant_attrs.flat_map do |attr| meth = method(attr) value = -- cgit v1.2.1 From 6f35614852a7cf360bbc4af6ba6c450d8667c04e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 12 Oct 2015 16:23:15 +0200 Subject: Fix mentionable specs --- spec/models/commit_spec.rb | 4 ++-- spec/models/issue_spec.rb | 2 +- spec/models/note_spec.rb | 5 ++--- spec/support/mentionable_shared_examples.rb | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index e303a97e6b5..90be9324951 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -89,9 +89,9 @@ eos end it_behaves_like 'a mentionable' do - subject { commit } + subject { create(:project).commit } - let(:author) { create(:user, email: commit.author_email) } + let(:author) { create(:user, email: subject.author_email) } let(:backref_text) { "commit #{subject.id}" } let(:set_mentionable_text) do ->(txt) { allow(subject).to receive(:safe_message).and_return(txt) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index cf336d82957..623332cd2f9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -69,7 +69,7 @@ describe Issue do end it_behaves_like 'an editable mentionable' do - subject { create(:issue, project: project) } + subject { create(:issue) } let(:backref_text) { "issue #{subject.to_reference}" } let(:set_mentionable_text) { ->(txt){ subject.description = txt } } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 3a0b194ba1e..75564839dcf 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -192,10 +192,9 @@ describe Note do end it_behaves_like 'an editable mentionable' do - subject { create :note, noteable: issue, project: project } + subject { create :note, noteable: issue, project: issue.project } - let(:project) { create(:project) } - let(:issue) { create :issue, project: project } + let(:issue) { create :issue } let(:backref_text) { issue.gfm_reference } let(:set_mentionable_text) { ->(txt) { subject.note = txt } } end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 220566a22b6..412c6f4ead8 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -5,7 +5,7 @@ # - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } } def common_mentionable_setup - let(:project) { create :project } + let(:project) { subject.project } let(:author) { subject.author } let(:mentioned_issue) { create(:issue, project: project) } -- cgit v1.2.1 From 42fb52adf4460682c35d70930bcb2379ccb26171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A7=D0=B8=D0=BD=D0=B3=D0=B8=D0=B7=20=D0=90=D1=83=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D0=BE=D0=B2?= Date: Tue, 13 Oct 2015 04:41:51 +0600 Subject: Use css style --- app/views/dashboard/projects/_projects.html.haml | 3 ++- app/views/groups/_projects.html.haml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml index 30372e98430..e641b82f819 100644 --- a/app/views/dashboard/projects/_projects.html.haml +++ b/app/views/dashboard/projects/_projects.html.haml @@ -5,6 +5,7 @@ - if current_user.can_create_project? %span.input-group-btn = link_to new_project_path, class: 'btn btn-green' do - + New Project + %i.fa.fa-plus + New Project = render 'shared/projects/list', projects: @projects, ci: true diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 9c73f7a0ef7..53c11a471b0 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -5,6 +5,7 @@ - if can? current_user, :create_projects, @group %span.input-group-btn = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do - + New Project + %i.fa.fa-plus + New Project = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false -- cgit v1.2.1 From d02d02c672bcac0d2ef46204d132645bc69827a8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 12 Oct 2015 21:43:24 -0700 Subject: Fix error preventing displaying of commit data for a directory with a leading dot Closes https://github.com/gitlabhq/gitlabhq/issues/8763 --- CHANGELOG | 1 + app/controllers/projects/refs_controller.rb | 7 +++++++ config/routes.rb | 4 +++- features/project/source/browse_files.feature | 6 ++++++ features/steps/project/source/browse_files.rb | 13 +++++++++++++ spec/support/test_env.rb | 2 +- 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a3d796bea66..d802fb8db40 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.1.0 (unreleased) + - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) - Make diff file view easier to use on mobile screens (Stan Hu) - Add support for creating directories from Files page (Stan Hu) - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 6080c849c8d..c4e18c17077 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController include TreeHelper before_action :require_non_empty_project + before_action :validate_ref_id before_action :assign_ref_vars before_action :authorize_download_code! @@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController format.js end end + + private + + def validate_ref_id + return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex + end end diff --git a/config/routes.rb b/config/routes.rb index 8e6fbf6340c..893ab59c327 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -543,8 +543,10 @@ Gitlab::Application.routes.draw do member do # tree viewer logs get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + # Directories with leading dots erroneously get rejected if git + # ref regex used in constraints. Regex verification now done in controller. get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: Gitlab::Regex.git_reference_regex, + id: /.*/, path: /.*/ } end diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 377c5e1a9a7..6b0484b6a38 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -205,3 +205,9 @@ Feature: Project Source Browse Files And I see the ref 'test' has been selected And I visit the 'test' tree Then I see the commit data + + @javascript + Scenario: I browse code with a leading dot in the directory + Given I switch ref to fix + And I visit the fix tree + Then I see the commit data for a directory with a leading dot diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index cb100ca0f54..1b27500497a 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -286,6 +286,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps select "'test'", from: 'ref' end + step "I switch ref to fix" do + select "fix", from: 'ref' + end + step "I see the ref 'test' has been selected" do expect(page).to have_selector '.select2-chosen', text: "'test'" end @@ -294,11 +298,20 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps visit namespace_project_tree_path(@project.namespace, @project, "'test'") end + step "I visit the fix tree" do + visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir") + end + step 'I see the commit data' do expect(page).to have_css('.tree-commit-link', visible: true) expect(page).not_to have_content('Loading commit data...') end + step 'I see the commit data for a directory with a leading dot' do + expect(page).to have_css('.tree-commit-link', visible: true) + expect(page).not_to have_content('Loading commit data...') + end + private def set_new_content diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 3eab74ba986..d12ba25b71b 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -9,7 +9,7 @@ module TestEnv 'flatten-dir' => 'e56497b', 'feature' => '0b4bc9a', 'feature_conflict' => 'bb5206f', - 'fix' => '12d65c8', + 'fix' => '48f0be4', 'improve/awesome' => '5937ac0', 'markdown' => '0ed8c6c', 'master' => '5937ac0', -- cgit v1.2.1 From 127f288afbc454975bee910cee22f330a86c0e1b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 13 Oct 2015 09:37:42 +0000 Subject: Use `to_reference` where possible. --- spec/services/git_push_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index bb620783410..93bac384a68 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -155,7 +155,7 @@ describe GitPushService do before do allow(commit).to receive_messages( - safe_message: "this commit \n mentions ##{issue.iid}", + safe_message: "this commit \n mentions #{issue.to_reference}", references: [issue], author_name: commit_author.name, author_email: commit_author.email @@ -272,4 +272,4 @@ describe GitPushService do service.execute(project, user, @blankrev, @newrev, new_ref) end end -end +end \ No newline at end of file -- cgit v1.2.1 From 712d17684b2b9a8664cdff685c44fa59ea6fabbc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 13 Oct 2015 13:07:59 +0200 Subject: Make Reply by email easier to configure --- .gitignore | 1 - Gemfile | 2 +- Gemfile.lock | 4 +- config/gitlab.yml.example | 24 ++- config/initializers/1_settings.rb | 4 +- config/mail_room.yml | 39 +++++ config/mail_room.yml.example | 39 ----- doc/incoming_email/README.md | 299 ++++++++++++++++---------------------- lib/gitlab/incoming_email.rb | 4 +- lib/tasks/gitlab/check.rake | 37 +---- 10 files changed, 199 insertions(+), 254 deletions(-) create mode 100644 config/mail_room.yml delete mode 100644 config/mail_room.yml.example diff --git a/.gitignore b/.gitignore index 2a97eacad48..73bde4cc761 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ config/initializers/rack_attack.rb config/initializers/smtp_settings.rb config/resque.yml config/unicorn.rb -config/mail_room.yml config/secrets.yml coverage/* db/*.sqlite3 diff --git a/Gemfile b/Gemfile index 967092994a6..392644dfa86 100644 --- a/Gemfile +++ b/Gemfile @@ -290,7 +290,7 @@ gem 'newrelic-grape' gem 'octokit', '~> 3.7.0' -gem "mail_room", "~> 0.6.0" +gem "mail_room", "~> 0.6.1" gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index 58426a60683..7e989aa461b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -392,7 +392,7 @@ GEM systemu (~> 2.6.2) mail (2.6.3) mime-types (>= 1.16, < 3) - mail_room (0.6.0) + mail_room (0.6.1) method_source (0.8.2) mime-types (1.25.1) mimemagic (0.3.0) @@ -854,7 +854,7 @@ DEPENDENCIES jquery-ui-rails (~> 4.2.1) kaminari (~> 0.16.3) letter_opener (~> 1.1.2) - mail_room (~> 0.6.0) + mail_room (~> 0.6.1) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) mysql2 (~> 0.3.16) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 4f7f0b6ef19..8b85981497a 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -99,7 +99,29 @@ production: &base # For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html incoming_email: enabled: false - address: "incoming+%{key}@gitlab.example.com" + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`. + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4c78bd6e2fa..f04263c760b 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -187,7 +187,9 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci[ # Reply by email # Settings['incoming_email'] ||= Settingslogic.new({}) -Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil? +Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil? +Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil? +Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil? # # Gravatar diff --git a/config/mail_room.yml b/config/mail_room.yml new file mode 100644 index 00000000000..42f6f74c465 --- /dev/null +++ b/config/mail_room.yml @@ -0,0 +1,39 @@ +:mailboxes: +<% +require_relative 'config/environment.rb' + +if Gitlab::IncomingEmail.enabled? + config = Gitlab::IncomingEmail.config + + redis_config_file = "config/resque.yml" + redis_url = + if File.exists?(redis_config_file) + YAML.load_file(redis_config_file)[Rails.env] + else + "redis://localhost:6379" + end + %> + - + :host: <%= config.host.to_json %> + :port: <%= config.port.to_json %> + :ssl: <%= config.ssl.to_json %> + :start_tls: <%= config.start_tls.to_json %> + :email: <%= config.user.to_json %> + :password: <%= config.password.to_json %> + + :name: <%= config.mailbox.to_json %> + + :delete_after_delivery: true + + :delivery_method: sidekiq + :delivery_options: + :redis_url: <%= redis_url.to_json %> + :namespace: resque:gitlab + :queue: incoming_email + :worker: EmailReceiverWorker + + :arbitration_method: redis + :arbitration_options: + :redis_url: <%= redis_url.to_json %> + :namespace: mail_room:gitlab +<% end %> diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example deleted file mode 100644 index bb624e8a187..00000000000 --- a/config/mail_room.yml.example +++ /dev/null @@ -1,39 +0,0 @@ -:mailboxes: - - - # # IMAP server host - # :host: "imap.gmail.com" - # # IMAP server port - # :port: 993 - # # Whether the IMAP server uses SSL - # :ssl: true - # # Whether the IMAP server uses StartTLS - # :start_tls: false - # # Email account username. Usually the full email address. - # :email: "gitlab-incoming@gmail.com" - # # Email account password - # :password: "password" - - # # The name of the mailbox where incoming mail will end up. Usually "inbox". - # :name: "inbox" - - # # Always "sidekiq". - # :delivery_method: sidekiq - # # Always true. - # :delete_after_delivery: true - # :delivery_options: - # # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. - # :redis_url: redis://localhost:6379 - # # Always "resque:gitlab". - # :namespace: resque:gitlab - # # Always "incoming_email". - # :queue: incoming_email - # # Always "EmailReceiverWorker". - # :worker: EmailReceiverWorker - - # # Always "redis". - # :arbitration_method: redis - # :arbitration_options: - # # The URL to the Redis server. Should match the URL in config/resque.yml. - # :redis_url: redis://localhost:6379 - # # Always "mail_room:gitlab". - # :namespace: mail_room:gitlab diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md index aafa2345fab..86d205ba7a5 100644 --- a/doc/incoming_email/README.md +++ b/doc/incoming_email/README.md @@ -4,9 +4,9 @@ GitLab can be set up to allow users to comment on issues and merge requests by r ## Get a mailbox -Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises. +Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises. -If you want to use Gmail with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255). +If you want to use Gmail / Google Apps with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255). To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these instructions](./postfix.md). @@ -14,30 +14,62 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these ### Omnibus package installations -1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature, enter the email address including a placeholder for the `key` that references the item being replied to and fill in the details for your specific IMAP server and email account: +1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature and fill in the details for your specific IMAP server and email account: ```ruby - # Postfix mail server, assumes mailbox incoming@gitlab.example.com + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com gitlab_rails['incoming_email_enabled'] = true + + # The email address including a placeholder for the key that references the item being replied to. + # The `%{key}` placeholder is added after the user part, before the `@`. gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" - gitlab_rails['incoming_email_host'] = "gitlab.example.com" # IMAP server host - gitlab_rails['incoming_email_port'] = 143 # IMAP server port - gitlab_rails['incoming_email_ssl'] = false # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_email'] = "incoming" # Email account username. Usually the full email address. - gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password - gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox". + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "incoming" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "gitlab.example.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 143 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = false + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" ``` ```ruby - # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`. gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" - gitlab_rails['incoming_email_host'] = "imap.gmail.com" # IMAP server host - gitlab_rails['incoming_email_port'] = 993 # IMAP server port - gitlab_rails['incoming_email_ssl'] = true # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" # Email account username. Usually the full email address. - gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password - gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox". + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "imap.gmail.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 993 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = true + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" ``` As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`. @@ -64,229 +96,146 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these cd /home/git/gitlab ``` -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `key` that references the item being replied to: +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: ```sh sudo editor config/gitlab.yml ``` ```yaml - # Postfix mail server, assumes mailbox incoming@gitlab.example.com + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com incoming_email: enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`. address: "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "incoming" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "gitlab.example.com" + # IMAP server port + port: 143 + # Whether the IMAP server uses SSL + ssl: false + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" ``` ```yaml - # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com incoming_email: enabled: true - address: "gitlab-incoming+%{key}@gmail.com" - ``` - - As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`. -2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`: + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`. + address: "gitlab-incoming+%{key}@gmail.com" - ```sh - sudo cp config/mail_room.yml.example config/mail_room.yml - ``` + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" -3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false - ```sh - sudo editor config/mail_room.yml - ``` - - ```yaml - # Postfix mail server - :mailboxes: - - - # IMAP server host - :host: "gitlab.example.com" - # IMAP server port - :port: 143 - # Whether the IMAP server uses SSL - :ssl: false - # Whether the IMAP server uses StartTLS - :start_tls: false - # Email account username. Usually the full email address. - :email: "incoming" - # Email account password - :password: "[REDACTED]" - - # The name of the mailbox where incoming mail will end up. Usually "inbox". - :name: "inbox" - - # Always "sidekiq". - :delivery_method: sidekiq - # Always true. - :delete_after_delivery: true - :delivery_options: - # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. - :redis_url: redis://localhost:6379 - # Always "resque:gitlab". - :namespace: resque:gitlab - # Always "incoming_email". - :queue: incoming_email - # Always "EmailReceiverWorker" - :worker: EmailReceiverWorker - - # Always "redis". - :arbitration_method: redis - :arbitration_options: - # The URL to the Redis server. Should match the URL in config/resque.yml. - :redis_url: redis://localhost:6379 - # Always "mail_room:gitlab". - :namespace: mail_room:gitlab + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" ``` - ```yaml - # Gmail / Google Apps - :mailboxes: - - - # IMAP server host - :host: "imap.gmail.com" - # IMAP server port - :port: 993 - # Whether the IMAP server uses SSL - :ssl: true - # Whether the IMAP server uses StartTLS - :start_tls: false - # Email account username. Usually the full email address. - :email: "gitlab-incoming@gmail.com" - # Email account password - :password: "[REDACTED]" - - # The name of the mailbox where incoming mail will end up. Usually "inbox". - :name: "inbox" - - # Always "sidekiq". - :delivery_method: sidekiq - # Always true. - :delete_after_delivery: true - :delivery_options: - # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. - :redis_url: redis://localhost:6379 - # Always "resque:gitlab". - :namespace: resque:gitlab - # Always "incoming_email". - :queue: incoming_email - # Always "EmailReceiverWorker" - :worker: EmailReceiverWorker - - # Always "redis". - :arbitration_method: redis - :arbitration_options: - # The URL to the Redis server. Should match the URL in config/resque.yml. - :redis_url: redis://localhost:6379 - # Always "mail_room:gitlab". - :namespace: mail_room:gitlab - ``` + As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`. -5. Edit the init script configuration at `/etc/default/gitlab` to enable `mail_room`: +1. Enable `mail_room` in the init script at `/etc/default/gitlab`: ```sh sudo mkdir -p /etc/default echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab ``` -6. Restart GitLab: +1. Restart GitLab: ```sh sudo service gitlab restart ``` -7. Verify that everything is configured correctly: +1. Verify that everything is configured correctly: ```sh sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production ``` -8. Reply by email should now be working. +1. Reply by email should now be working. ### Development 1. Go to the GitLab installation directory. -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `key` that references the item being replied to: +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: ```yaml - # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com incoming_email: enabled: true + + # The email address including a placeholder for the key that references the item being replied to. + # The `%{key}` placeholder is added after the user part, before the `@`. address: "gitlab-incoming+%{key}@gmail.com" - ``` - As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" -2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`: + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false - ```sh - sudo cp config/mail_room.yml.example config/mail_room.yml + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" ``` -3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: - - ```yaml - # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - :mailboxes: - - - # IMAP server host - :host: "imap.gmail.com" - # IMAP server port - :port: 993 - # Whether the IMAP server uses SSL - :ssl: true - # Whether the IMAP server uses StartTLS - :start_tls: false - # Email account username. Usually the full email address. - :email: "gitlab-incoming@gmail.com" - # Email account password - :password: "[REDACTED]" - - # The name of the mailbox where incoming mail will end up. Usually "inbox". - :name: "inbox" - - # Always "sidekiq". - :delivery_method: sidekiq - # Always true. - :delete_after_delivery: true - :delivery_options: - # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. - :redis_url: redis://localhost:6379 - # Always "resque:gitlab". - :namespace: resque:gitlab - # Always "incoming_email". - :queue: incoming_email - # Always "EmailReceiverWorker" - :worker: EmailReceiverWorker - - # Always "redis". - :arbitration_method: redis - :arbitration_options: - # The URL to the Redis server. Should match the URL in config/resque.yml. - :redis_url: redis://localhost:6379 - # Always "mail_room:gitlab". - :namespace: mail_room:gitlab - ``` + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. -4. Uncomment the `mail_room` line in your `Procfile`: +1. Uncomment the `mail_room` line in your `Procfile`: ```yaml mail_room: bundle exec mail_room -q -c config/mail_room.yml ``` -6. Restart GitLab: +1. Restart GitLab: ```sh bundle exec foreman start ``` -7. Verify that everything is configured correctly: +1. Verify that everything is configured correctly: ```sh bundle exec rake gitlab:incoming_email:check RAILS_ENV=development ``` -8. Reply by email should now be working. +1. Reply by email should now be working. diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 856ccc71084..9068d79c95e 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -24,12 +24,12 @@ module Gitlab match[1] end - private - def config Gitlab.config.incoming_email end + private + def address_regex wildcard_address = config.address return nil unless wildcard_address diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 66f1ecf385f..606bf241db7 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -642,7 +642,6 @@ namespace :gitlab do if Gitlab.config.incoming_email.enabled check_address_formatted_correctly - check_mail_room_config_exists check_imap_authentication if Rails.env.production? @@ -744,42 +743,16 @@ namespace :gitlab do end end - def check_mail_room_config_exists - print "MailRoom config exists? ... " - - mail_room_config_file = Rails.root.join("config", "mail_room.yml") - - if File.exists?(mail_room_config_file) - puts "yes".green - else - puts "no".red - try_fixing_it( - "Copy config/mail_room.yml.example to config/mail_room.yml", - "Check that the information in config/mail_room.yml is correct" - ) - for_more_information( - "doc/incoming_email/README.md" - ) - fix_and_rerun - end - end - def check_imap_authentication print "IMAP server credentials are correct? ... " - mail_room_config_file = Rails.root.join("config", "mail_room.yml") - - unless File.exists?(mail_room_config_file) - puts "can't check because of previous errors".magenta - return - end - - config = YAML.load_file(mail_room_config_file)[:mailboxes].first rescue nil + config = Gitlab.config.incoming_email if config begin - imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) - imap.login(config[:email], config[:password]) + imap = Net::IMAP.new(config.host, port: config.port, ssl: config.ssl) + imap.starttls if config.start_tls + imap.login(config.user, config.password) connected = true rescue connected = false @@ -791,7 +764,7 @@ namespace :gitlab do else puts "no".red try_fixing_it( - "Check that the information in config/mail_room.yml is correct" + "Check that the information in config/gitlab.yml is correct" ) for_more_information( "doc/incoming_email/README.md" -- cgit v1.2.1 From 8346dde0520ed625446ecc5d5a35b53e0b60dbb0 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 9 Oct 2015 20:07:29 +0300 Subject: Only render 404 page from /public --- CHANGELOG | 1 + app/controllers/application_controller.rb | 6 +----- app/controllers/import/bitbucket_controller.rb | 2 +- app/controllers/import/fogbugz_controller.rb | 2 +- app/controllers/import/github_controller.rb | 2 +- app/controllers/import/gitlab_controller.rb | 2 +- app/controllers/import/gitorious_controller.rb | 2 +- app/controllers/import/google_code_controller.rb | 2 +- app/controllers/projects/avatars_controller.rb | 2 +- app/controllers/projects/blob_controller.rb | 6 +++--- app/controllers/projects/raw_controller.rb | 2 +- app/controllers/projects/tree_controller.rb | 6 +++--- app/controllers/projects/uploads_controller.rb | 2 +- app/controllers/uploads_controller.rb | 6 +++--- lib/extracts_path.rb | 2 +- 15 files changed, 21 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8f0bbb12d7b..18fc76e444b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,7 @@ v 8.1.0 (unreleased) - Persist filters when sorting on admin user page (Jerry Lukins) - Add spellcheck=false to certain input fields - Invalidate stored service password if the endpoint URL is changed + - Only render 404 page from /public v 8.0.4 - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 527c9da0faa..2b2ea3dff16 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -30,7 +30,7 @@ class ApplicationController < ActionController::Base rescue_from ActiveRecord::RecordNotFound do |exception| log_exception(exception) - render "errors/not_found", layout: "errors", status: 404 + render_404 end protected @@ -149,10 +149,6 @@ class ApplicationController < ActionController::Base render "errors/access_denied", layout: "errors", status: 404 end - def not_found! - render "errors/not_found", layout: "errors", status: 404 - end - def git_not_found! render "errors/git_not_found", layout: "errors", status: 404 end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index f84f85a7df8..25e58724860 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController end def verify_bitbucket_import_enabled - not_found! unless bitbucket_import_enabled? + render_404 unless bitbucket_import_enabled? end def bitbucket_auth diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 849646cd665..18300390851 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -99,6 +99,6 @@ class Import::FogbugzController < Import::BaseController end def verify_fogbugz_import_enabled - not_found! unless fogbugz_import_enabled? + render_404 unless fogbugz_import_enabled? end end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index f21fbd9ecca..aae77d384c6 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -47,7 +47,7 @@ class Import::GithubController < Import::BaseController end def verify_github_import_enabled - not_found! unless github_import_enabled? + render_404 unless github_import_enabled? end def github_auth diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 27af19f5f61..23a396e8084 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -44,7 +44,7 @@ class Import::GitlabController < Import::BaseController end def verify_gitlab_import_enabled - not_found! unless gitlab_import_enabled? + render_404 unless gitlab_import_enabled? end def gitlab_auth diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb index f24cdb3709a..eecbe380c9e 100644 --- a/app/controllers/import/gitorious_controller.rb +++ b/app/controllers/import/gitorious_controller.rb @@ -42,7 +42,7 @@ class Import::GitoriousController < Import::BaseController end def verify_gitorious_import_enabled - not_found! unless gitorious_import_enabled? + render_404 unless gitorious_import_enabled? end end diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb index 82fadeb7e83..41472a6fe6c 100644 --- a/app/controllers/import/google_code_controller.rb +++ b/app/controllers/import/google_code_controller.rb @@ -106,7 +106,7 @@ class Import::GoogleCodeController < Import::BaseController end def verify_google_code_import_enabled - not_found! unless google_code_import_enabled? + render_404 unless google_code_import_enabled? end def user_map diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index 9c3763d5934..548f1b9ebfe 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -12,7 +12,7 @@ class Projects::AvatarsController < Projects::ApplicationController filename: @blob.name ) else - not_found! + render_404 end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index ae9b1384463..8cc2f21d887 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -113,14 +113,14 @@ class Projects::BlobController < Projects::ApplicationController end end - return not_found! + return render_404 end end def commit @commit = @repository.commit(@ref) - return not_found! unless @commit + return render_404 unless @commit end def assign_blob_vars @@ -128,7 +128,7 @@ class Projects::BlobController < Projects::ApplicationController @ref, @path = extract_ref(@id) rescue InvalidPathError - not_found! + render_404 end def after_edit_path diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 5f6fbce795e..d5ee6ac8663 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -20,7 +20,7 @@ class Projects::RawController < Projects::ApplicationController disposition: 'inline' ) else - not_found! + render_404 end end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 7eaff1d61ee..bdcb1a3e297 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -10,7 +10,7 @@ class Projects::TreeController < Projects::ApplicationController before_action :authorize_push_code!, only: [:create_dir] def show - return not_found! unless @repository.commit(@ref) + return render_404 unless @repository.commit(@ref) if tree.entries.empty? if @repository.blob_at(@commit.id, @path) @@ -19,7 +19,7 @@ class Projects::TreeController < Projects::ApplicationController File.join(@ref, @path)) ) and return elsif @path.present? - return not_found! + return render_404 end end @@ -31,7 +31,7 @@ class Projects::TreeController < Projects::ApplicationController end def create_dir - return not_found! unless @commit_params.values.all? + return render_404 unless @commit_params.values.all? begin result = Files::CreateDirService.new(@project, current_user, @commit_params).execute diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index 71ecc20dd95..e1fe7ea2114 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -20,7 +20,7 @@ class Projects::UploadsController < Projects::ApplicationController end def show - return not_found! if uploader.nil? || !uploader.file.exists? + return render_404 if uploader.nil? || !uploader.file.exists? disposition = uploader.image? ? 'inline' : 'attachment' send_file uploader.file.path, disposition: disposition diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 28536e359e5..868b05929d7 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -10,7 +10,7 @@ class UploadsController < ApplicationController end unless uploader.file && uploader.file.exists? - return not_found! + return render_404 end disposition = uploader.image? ? 'inline' : 'attachment' @@ -21,7 +21,7 @@ class UploadsController < ApplicationController def find_model unless upload_model && upload_mount - return not_found! + return render_404 end @model = upload_model.find(params[:id]) @@ -44,7 +44,7 @@ class UploadsController < ApplicationController return if authorized if current_user - not_found! + render_404 else authenticate_user! end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index 322aed5e27c..51e46da82cc 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -110,7 +110,7 @@ module ExtractsPath @project, @ref, @path) rescue RuntimeError, NoMethodError, InvalidPathError - not_found! + render_404 end def tree -- cgit v1.2.1 From 530f0d71f5d8157df3a469900e2fe0a81c9eaa5a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Oct 2015 01:07:58 -0400 Subject: Shut up, Rubocop --- spec/services/git_push_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 93bac384a68..fd72905c331 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -272,4 +272,4 @@ describe GitPushService do service.execute(project, user, @blankrev, @newrev, new_ref) end end -end \ No newline at end of file +end -- cgit v1.2.1 From 0fbb544c502a30c751a4a8c8f954f853aece93b2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Oct 2015 02:39:59 -0400 Subject: Update uglifier to ~> 2.7.2 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 967092994a6..5b6d4d8c087 100644 --- a/Gemfile +++ b/Gemfile @@ -196,7 +196,7 @@ gem 'charlock_holmes', '~> 0.6.9.4' gem "sass-rails", '~> 4.0.5' gem "coffee-rails", '~> 4.1.0' -gem "uglifier", '~> 2.3.2' +gem "uglifier", '~> 2.7.2' gem 'turbolinks', '~> 2.5.0' gem 'jquery-turbolinks', '~> 2.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 58426a60683..6fd4d99598f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -741,7 +741,7 @@ GEM simple_oauth (~> 0.1.4) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.3.3) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) underscore-rails (1.4.4) @@ -926,7 +926,7 @@ DEPENDENCIES thin (~> 1.6.1) tinder (~> 1.10.0) turbolinks (~> 2.5.0) - uglifier (~> 2.3.2) + uglifier (~> 2.7.2) underscore-rails (~> 1.4.4) unf (~> 0.1.4) unicorn (~> 4.8.2) -- cgit v1.2.1 From 613aa1cb87ee1000cb08bd15db1b62eae64f19dd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 07:21:15 +0000 Subject: Add defaults for incoming_email ssl and start_tls. --- config/initializers/1_settings.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index f04263c760b..432bf026ba2 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -187,9 +187,11 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci[ # Reply by email # Settings['incoming_email'] ||= Settingslogic.new({}) -Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil? -Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil? -Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil? +Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil? +Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil? +Settings.incoming_email['ssl'] = 143 if Settings.incoming_email['ssl'].nil? +Settings.incoming_email['start_tls'] = 143 if Settings.incoming_email['start_tls'].nil? +Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil? # # Gravatar @@ -267,4 +269,4 @@ if Rails.env.test? Settings.gitlab['default_projects_limit'] = 42 Settings.gitlab['default_can_create_group'] = true Settings.gitlab['default_can_create_team'] = false -end +end \ No newline at end of file -- cgit v1.2.1 From 276b3a7bc202bd9b51c8f5401f4c525227f3a4d8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 09:27:30 +0200 Subject: Make Mentionable#cross_reference_exists? private. --- app/models/concerns/mentionable.rb | 12 ++++++------ spec/support/mentionable_shared_examples.rb | 7 ------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 5f53ea25630..b34def66d2e 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -41,12 +41,6 @@ module Mentionable self end - # Determine whether or not a cross-reference Note has already been created between this Mentionable and - # the specified target. - def cross_reference_exists?(target) - SystemNoteService.cross_reference_exists?(target, local_reference) - end - def all_references(current_user = self.author, text = self.mentionable_text) ext = Gitlab::ReferenceExtractor.new(self.project, current_user) ext.analyze(text) @@ -111,4 +105,10 @@ module Mentionable # Only include changed fields that are mentionable source.select { |key, val| mentionable.include?(key) } end + + # Determine whether or not a cross-reference Note has already been created between this Mentionable and + # the specified target. + def cross_reference_exists?(target) + SystemNoteService.cross_reference_exists?(target, local_reference) + end end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 412c6f4ead8..f584904845e 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -86,13 +86,6 @@ shared_examples 'a mentionable' do subject.create_cross_references! end - - it 'detects existing cross-references' do - SystemNoteService.cross_reference(mentioned_issue, subject.local_reference, author) - - expect(subject.cross_reference_exists?(mentioned_issue)).to be_truthy - expect(subject.cross_reference_exists?(mentioned_mr)).to be_falsey - end end shared_examples 'an editable mentionable' do -- cgit v1.2.1 From 033a879cc96c6210175f03f72547d05a2946c0bb Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 14 Oct 2015 11:12:21 +0200 Subject: Fix NGINX API download regex Users are allowed to supply namespace%2Fproject instead of a numeric ID --- lib/support/nginx/gitlab | 2 +- lib/support/nginx/gitlab-ssl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index ffc0eb0585c..1e55c5a0486 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -125,7 +125,7 @@ server { return 418; } - location ~ ^/api/v3/projects/[0-9]+/repository/archive { + location ~ ^/api/v3/projects/.*/repository/archive { # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block error_page 418 = @gitlab-git-http-server; return 418; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index c2e9f8864f8..08641bbcc17 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -172,7 +172,7 @@ server { return 418; } - location ~ ^/api/v3/projects/[0-9]+/repository/archive { + location ~ ^/api/v3/projects/.*/repository/archive { # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block error_page 418 = @gitlab-git-http-server; return 418; -- cgit v1.2.1 From fc94b3b036c565753b4ae9740ddeeaa40525758b Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 14 Oct 2015 12:13:17 +0200 Subject: Fix API archive specs --- spec/requests/api/repositories_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 09a79553f72..1149f7e7989 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -166,24 +166,21 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/archive", user) repo_name = project.repository.name.gsub("\.git", "") expect(response.status).to eq(200) - expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/) - expect(response.content_type).to eq(MIME::Types.type_for('file.tar.gz').first.content_type) + expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/) end it "should get the archive.zip" do get api("/projects/#{project.id}/repository/archive.zip", user) repo_name = project.repository.name.gsub("\.git", "") expect(response.status).to eq(200) - expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.zip\"/) - expect(response.content_type).to eq(MIME::Types.type_for('file.zip').first.content_type) + expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/) end it "should get the archive.tar.bz2" do get api("/projects/#{project.id}/repository/archive.tar.bz2", user) repo_name = project.repository.name.gsub("\.git", "") expect(response.status).to eq(200) - expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/) - expect(response.content_type).to eq(MIME::Types.type_for('file.tar.bz2').first.content_type) + expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/) end it "should return 404 for invalid sha" do -- cgit v1.2.1 From 8f14332625d2031cb8275d1a4c8293120a25538d Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 14 Oct 2015 12:17:59 +0200 Subject: Remove RepositoryArchiveWorker specs These tasks have shifted to gitlab_git and gitlab-git-http-server. --- spec/services/archive_repository_service_spec.rb | 67 +------------------- spec/workers/repository_archive_worker_spec.rb | 79 ------------------------ 2 files changed, 1 insertion(+), 145 deletions(-) delete mode 100644 spec/workers/repository_archive_worker_spec.rb diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb index 0ec70c51b3a..1cc7b240216 100644 --- a/spec/services/archive_repository_service_spec.rb +++ b/spec/services/archive_repository_service_spec.rb @@ -13,7 +13,7 @@ describe ArchiveRepositoryService do context "when the repository doesn't have an archive file path" do before do - allow(project.repository).to receive(:archive_file_path).and_return(nil) + allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) end it "raises an error" do @@ -21,70 +21,5 @@ describe ArchiveRepositoryService do end end - context "when the repository has an archive file path" do - let(:file_path) { "/archive.zip" } - let(:pid_file_path) { "/archive.zip.pid" } - - before do - allow(project.repository).to receive(:archive_file_path).and_return(file_path) - allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path) - end - - context "when the archive file already exists" do - before do - allow(File).to receive(:exist?).with(file_path).and_return(true) - end - - it "returns the file path" do - expect(subject.execute(timeout: 0.0)).to eq(file_path) - end - end - - context "when the archive file doesn't exist yet" do - before do - allow(File).to receive(:exist?).with(file_path).and_return(false) - allow(File).to receive(:exist?).with(pid_file_path).and_return(true) - end - - context "when the archive pid file doesn't exist yet" do - before do - allow(File).to receive(:exist?).with(pid_file_path).and_return(false) - end - - it "queues the RepositoryArchiveWorker" do - expect(RepositoryArchiveWorker).to receive(:perform_async) - - subject.execute(timeout: 0.0) - end - end - - context "when the archive pid file already exists" do - it "doesn't queue the RepositoryArchiveWorker" do - expect(RepositoryArchiveWorker).not_to receive(:perform_async) - - subject.execute(timeout: 0.0) - end - end - - context "when the archive file exists after a little while" do - before do - Thread.new do - sleep 0.1 - allow(File).to receive(:exist?).with(file_path).and_return(true) - end - end - - it "returns the file path" do - expect(subject.execute(timeout: 0.2)).to eq(file_path) - end - end - - context "when the archive file doesn't exist after the timeout" do - it "returns nil" do - expect(subject.execute(timeout: 0.0)).to eq(nil) - end - end - end - end end end diff --git a/spec/workers/repository_archive_worker_spec.rb b/spec/workers/repository_archive_worker_spec.rb deleted file mode 100644 index a914d0ac8dc..00000000000 --- a/spec/workers/repository_archive_worker_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'spec_helper' - -describe RepositoryArchiveWorker do - let(:project) { create(:project) } - subject { RepositoryArchiveWorker.new } - - before do - allow(Project).to receive(:find).and_return(project) - end - - describe "#perform" do - it "cleans old archives" do - expect(project.repository).to receive(:clean_old_archives) - - subject.perform(project.id, "master", "zip") - end - - context "when the repository doesn't have an archive file path" do - before do - allow(project.repository).to receive(:archive_file_path).and_return(nil) - end - - it "doesn't archive the repo" do - expect(project.repository).not_to receive(:archive_repo) - - subject.perform(project.id, "master", "zip") - end - end - - context "when the repository has an archive file path" do - let(:file_path) { "/archive.zip" } - let(:pid_file_path) { "/archive.zip.pid" } - - before do - allow(project.repository).to receive(:archive_file_path).and_return(file_path) - allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path) - end - - context "when the archive file already exists" do - before do - allow(File).to receive(:exist?).with(file_path).and_return(true) - end - - it "doesn't archive the repo" do - expect(project.repository).not_to receive(:archive_repo) - - subject.perform(project.id, "master", "zip") - end - end - - context "when the archive file doesn't exist yet" do - before do - allow(File).to receive(:exist?).with(file_path).and_return(false) - allow(File).to receive(:exist?).with(pid_file_path).and_return(true) - end - - context "when the archive pid file doesn't exist yet" do - before do - allow(File).to receive(:exist?).with(pid_file_path).and_return(false) - end - - it "archives the repo" do - expect(project.repository).to receive(:archive_repo) - - subject.perform(project.id, "master", "zip") - end - end - - context "when the archive pid file already exists" do - it "doesn't archive the repo" do - expect(project.repository).not_to receive(:archive_repo) - - subject.perform(project.id, "master", "zip") - end - end - end - end - end -end -- cgit v1.2.1 From 3a69dd20a1d8e54c25f871f35f09a1b8b5d4b2a4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 12:33:39 +0200 Subject: Allow dashboard and group issues/MRs to be filtered by label --- CHANGELOG | 1 + app/helpers/labels_helper.rb | 18 +++++++++++++----- app/models/group_label.rb | 9 +++++++++ app/models/group_milestone.rb | 12 ++---------- app/services/labels/group_service.rb | 26 ++++++++++++++++++++++++++ app/views/shared/issuable/_filter.html.haml | 9 ++++----- 6 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 app/models/group_label.rb create mode 100644 app/services/labels/group_service.rb diff --git a/CHANGELOG b/CHANGELOG index a3d796bea66..0a0afb4a053 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.1.0 (unreleased) - Fix position of hamburger in header for smaller screens (Han Loong Liauw) - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) - Persist filters when sorting on admin user page (Jerry Lukins) + - Allow dashboard and group issues/MRs to be filtered by label v 8.0.4 - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 66b18eea699..ee04ace35d0 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -92,11 +92,19 @@ module LabelsHelper end end - def project_labels_options(project) - labels = project.labels.to_a - labels.unshift(Label::None) - labels.unshift(Label::Any) - options_from_collection_for_select(labels, 'name', 'title', params[:label_name]) + def projects_labels_options + labels = + if @project + @project.labels + else + Label.where(project_id: @projects) + end + + grouped_labels = Labels::GroupService.new(labels).execute + grouped_labels.unshift(Label::None) + grouped_labels.unshift(Label::Any) + + options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name]) end # Required for Gitlab::Markdown::LabelReferenceFilter diff --git a/app/models/group_label.rb b/app/models/group_label.rb new file mode 100644 index 00000000000..0fc39cb8771 --- /dev/null +++ b/app/models/group_label.rb @@ -0,0 +1,9 @@ +class GroupLabel + attr_accessor :title, :labels + alias_attribute :name, :title + + def initialize(title, labels) + @title = title + @labels = labels + end +end diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 1dd2be68ebf..91844da62e2 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -1,5 +1,5 @@ class GroupMilestone - + attr_accessor :title, :milestones alias_attribute :name, :title def initialize(title, milestones) @@ -7,18 +7,10 @@ class GroupMilestone @milestones = milestones end - def title - @title - end - def safe_title @title.parameterize end - - def milestones - @milestones - end - + def projects milestones.map { |milestone| milestone.project } end diff --git a/app/services/labels/group_service.rb b/app/services/labels/group_service.rb new file mode 100644 index 00000000000..b26cee24d56 --- /dev/null +++ b/app/services/labels/group_service.rb @@ -0,0 +1,26 @@ +module Labels + class GroupService < ::BaseService + def initialize(project_labels) + @project_labels = project_labels.group_by(&:title) + end + + def execute + build(@project_labels) + end + + def label(title) + if title + group_label = @project_labels[title].group_by(&:title) + build(group_label).first + else + nil + end + end + + private + + def build(label) + label.map { |title, labels| GroupLabel.new(title, labels) } + end + end +end diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 8f16773077e..0e4e9c0987a 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -42,11 +42,10 @@ class: 'select2 trigger-submit', include_blank: true, data: {placeholder: 'Milestone'}) - - if @project - .filter-item.inline.labels-filter - = select_tag('label_name', project_labels_options(@project), - class: 'select2 trigger-submit', include_blank: true, - data: {placeholder: 'Label'}) + .filter-item.inline.labels-filter + = select_tag('label_name', projects_labels_options, + class: 'select2 trigger-submit', include_blank: true, + data: {placeholder: 'Label'}) .pull-right = render 'shared/sort_dropdown' -- cgit v1.2.1 From 33c9d6e45c0800fd5a312af2c286826764fd7589 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 13 Oct 2015 16:51:13 +0200 Subject: Fix retry and cancel for build --- app/models/ci/build.rb | 4 ++-- app/views/projects/commit/ci.html.haml | 22 +++++++++++++--------- .../commit_statuses/_commit_status.html.haml | 6 +++--- spec/features/commits_spec.rb | 10 +++++++++- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f8c731a7bf7..cfafbf6786e 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -220,14 +220,14 @@ module Ci def cancel_url if active? Gitlab::Application.routes.url_helpers. - cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url) + cancel_namespace_project_build_path(gl_project.namespace, gl_project, self) end end def retry_url if commands.present? Gitlab::Application.routes.url_helpers. - cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url) + retry_namespace_project_build_path(gl_project.namespace, gl_project, self) end end diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml index 4a1ef378a30..ca71a91af15 100644 --- a/app/views/projects/commit/ci.html.haml +++ b/app/views/projects/commit/ci.html.haml @@ -3,11 +3,6 @@ = render "commit_box" = render "ci_menu" -- if @ci_project && current_user && can?(current_user, :manage_builds, @project) - .pull-right - - if @ci_commit.builds.running_or_pending.any? - = link_to "Cancel", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-sm btn-danger' - - if @ci_commit.yaml_errors.present? .bs-callout.bs-callout-danger @@ -22,11 +17,18 @@ .gray-content-block.second-block Latest builds - - if @ci_commit.duration > 0 - %small.pull-right + + .pull-right + - if @ci_commit.duration > 0 %i.fa.fa-time #{time_interval_in_words @ci_commit.duration} +   + + - if @ci_project && current_user && can?(current_user, :manage_builds, @project) + - if @ci_commit.builds.running_or_pending.any? + = link_to "Cancel all", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger' + %table.table.builds %thead %tr @@ -41,7 +43,8 @@ %th Coverage %th - @ci_commit.refs.each do |ref| - = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, coverage: @ci_project.try(:coverage_enabled?), controls: true + = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, + locals: { coverage: @ci_project.try(:coverage_enabled?), allow_retry: true } - if @ci_commit.retried.any? .gray-content-block.second-block @@ -60,4 +63,5 @@ - if @ci_project && @ci_project.coverage_enabled? %th Coverage %th - = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, coverage: @ci_project.try(:coverage_enabled?) + = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, + locals: { coverage: @ci_project.try(:coverage_enabled?) } diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml index e3a17faf0bd..7314f8e79d3 100644 --- a/app/views/projects/commit_statuses/_commit_status.html.haml +++ b/app/views/projects/commit_statuses/_commit_status.html.haml @@ -41,11 +41,11 @@ #{commit_status.coverage}% %td - - if defined?(controls) && controls && current_user && can?(current_user, :manage_builds, gl_project) - .pull-right + .pull-right + - if current_user && can?(current_user, :manage_builds, commit_status.gl_project) - if commit_status.cancel_url = link_to commit_status.cancel_url, title: 'Cancel' do %i.fa.fa-remove.cred - - elsif commit_status.retry_url + - elsif defined?(allow_retry) && allow_retry && commit_status.retry_url = link_to commit_status.retry_url, method: :post, title: 'Retry' do %i.fa.fa-repeat diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index cbb6360069b..1adc2cdf70a 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -29,8 +29,16 @@ describe "Commits" do it { expect(page).to have_content @commit.git_author_name } end - describe "Cancel commit" do + describe "Cancel all builds" do it "cancels commit" do + visit ci_status_path(@commit) + click_on "Cancel all" + expect(page).to have_content "canceled" + end + end + + describe "Cancel build" do + it "cancels build" do visit ci_status_path(@commit) click_on "Cancel" expect(page).to have_content "canceled" -- cgit v1.2.1 From 381ca79bfa59d8d78a8bb1a7ee60cb46a87e4929 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 14 Oct 2015 15:21:14 +0200 Subject: Remove archive file sending spec This is done by gitlab-git-http-server now. --- .../projects/repositories_controller_spec.rb | 28 ---------------------- 1 file changed, 28 deletions(-) diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 91856ed0cc0..18a30033ed8 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -33,33 +33,5 @@ describe Projects::RepositoriesController do expect(response.status).to eq(404) end end - - context "when the service doesn't return a path" do - - before do - allow(service).to receive(:execute).and_return(nil) - end - - it "reloads the page" do - get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" - - expect(response).to redirect_to(archive_namespace_project_repository_path(project.namespace, project, ref: "master", format: "zip")) - end - end - - context "when the service returns a path" do - - let(:path) { Rails.root.join("spec/fixtures/dk.png").to_s } - - before do - allow(service).to receive(:execute).and_return(path) - end - - it "sends the file" do - get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" - - expect(response.body).to eq(File.binread(path)) - end - end end end -- cgit v1.2.1 From a74915a4adb4bc116f039dfb2438ae97ffde4e7e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 14 Oct 2015 15:22:03 +0200 Subject: Always return HTML in git_not_found This allows us to give a nice 404 for e.g. archive.zip. --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 527c9da0faa..be217e121b0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -154,7 +154,7 @@ class ApplicationController < ActionController::Base end def git_not_found! - render "errors/git_not_found", layout: "errors", status: 404 + render html: "errors/git_not_found", layout: "errors", status: 404 end def method_missing(method_sym, *arguments, &block) -- cgit v1.2.1 From b46397548056e4e8ef00efe4f641c61ba1dd5230 Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Tue, 13 Oct 2015 11:29:57 +0200 Subject: Improve invalidation of stored service password if the endpoint URL is changed It now allows to specify a password at the same time as the new URL, and works on the service template admin pages. --- app/controllers/admin/services_controller.rb | 8 ++++- app/controllers/projects/services_controller.rb | 8 ++++- app/models/project_services/bamboo_service.rb | 2 +- app/models/project_services/teamcity_service.rb | 2 +- app/models/service.rb | 32 ++++++++++++++++---- spec/models/service_spec.rb | 39 +++++++++++++++++++++++-- 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index a62170662e1..46133588332 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController end def application_services_params - params.permit(:id, + application_services_params = params.permit(:id, service: Projects::ServicesController::ALLOWED_PARAMS) + if application_services_params[:service].is_a?(Hash) + Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param| + application_services_params[:service].delete(param) if application_services_params[:service][param].blank? + end + end + application_services_params end end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 3047ee8a1ff..129068ef019 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, :server_host, :server_port, :default_irc_uri, :enable_ssl_verification] + + # Parameters to ignore if no value is specified + FILTER_BLANK_PARAMS = [:password] + # Authorize before_action :authorize_admin_project! before_action :service, only: [:edit, :update, :test] @@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController def service_params service_params = params.require(:service).permit(ALLOWED_PARAMS) - service_params.delete("password") if service_params["password"].blank? + FILTER_BLANK_PARAMS.each do |param| + service_params.delete(param) if service_params[param].blank? + end service_params end end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 5f5255ab487..4a18c772e50 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -48,7 +48,7 @@ class BambooService < CiService end def reset_password - if prop_updated?(:bamboo_url) + if prop_modified?(:bamboo_url) && !prop_updated?(:password) self.password = nil end end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index fb11cad352e..a548a557dfe 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -45,7 +45,7 @@ class TeamcityService < CiService end def reset_password - if prop_updated?(:teamcity_url) + if prop_modified?(:teamcity_url) && !prop_updated?(:password) self.password = nil end end diff --git a/app/models/service.rb b/app/models/service.rb index 7e845d565b1..946ea1a096b 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -33,6 +33,8 @@ class Service < ActiveRecord::Base after_initialize :initialize_properties + after_commit :reset_updated_properties + belongs_to :project has_one :service_hook @@ -103,6 +105,7 @@ class Service < ActiveRecord::Base # Provide convenient accessor methods # for each serialized property. + # Also keep track of updated properties. def self.prop_accessor(*args) args.each do |arg| class_eval %{ @@ -111,19 +114,36 @@ class Service < ActiveRecord::Base end def #{arg}=(value) + updated_properties['#{arg}'] = #{arg} unless updated_properties.include?('#{arg}') self.properties['#{arg}'] = value end } end end - # ActiveRecord does not provide a mechanism to track changes in serialized keys. - # This is why we need to perform extra query to do it mannually. + # Returns a hash of the properties that have been assigned a new value since last save, + # indicating their original values (attr => original value). + # ActiveRecord does not provide a mechanism to track changes in serialized keys, + # so we need a specific implementation for service properties. + # This allows to track changes to properties set with the accessor methods, + # but not direct manipulation of properties hash. + def updated_properties + @updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new + end + def prop_updated?(prop_name) - relation_name = self.type.underscore - previous_value = project.send(relation_name).send(prop_name) - return false if previous_value.nil? - previous_value != send(prop_name) + # Check if a new value was set. + # The new value may or may not be the same as the old value + updated_properties.include?(prop_name) + end + + def prop_modified?(prop_name) + # Check if new value was set and it is different from the old value + prop_updated?(prop_name) && updated_properties[prop_name] != send(prop_name) + end + + def reset_updated_properties + @updated_properties = nil end def async_execute(data) diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index da87ea5b84f..d42b96294ba 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -116,14 +116,47 @@ describe Service do ) end - it "returns false" do + it "returns false when the property has not been assigned a new value" do service.username = "key_changed" expect(service.prop_updated?(:bamboo_url)).to be_falsy end - it "returns true" do - service.bamboo_url = "http://other.com" + it "returns true when the property has been assigned a different value" do + service.bamboo_url = "http://example.com" expect(service.prop_updated?(:bamboo_url)).to be_truthy end + + it "returns true when the property has been re-assigned the same value" do + service.bamboo_url = 'http://gitlab.com' + expect(service.prop_updated?(:bamboo_url)).to be_truthy + end + end + + describe "#prop_modified?" do + let(:service) do + BambooService.create( + project: create(:project), + properties: { + bamboo_url: 'http://gitlab.com', + username: 'mic', + password: "password" + } + ) + end + + it "returns false when the property has not been assigned a new value" do + service.username = "key_changed" + expect(service.prop_modified?(:bamboo_url)).to be_falsy + end + + it "returns true when the property has been assigned a different value" do + service.bamboo_url = "http://example.com" + expect(service.prop_modified?(:bamboo_url)).to be_truthy + end + + it "returns false when the property has been re-assigned the same value" do + service.bamboo_url = 'http://gitlab.com' + expect(service.prop_modified?(:bamboo_url)).to be_falsy + end end end -- cgit v1.2.1 From 2d0fcb4de23ab368e2e030b3cf7f6b1705ef676f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 17:26:24 +0200 Subject: Fix spinach tests introduced by 07101cfab61f28c6328efebea98f018ab8356cdd --- features/dashboard/new_project.feature | 10 +++++----- features/steps/dashboard/new_project.rb | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature index bbd82a85e3a..76392068357 100644 --- a/features/dashboard/new_project.feature +++ b/features/dashboard/new_project.feature @@ -7,24 +7,24 @@ Background: And I click "New project" link @javascript - Scenario: I should see New projects page - Then I see "New project" page + Scenario: I should see New Projects page + Then I see "New Project" page Then I see all possible import optios @javascript Scenario: I should see instructions on how to import from Git URL - Given I see "New project" page + Given I see "New Project" page When I click on "Any repo by URL" Then I see instructions on how to import from Git URL @javascript Scenario: I should see instructions on how to import from GitHub - Given I see "New project" page + Given I see "New Project" page When I click on "Import project from GitHub" Then I see instructions on how to import from GitHub @javascript Scenario: I should see Google Code import page - Given I see "New project" page + Given I see "New Project" page When I click on "Google Code" Then I redirected to Google Code import page diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 1e09162a5b5..44a4aa9844a 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -3,13 +3,13 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps include SharedPaths include SharedProject - step 'I click "New project" link' do + step 'I click "New Project" link' do page.within('.content') do - click_link "New project" + click_link "New Project" end end - step 'I see "New project" page' do + step 'I see "New Project" page' do expect(page).to have_content('Project path') end -- cgit v1.2.1 From 7b5ab3ded5e6f5d88f04802d5a71a9ae94d20f92 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 12 Oct 2015 17:06:53 +0200 Subject: Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds --- CHANGELOG | 1 + app/models/ci/build.rb | 11 ++++++++++- doc/ci/variables/README.md | 38 +++++++++++++++++++++++-------------- spec/models/build_spec.rb | 34 +++++++++++++++++++++++++++++---- spec/requests/ci/api/builds_spec.rb | 5 +++++ 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e3d21f7e54..91ce558e2a7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.1.0 (unreleased) - Add first and last to pagination (Zeger-Jan van de Weg) - Added Commit Status API - Show CI status on commit page + - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds - Show CI status on Your projects page and Starred projects page - Remove "Continuous Integration" page from dashboard - Add notes and SSL verification entries to hook APIs (Ben Boeckel) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index cfafbf6786e..481440e869f 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -119,7 +119,7 @@ module Ci end def variables - yaml_variables + project_variables + trigger_variables + predefined_variables + yaml_variables + project_variables + trigger_variables end def project @@ -258,5 +258,14 @@ module Ci [] end end + + def predefined_variables + variables = [] + variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag + variables << { key: :CI_BUILD_NAME, value: name, public: true } + variables << { key: :CI_BUILD_STAGE, value: stage, public: true } + variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request + variables + end end end diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 04c6bf1e3a3..022afb70042 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -15,21 +15,27 @@ The API_TOKEN will take the Secure Variable value: `SECURE`. ### Predefined variables (Environment Variables) -| Variable | Description | +| Variable | Runner | Description | |-------------------------|-------------| -| **CI** | Mark that build is executed in CI environment | -| **GITLAB_CI** | Mark that build is executed in GitLab CI environment | -| **CI_SERVER** | Mark that build is executed in CI environment | -| **CI_SERVER_NAME** | CI server that is used to coordinate builds | -| **CI_SERVER_VERSION** | Not yet defined | -| **CI_SERVER_REVISION** | Not yet defined | -| **CI_BUILD_REF** | The commit revision for which project is built | -| **CI_BUILD_BEFORE_SHA** | The first commit that were included in push request | -| **CI_BUILD_REF_NAME** | The branch or tag name for which project is built | -| **CI_BUILD_ID** | The unique id of the current build that GitLab CI uses internally | -| **CI_BUILD_REPO** | The URL to clone the Git repository | -| **CI_PROJECT_ID** | The unique id of the current project that GitLab CI uses internally | -| **CI_PROJECT_DIR** | The full path where the repository is cloned and where the build is ran | +| **CI** | 0.4 | Mark that build is executed in CI environment | +| **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment | +| **CI_SERVER** | all | Mark that build is executed in CI environment | +| **CI_SERVER_NAME** | all | CI server that is used to coordinate builds | +| **CI_SERVER_VERSION** | all | Not yet defined | +| **CI_SERVER_REVISION** | all | Not yet defined | +| **CI_BUILD_REF** | all | The commit revision for which project is built | +| **CI_BUILD_TAG** | 0.5 | The commit tag name. Present only when building tags. | +| **CI_BUILD_NAME** | 0.5 | The name of the build as defined in `.gitlab-ci.yml` | +| **CI_BUILD_STAGE** | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` | +| **CI_BUILD_BEFORE_SHA** | all | The first commit that were included in push request | +| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built | +| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally | +| **CI_BUILD_REPO** | all | The URL to clone the Git repository | +| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was triggered | +| **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally | +| **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran | + +**Some of the variables are only available when using runner with at least defined version.** Example values: @@ -39,6 +45,10 @@ export CI_BUILD_ID="50" export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a" export CI_BUILD_REF_NAME="master" export CI_BUILD_REPO="https://gitlab.com/gitlab-org/gitlab-ce.git" +export CI_BUILD_TAG="1.0.0" +export CI_BUILD_NAME="spec:other" +export CI_BUILD_STAGE="test" +export CI_BUILD_TRIGGERED="true" export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce" export CI_PROJECT_ID="34" export CI_SERVER="yes" diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index d875015b991..7f999581ec7 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -200,13 +200,34 @@ describe Ci::Build do context 'returns variables' do subject { build.variables } - let(:variables) do + let(:predefined_variables) do + [ + { key: :CI_BUILD_NAME, value: 'test', public: true }, + { key: :CI_BUILD_STAGE, value: 'stage', public: true }, + ] + end + + let(:yaml_variables) do [ { key: :DB_NAME, value: 'postgres', public: true } ] end - it { is_expected.to eq(variables) } + before { build.update_attributes(stage: 'stage') } + + it { is_expected.to eq(predefined_variables + yaml_variables) } + + context 'for tag' do + let(:tag_variable) do + [ + { key: :CI_BUILD_TAG, value: 'master', public: true } + ] + end + + before { build.update_attributes(tag: true) } + + it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) } + end context 'and secure variables' do let(:secure_variables) do @@ -219,7 +240,7 @@ describe Ci::Build do build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value') end - it { is_expected.to eq(variables + secure_variables) } + it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) } context 'and trigger variables' do let(:trigger) { FactoryGirl.create :ci_trigger, project: project } @@ -229,12 +250,17 @@ describe Ci::Build do { key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false } ] end + let(:predefined_trigger_variable) do + [ + { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } + ] + end before do build.trigger_request = trigger_request end - it { is_expected.to eq(variables + secure_variables + trigger_variables) } + it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) } end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 54c1d0199f6..88218a93e1f 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -76,6 +76,8 @@ describe Ci::API::API do expect(response.status).to eq(201) expect(json_response["variables"]).to eq([ + { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, + { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true }, { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, ]) @@ -93,6 +95,9 @@ describe Ci::API::API do expect(response.status).to eq(201) expect(json_response["variables"]).to eq([ + { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, + { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, + { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true }, { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, { "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false }, -- cgit v1.2.1 From 58074195198e715045dc5280aed9515297fe7ad3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 13 Oct 2015 17:00:55 +0200 Subject: Use tag? instead of tag to indicate that this is boolean --- app/models/ci/build.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 481440e869f..23aed6d9952 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -261,7 +261,7 @@ module Ci def predefined_variables variables = [] - variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag + variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag? variables << { key: :CI_BUILD_NAME, value: name, public: true } variables << { key: :CI_BUILD_STAGE, value: stage, public: true } variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request -- cgit v1.2.1 From a957eca6f364b7587175a6ffa647fc9df80abed9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 12:15:03 +0200 Subject: Added builds view --- CHANGELOG | 1 + app/assets/javascripts/shortcuts_navigation.coffee | 1 + app/controllers/projects/builds_controller.rb | 25 ++++++++++- app/helpers/gitlab_routing_helper.rb | 4 ++ app/helpers/projects_helper.rb | 4 ++ app/helpers/runners_helper.rb | 13 ++++++ app/models/ability.rb | 2 + app/models/ci/runner.rb | 4 +- app/views/help/_shortcuts.html.haml | 6 +++ app/views/layouts/nav/_project.html.haml | 9 +++- app/views/layouts/nav/_project_settings.html.haml | 2 +- app/views/projects/builds/_build.html.haml | 52 ++++++++++++++++++++++ app/views/projects/builds/index.html.haml | 51 +++++++++++++++++++++ config/routes.rb | 6 ++- spec/features/builds_spec.rb | 49 ++++++++++++++++++++ 15 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 app/views/projects/builds/_build.html.haml create mode 100644 app/views/projects/builds/index.html.haml diff --git a/CHANGELOG b/CHANGELOG index 9e3d21f7e54..8e71c8afb2c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.1.0 (unreleased) - Fix cases where Markdown did not render links in activity feed (Stan Hu) - Add first and last to pagination (Zeger-Jan van de Weg) - Added Commit Status API + - Added Builds View - Show CI status on commit page - Show CI status on Your projects page and Starred projects page - Remove "Continuous Integration" page from dashboard diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee index 5b6f9e7e3f2..8decaedd87b 100644 --- a/app/assets/javascripts/shortcuts_navigation.coffee +++ b/app/assets/javascripts/shortcuts_navigation.coffee @@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity')) Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree')) Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits')) + Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds')) Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network')) Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs')) Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues')) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 4e4ac6689d3..0bcd9a8a360 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -1,11 +1,32 @@ class Projects::BuildsController < Projects::ApplicationController before_action :ci_project - before_action :build + before_action :build, except: [:index, :cancel_all] - before_action :authorize_admin_project!, except: [:show, :status] + before_action :authorize_admin_project!, except: [:index, :show, :status] layout "project" + def index + @scope = params[:scope] + @all_builds = project.ci_builds.order('created_at DESC').page(params[:page]).per(30) + + @builds = + case @scope + when 'pending' + @all_builds.pending + when 'running' + @all_builds.running + else + @all_builds + end + end + + def cancel_all + @project.ci_builds.running_or_pending.each(&:cancel) + + redirect_to namespace_project_builds_path(project.namespace, project) + end + def show @builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20) diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 4d9da6ff837..b0b536d4649 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -25,6 +25,10 @@ module GitlabRoutingHelper namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) end + def project_builds_path(project, *args) + namespace_project_builds_path(project.namespace, project, *args) + end + def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a0220af4c30..b7965aee875 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -113,6 +113,10 @@ module ProjectsHelper nav_tabs << :merge_requests end + if can?(current_user, :read_build, project) + nav_tabs << :builds + end + if can?(current_user, :admin_project, project) nav_tabs << :settings end diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb index 5d7d06c8490..c13db93ae9c 100644 --- a/app/helpers/runners_helper.rb +++ b/app/helpers/runners_helper.rb @@ -17,4 +17,17 @@ module RunnersHelper class: "fa fa-circle runner-status-#{status}", title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago" end + + def runner_link(runner) + display_name = truncate(runner.display_name, length: 20) + id = "\##{runner.id}" + + if current_user && current_user.admin + link_to ci_admin_runner_path(runner) do + display_name + id + end + else + display_name + id + end + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 77c121ca5e8..38bc2086683 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -41,6 +41,7 @@ class Ability :read_project_member, :read_merge_request, :read_note, + :read_build, :download_code ] @@ -127,6 +128,7 @@ class Ability :read_project_member, :read_merge_request, :read_note, + :read_build, :create_project, :create_issue, :create_note diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 6838ccfaaab..bc5cd137e91 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -56,7 +56,7 @@ module Ci end def display_name - return token unless !description.blank? + return short_sha unless !description.blank? description end @@ -78,7 +78,7 @@ module Ci end def short_sha - token[0...10] + token[0...8] end end end diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index e809d99ba71..67349fcbd78 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -99,6 +99,12 @@ .key c %td Go to commits + %tr + %td.shortcut + .key g + .key b + %td + Go to builds %tr %td.shortcut .key g diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index e4c285d8023..f52d0ad9c02 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -32,12 +32,19 @@ Files - if project_nav_tab? :commits - = nav_link(controller: %w(commit commits compare repositories tags branches builds)) do + = nav_link(controller: %w(commit commits compare repositories tags branches)) do = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do = icon('history fw') %span Commits + - if project_nav_tab? :builds + = nav_link(controller: %w(builds)) do + = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do + = icon('link fw') + %span + Builds + - if project_nav_tab? :network = nav_link(controller: %w(network)) do = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 954dbe5d2b9..b12dcd84116 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -66,7 +66,7 @@ %span CI Services = nav_link path: 'events#index' do - = link_to ci_project_events_path(@project.gitlab_ci_project) do + = link_to ci_project_events_path(@project.ensure_gitlab_ci_project) do = icon('book fw') %span CI Events diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml new file mode 100644 index 00000000000..10b3ef71202 --- /dev/null +++ b/app/views/projects/builds/_build.html.haml @@ -0,0 +1,52 @@ +%tr.build + %td.status + = ci_status_with_icon(build.status) + + %td.commit_status-link + - if build.target_url + = link_to build.target_url do + %strong Build ##{build.id} + - else + %strong Build ##{build.id} + + %td + = link_to namespace_project_commit_path(@project.namespace, @project, build.sha) do + = build.short_sha + + %td + = link_to namespace_project_commits_path(@project.namespace, @project, build.ref) do + = build.ref + + %td + - if build.runner + = runner_link(build.runner) + - else + .light none + + %td + = build.name + + .pull-right + - if build.tags.any? + - build.tags.each do |tag| + %span.label.label-primary + = tag + - if build.try(:trigger_request) + %span.label.label-info triggered + - if build.try(:allow_failure) + %span.label.label-danger allowed to fail + + %td.duration + - if build.duration + #{duration_in_words(build.finished_at, build.started_at)} + + %td.timestamp + - if build.finished_at + %span #{time_ago_in_words build.finished_at} ago + + %td + .pull-right + - if current_user && can?(current_user, :manage_builds, @project) + - if build.cancel_url + = link_to build.cancel_url, title: 'Cancel' do + %i.fa.fa-remove.cred diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml new file mode 100644 index 00000000000..2c6cf69c23e --- /dev/null +++ b/app/views/projects/builds/index.html.haml @@ -0,0 +1,51 @@ +- page_title "Builds" +- header_title project_title(@project, "Builds", project_builds_path(@project)) + +%ul.center-top-menu + %li{class: ('active' if @scope.nil?)} + = link_to project_builds_path(@project) do + All builds + %span.badge.js-totalbuilds-count= @all_builds.size + + %li{class: ('active' if @scope == 'pending')} + = link_to project_builds_path(@project, scope: :pending) do + Pending + %span.badge.js-pending-count= @all_builds.pending.size + + %li{class: ('active' if @scope == 'running')} + = link_to project_builds_path(@project, scope: :running) do + Running + %span.badge.js-running-count= @all_builds.running.size + +.gray-content-block + .oneline + List of all builds from this project + + - if @ci_project && current_user && can?(current_user, :manage_builds, @project) + .pull-right + - if @all_builds.running_or_pending.any? + = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, method: :post, class: 'btn btn-danger' + +%ul.content-list + - if @builds.blank? + %li + .nothing-here-block No builds to show + - else + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Commit + %th Ref + %th Runner + %th Name + %th Duration + %th Finished at + %th + + - @builds.each do |build| + = render 'projects/builds/build', build: build + + = paginate @builds + diff --git a/config/routes.rb b/config/routes.rb index 8e6fbf6340c..3253d950f27 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -585,7 +585,11 @@ Gitlab::Application.routes.draw do end end - resources :builds, only: [:show] do + resources :builds, only: [:index, :show] do + collection do + post :cancel_all + end + member do get :cancel get :status diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 924047a0d8f..31f8aa83981 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -9,6 +9,55 @@ describe "Builds" do @gl_project.team << [@user, :master] end + describe "GET /:project/builds" do + context "All builds" do + before do + @build.success + visit namespace_project_builds_path(@gl_project.namespace, @gl_project) + end + + it { expect(page).to have_content 'All builds' } + it { expect(page).to have_content @build.short_sha } + it { expect(page).to have_content @build.ref } + it { expect(page).to have_content @build.name } + it { expect(page).to_not have_content 'Cancel all' } + end + + context "Pending scope" do + before do + @build.success + visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :pending) + end + + it { expect(page).to have_content 'No builds to show' } + it { expect(page).to_not have_content 'Cancel all' } + end + + context "Running scope" do + before do + @build.run! + visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :running) + end + + it { expect(page).to have_content 'Running' } + it { expect(page).to have_content 'Cancel all' } + it { expect(page).to have_content @build.short_sha } + it { expect(page).to have_content @build.ref } + it { expect(page).to have_content @build.name } + end + end + + describe "POST /:project/builds/:id/cancel_all" do + before do + @build.run! + visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + end + + it { expect(page).to have_content 'All builds' } + it { expect(page).to have_content 'canceled' } + it { expect(page).to_not have_content 'Cancel all' } + end + describe "GET /:project/builds/:id" do before do visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) -- cgit v1.2.1 From 09255eecd0812d35b09613a1cf2402d3108fcc49 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 14:07:41 +0200 Subject: Remove ordering from :ci_commits relation --- app/models/ci/commit.rb | 2 ++ app/models/ci/project.rb | 2 +- app/models/project.rb | 2 +- spec/models/ci/commit_spec.rb | 18 ++++++++++++++++++ spec/models/ci/project_spec.rb | 18 ------------------ 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 68864edfbbf..cd45366b34e 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -24,6 +24,8 @@ module Ci has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) } + validates_presence_of :sha validate :valid_commit_sha diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 88ba933a434..afd697f03f1 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -205,7 +205,7 @@ module Ci end def commits - gl_project.ci_commits + gl_project.ci_commits.ordered end def builds diff --git a/app/models/project.rb b/app/models/project.rb index 021920008ad..b99f3408271 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -119,7 +119,7 @@ class Project < ActiveRecord::Base has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy has_many :starrers, through: :users_star_projects, source: :user - has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id + has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 330971174fb..d1cecce5a6d 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -32,6 +32,24 @@ describe Ci::Commit do it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :short_sha } + describe :ordered do + let(:project) { FactoryGirl.create :empty_project } + + it 'returns ordered list of commits' do + commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project + commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project + expect(project.ci_commits.ordered).to eq([commit2, commit1]) + end + + it 'returns commits ordered by committed_at and id, with nulls last' do + commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project + commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project + commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project + commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project + expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1]) + end + end + describe :last_build do subject { commit.last_build } before do diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index dec4720a711..81bb2a07cb0 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -131,24 +131,6 @@ describe Ci::Project do end end - describe 'ordered commits' do - let(:project) { FactoryGirl.create :empty_project } - - it 'returns ordered list of commits' do - commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project - commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project - expect(project.ci_commits).to eq([commit2, commit1]) - end - - it 'returns commits ordered by committed_at and id, with nulls last' do - commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project - commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project - commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project - commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project - expect(project.ci_commits).to eq([commit2, commit4, commit3, commit1]) - end - end - context :valid_project do let(:commit) { FactoryGirl.create(:ci_commit) } -- cgit v1.2.1 From 4d69c6a3361bbc673e853995e3896d31241aa748 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 14:20:27 +0200 Subject: Refactor builds view --- app/controllers/projects/builds_controller.rb | 10 +++---- app/models/commit_status.rb | 1 + app/views/layouts/nav/_project.html.haml | 3 +- app/views/projects/builds/_build.html.haml | 6 ++-- app/views/projects/builds/index.html.haml | 43 ++++++++++++++------------- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 0bcd9a8a360..b7d77c21e72 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -12,12 +12,12 @@ class Projects::BuildsController < Projects::ApplicationController @builds = case @scope - when 'pending' - @all_builds.pending - when 'running' - @all_builds.running - else + when 'all' @all_builds + when 'finished' + @all_builds.finished + else + @all_builds.running_or_pending end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index b4d91b1b0c3..41eeb9a4ce4 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base scope :success, -> { where(status: 'success') } scope :failed, -> { where(status: 'failed') } scope :running_or_pending, -> { where(status:[:running, :pending]) } + scope :finished, -> { where(status:[:success, :failed, :canceled]) } scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :for_ref, ->(ref) { where(ref: ref) } diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index f52d0ad9c02..53a913fe8f3 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -41,9 +41,10 @@ - if project_nav_tab? :builds = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do - = icon('link fw') + = icon('cubes fw') %span Builds + %span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all) - if project_nav_tab? :network = nav_link(controller: %w(network)) do diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml index 10b3ef71202..e74a3a62672 100644 --- a/app/views/projects/builds/_build.html.haml +++ b/app/views/projects/builds/_build.html.haml @@ -10,12 +10,10 @@ %strong Build ##{build.id} %td - = link_to namespace_project_commit_path(@project.namespace, @project, build.sha) do - = build.short_sha + = link_to build.short_sha, namespace_project_commit_path(@project.namespace, @project, build.sha) %td - = link_to namespace_project_commits_path(@project.namespace, @project, build.ref) do - = build.ref + = link_to build.ref, namespace_project_commits_path(@project.namespace, @project, build.ref) %td - if build.runner diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 2c6cf69c23e..56bb7b11177 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -1,31 +1,32 @@ - page_title "Builds" - header_title project_title(@project, "Builds", project_builds_path(@project)) -%ul.center-top-menu - %li{class: ('active' if @scope.nil?)} - = link_to project_builds_path(@project) do - All builds - %span.badge.js-totalbuilds-count= @all_builds.size - - %li{class: ('active' if @scope == 'pending')} - = link_to project_builds_path(@project, scope: :pending) do - Pending - %span.badge.js-pending-count= @all_builds.pending.size - - %li{class: ('active' if @scope == 'running')} - = link_to project_builds_path(@project, scope: :running) do - Running - %span.badge.js-running-count= @all_builds.running.size - -.gray-content-block - .oneline - List of all builds from this project - +.project-issuable-filter + .controls - if @ci_project && current_user && can?(current_user, :manage_builds, @project) - .pull-right + .pull-left.hidden-xs - if @all_builds.running_or_pending.any? = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, method: :post, class: 'btn btn-danger' + %ul.center-top-menu + %li{class: ('active' if @scope.nil?)} + = link_to project_builds_path(@project) do + Running + %span.badge.js-running-count= @all_builds.running_or_pending.size + + %li{class: ('active' if @scope == 'finished')} + = link_to project_builds_path(@project, scope: :finished) do + Finished + %span.badge.js-running-count= @all_builds.finished.size + + %li{class: ('active' if @scope == 'all')} + = link_to project_builds_path(@project, scope: :all) do + All + %span.badge.js-totalbuilds-count= @all_builds.size + +.gray-content-block + List of #{@scope || 'running'} builds from this project + %ul.content-list - if @builds.blank? %li -- cgit v1.2.1 From 7af4f5215e28927830cbc74d383cdfeb9e4ef587 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 12 Oct 2015 21:12:31 +0200 Subject: Show warning if build doesn't have runners with specified tags or runners didn't connect recently Slightly refactor runner status detection: moving it to Runner class Signed-off-by: Kamil Trzcinski --- CHANGELOG | 1 + app/controllers/ci/admin/runners_controller.rb | 4 +- app/controllers/projects/runners_controller.rb | 2 +- app/helpers/runners_helper.rb | 26 +++--- app/models/ci/build.rb | 12 +++ app/models/ci/project.rb | 6 +- app/models/ci/runner.rb | 17 ++++ app/models/commit_status.rb | 4 + app/services/ci/register_build_service.rb | 2 +- app/views/projects/builds/show.html.haml | 21 +++++ .../commit_statuses/_commit_status.html.haml | 4 + spec/models/build_spec.rb | 101 +++++++++++++++++++++ spec/models/ci/project_spec.rb | 13 +++ spec/models/ci/runner_spec.rb | 65 +++++++++++++ 14 files changed, 256 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e3d21f7e54..04af9cc0f4d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.1.0 (unreleased) - Move CI triggers page to project settings area - Move CI project settings page to CE project settings area - Fix bug when removed file was not appearing in merge request diff + - Show warning when build cannot be served by any of the available CI runners - Note the original location of a moved project when notifying users of the move - Improve error message when merging fails - Add support of multibyte characters in LDAP UID (Roman Petrov) diff --git a/app/controllers/ci/admin/runners_controller.rb b/app/controllers/ci/admin/runners_controller.rb index 9a68add9083..110954a612d 100644 --- a/app/controllers/ci/admin/runners_controller.rb +++ b/app/controllers/ci/admin/runners_controller.rb @@ -6,7 +6,7 @@ module Ci @runners = Ci::Runner.order('id DESC') @runners = @runners.search(params[:search]) if params[:search].present? @runners = @runners.page(params[:page]).per(30) - @active_runners_cnt = Ci::Runner.where("contacted_at > ?", 1.minutes.ago).count + @active_runners_cnt = Ci::Runner.online.count end def show @@ -66,7 +66,7 @@ module Ci end def runner_params - params.require(:runner).permit(:token, :description, :tag_list, :contacted_at, :active) + params.require(:runner).permit(:token, :description, :tag_list, :active) end end end diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 6cb6e3ef6d4..deb07a21416 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -60,6 +60,6 @@ class Projects::RunnersController < Projects::ApplicationController end def runner_params - params.require(:runner).permit(:description, :tag_list, :contacted_at, :active) + params.require(:runner).permit(:description, :tag_list, :active) end end diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb index 5d7d06c8490..f79fef03ae8 100644 --- a/app/helpers/runners_helper.rb +++ b/app/helpers/runners_helper.rb @@ -1,20 +1,16 @@ module RunnersHelper def runner_status_icon(runner) - unless runner.contacted_at - return content_tag :i, nil, - class: "fa fa-warning-sign", - title: "New runner. Has not connected yet" - end - - status = - if runner.active? - runner.contacted_at > 3.hour.ago ? :online : :offline - else - :paused - end + status = runner.status + case status + when :not_connected + content_tag :i, nil, + class: "fa fa-warning-sign", + title: "New runner. Has not connected yet" - content_tag :i, nil, - class: "fa fa-circle runner-status-#{status}", - title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago" + when :online, :offline, :paused + content_tag :i, nil, + class: "fa fa-circle runner-status-#{status}", + title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago" + end end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index cfafbf6786e..2c7cad46b00 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -231,6 +231,18 @@ module Ci end end + def can_be_served?(runner) + (tag_list - runner.tag_list).empty? + end + + def any_runners_online? + project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } + end + + def show_warning? + pending? && !any_runners_online? + end + private def yaml_variables diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 88ba933a434..ef28353a30c 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -115,12 +115,12 @@ module Ci web_url end - def any_runners? - if runners.active.any? + def any_runners?(&block) + if runners.active.any?(&block) return true end - shared_runners_enabled && Ci::Runner.shared.active.any? + shared_runners_enabled && Ci::Runner.shared.active.any?(&block) end def set_default_values diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 6838ccfaaab..02a3e9db1fa 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -20,6 +20,8 @@ module Ci class Runner < ActiveRecord::Base extend Ci::Model + + LAST_CONTACT_TIME = 5.minutes.ago has_many :builds, class_name: 'Ci::Build' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' @@ -33,6 +35,7 @@ module Ci scope :shared, ->() { where(is_shared: true) } scope :active, ->() { where(active: true) } scope :paused, ->() { where(active: false) } + scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) } acts_as_taggable @@ -65,6 +68,20 @@ module Ci is_shared end + def online? + contacted_at && contacted_at > LAST_CONTACT_TIME + end + + def status + if contacted_at.nil? + :not_connected + elsif active? + online? ? :online : :offline + else + :paused + end + end + def belongs_to_one_project? runner_projects.count == 1 end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index b4d91b1b0c3..92905c618eb 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -88,4 +88,8 @@ class CommitStatus < ActiveRecord::Base def retry_url nil end + + def show_warning? + false + end end diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index 71b61bbe389..7beb098659c 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -17,7 +17,7 @@ module Ci builds = builds.order('created_at ASC') build = builds.find do |build| - (build.tag_list - current_runner.tag_list).empty? + build.can_be_served?(current_runner) end diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 9c3ae622b72..70a93eb5976 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -39,6 +39,27 @@ .pull-right = @build.updated_at.stamp('19:00 Aug 27') + - if @build.show_warning? + - unless @build.any_runners_online? + .bs-callout.bs-callout-warning + %p + - if no_runners_for_project?(@build.project) + This build is stuck, because the project doesn't have runners assigned. + - elsif @build.tags.any? + This build is stuck. + %br + This build is stuck, because you don't have any active runners online with these tags assigned to the project: + - @build.tags.each do |tag| + %span.label.label-primary + = tag + - else + This build is stuck, because you don't have any active runners online that can run this build. + + %br + Go to + = link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do + Runners page + .row.prepend-top-default .col-md-9 .clearfix diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml index 7314f8e79d3..99fdde45402 100644 --- a/app/views/projects/commit_statuses/_commit_status.html.haml +++ b/app/views/projects/commit_statuses/_commit_status.html.haml @@ -2,6 +2,10 @@ %td.status = ci_status_with_icon(commit_status.status) + - if commit_status.show_warning? + .pull-right + %i.fa.fa-warning-sign.text-warning + %td.commit_status-link - if commit_status.target_url = link_to commit_status.target_url do diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index d875015b991..6047171a6f1 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -273,4 +273,105 @@ describe Ci::Build do is_expected.to eq(['rec1', pusher_email]) end end + + describe :can_be_served? do + let(:runner) { FactoryGirl.create :ci_specific_runner } + + before { build.project.runners << runner } + + context 'runner without tags' do + it 'can handle builds without tags' do + expect(build.can_be_served?(runner)).to be_truthy + end + + it 'cannot handle build with tags' do + build.tag_list = ['aa'] + expect(build.can_be_served?(runner)).to be_falsey + end + end + + context 'runner with tags' do + before { runner.tag_list = ['bb', 'cc'] } + + it 'can handle builds without tags' do + expect(build.can_be_served?(runner)).to be_truthy + end + + it 'can handle build with matching tags' do + build.tag_list = ['bb'] + expect(build.can_be_served?(runner)).to be_truthy + end + + it 'cannot handle build with not matching tags' do + build.tag_list = ['aa'] + expect(build.can_be_served?(runner)).to be_falsey + end + end + end + + describe :any_runners_online? do + subject { build.any_runners_online? } + + context 'when no runners' do + it { is_expected.to be_falsey } + end + + context 'if there are runner' do + let(:runner) { FactoryGirl.create :ci_specific_runner } + + before do + build.project.runners << runner + runner.update_attributes(contacted_at: 1.second.ago) + end + + it { is_expected.to be_truthy } + + it 'that is inactive' do + runner.update_attributes(active: false) + is_expected.to be_falsey + end + + it 'that is not online' do + runner.update_attributes(contacted_at: nil) + is_expected.to be_falsey + end + + it 'that cannot handle build' do + expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false) + is_expected.to be_falsey + end + + end + end + + describe :show_warning? do + subject { build.show_warning? } + + %w(pending).each do |state| + context "if commit_status.status is #{state}" do + before { build.status = state } + + it { is_expected.to be_truthy } + + context "and there are specific runner" do + let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago } + + before do + build.project.runners << runner + runner.save + end + + it { is_expected.to be_falsey } + end + end + end + + %w(success failed canceled running).each do |state| + context "if commit_status.status is #{state}" do + before { build.status = state } + + it { is_expected.to be_falsey } + end + end + end end diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index dec4720a711..c1605d68859 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -259,5 +259,18 @@ describe Ci::Project do FactoryGirl.create(:ci_shared_runner) expect(project.any_runners?).to be_falsey end + + it "checks the presence of specific runner" do + project = FactoryGirl.create(:ci_project) + specific_runner = FactoryGirl.create(:ci_specific_runner) + project.runners << specific_runner + expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy + end + + it "checks the presence of shared runner" do + project = FactoryGirl.create(:ci_project, shared_runners_enabled: true) + shared_runner = FactoryGirl.create(:ci_shared_runner) + expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy + end end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 757593a7ab8..536a737a33d 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -48,6 +48,71 @@ describe Ci::Runner do it { expect(shared_runner.only_for?(project)).to be_truthy } end + describe :online do + subject { Ci::Runner.online } + + before do + @runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago) + @runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) + end + + it { is_expected.to eq([@runner2])} + end + + describe :online? do + let(:runner) { FactoryGirl.create(:ci_shared_runner) } + + subject { runner.online? } + + context 'never contacted' do + before { runner.contacted_at = nil } + + it { is_expected.to be_falsey } + end + + context 'contacted long time ago time' do + before { runner.contacted_at = 1.year.ago } + + it { is_expected.to be_falsey } + end + + context 'contacted 1s ago' do + before { runner.contacted_at = 1.second.ago } + + it { is_expected.to be_truthy } + end + end + + describe :status do + let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) } + + subject { runner.status } + + context 'never connected' do + before { runner.contacted_at = nil } + + it { is_expected.to eq(:not_connected) } + end + + context 'contacted 1s ago' do + before { runner.contacted_at = 1.second.ago } + + it { is_expected.to eq(:online) } + end + + context 'contacted long time ago' do + before { runner.contacted_at = 1.year.ago } + + it { is_expected.to eq(:offline) } + end + + context 'inactive' do + before { runner.active = false } + + it { is_expected.to eq(:paused) } + end + end + describe "belongs_to_one_project?" do it "returns false if there are two projects runner assigned to" do runner = FactoryGirl.create(:ci_specific_runner) -- cgit v1.2.1 From 16bfd9d31a4e79e68591ba85428384512a93b680 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 13 Oct 2015 02:41:25 +0200 Subject: Fix specs --- spec/helpers/runners_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/runners_helper_spec.rb b/spec/helpers/runners_helper_spec.rb index b3d635a1932..35f91b7decf 100644 --- a/spec/helpers/runners_helper_spec.rb +++ b/spec/helpers/runners_helper_spec.rb @@ -12,7 +12,7 @@ describe RunnersHelper do end it "returns online text" do - runner = FactoryGirl.build(:ci_runner, contacted_at: 1.hour.ago, active: true) + runner = FactoryGirl.build(:ci_runner, contacted_at: 1.second.ago, active: true) expect(runner_status_icon(runner)).to include("Runner is online") end end -- cgit v1.2.1 From d9e2232f6393a1627847c9f6c2be3c4f987d0797 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 13:51:29 +0200 Subject: Fix warning sign --- app/helpers/runners_helper.rb | 2 +- app/views/projects/builds/show.html.haml | 2 +- app/views/projects/commit_statuses/_commit_status.html.haml | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb index f79fef03ae8..5afebab88e1 100644 --- a/app/helpers/runners_helper.rb +++ b/app/helpers/runners_helper.rb @@ -4,7 +4,7 @@ module RunnersHelper case status when :not_connected content_tag :i, nil, - class: "fa fa-warning-sign", + class: "fa fa-warning", title: "New runner. Has not connected yet" when :online, :offline, :paused diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 70a93eb5976..91c1b16c9f6 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -25,7 +25,7 @@ %a Build ##{@build.id} · - %i.fa.fa-warning-sign + %i.fa.fa-warning This build was retried. .gray-content-block.second-block diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml index 99fdde45402..637154f56aa 100644 --- a/app/views/projects/commit_statuses/_commit_status.html.haml +++ b/app/views/projects/commit_statuses/_commit_status.html.haml @@ -2,10 +2,6 @@ %td.status = ci_status_with_icon(commit_status.status) - - if commit_status.show_warning? - .pull-right - %i.fa.fa-warning-sign.text-warning - %td.commit_status-link - if commit_status.target_url = link_to commit_status.target_url do @@ -13,6 +9,9 @@ - else %strong Build ##{commit_status.id} + - if commit_status.show_warning? + %i.fa.fa-warning.text-warning + %td = commit_status.ref -- cgit v1.2.1 From d9ece71ef0677a1d3468697485db7cbcf1b83745 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 14:21:49 +0200 Subject: Fix specs --- app/controllers/projects/builds_controller.rb | 12 ++++++------ app/models/ci/runner.rb | 2 +- features/steps/project/commits/commits.rb | 2 +- spec/features/builds_spec.rb | 28 +++++++++++++-------------- spec/models/ci/runner_spec.rb | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index b7d77c21e72..54c01ddf238 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -12,12 +12,12 @@ class Projects::BuildsController < Projects::ApplicationController @builds = case @scope - when 'all' - @all_builds - when 'finished' - @all_builds.finished - else - @all_builds.running_or_pending + when 'all' + @all_builds + when 'finished' + @all_builds.finished + else + @all_builds.running_or_pending end end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index bc5cd137e91..52fdc2578e3 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -78,7 +78,7 @@ module Ci end def short_sha - token[0...8] + token[0...8] if token end end end diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index a3cb83880e3..e5b3f27135d 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -113,7 +113,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I click status link' do - click_link "Builds" + find('.commit-ci-menu').click_link "Builds" end step 'I see builds list' do diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 31f8aa83981..a339a151112 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -10,40 +10,40 @@ describe "Builds" do end describe "GET /:project/builds" do - context "All builds" do + context "Running scope" do before do - @build.success + @build.run! visit namespace_project_builds_path(@gl_project.namespace, @gl_project) end - it { expect(page).to have_content 'All builds' } + it { expect(page).to have_content 'Running' } + it { expect(page).to have_content 'Cancel all' } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } - it { expect(page).to_not have_content 'Cancel all' } end - context "Pending scope" do + context "Finished scope" do before do - @build.success - visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :pending) + @build.run! + visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :finished) end it { expect(page).to have_content 'No builds to show' } - it { expect(page).to_not have_content 'Cancel all' } + it { expect(page).to have_content 'Cancel all' } end - context "Running scope" do + context "All builds" do before do - @build.run! - visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :running) + @gl_project.ci_builds.running_or_pending.each(&:success) + visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :all) end - it { expect(page).to have_content 'Running' } - it { expect(page).to have_content 'Cancel all' } + it { expect(page).to have_content 'All' } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } + it { expect(page).to_not have_content 'Cancel all' } end end @@ -53,7 +53,7 @@ describe "Builds" do visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build) end - it { expect(page).to have_content 'All builds' } + it { expect(page).to have_content 'All' } it { expect(page).to have_content 'canceled' } it { expect(page).to_not have_content 'Cancel all' } end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 757593a7ab8..a401ae7fe51 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -32,7 +32,7 @@ describe Ci::Runner do end it 'should return the token if the description is an empty string' do - runner = FactoryGirl.build(:ci_runner, description: '') + runner = FactoryGirl.build(:ci_runner, description: '', token: 'token') expect(runner.display_name).to eq runner.token end end -- cgit v1.2.1 From 8f584d5f2c8dc036214a7fc71ce864fe23b4cb9e Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 6 Oct 2015 17:09:03 +0300 Subject: Fix: Images cannot show when projects' path was changed --- CHANGELOG | 1 + app/models/namespace.rb | 2 + app/models/project.rb | 2 + app/services/projects/transfer_service.rb | 4 ++ app/uploaders/file_uploader.rb | 2 +- features/steps/admin/projects.rb | 2 + lib/gitlab/markdown.rb | 2 + lib/gitlab/markdown/upload_link_filter.rb | 47 ++++++++++++++ lib/gitlab/uploads_transfer.rb | 35 ++++++++++ public/uploads/.gitkeep | 0 .../projects/uploads_controller_spec.rb | 4 +- spec/lib/gitlab/email/attachment_uploader_spec.rb | 1 - .../lib/gitlab/markdown/upload_link_filter_spec.rb | 75 ++++++++++++++++++++++ spec/lib/gitlab/uploads_transfer_spec.rb | 50 +++++++++++++++ spec/services/projects/download_service_spec.rb | 2 - spec/services/projects/transfer_service_spec.rb | 2 + spec/services/projects/upload_service_spec.rb | 4 -- 17 files changed, 225 insertions(+), 10 deletions(-) create mode 100644 lib/gitlab/markdown/upload_link_filter.rb create mode 100644 lib/gitlab/uploads_transfer.rb delete mode 100644 public/uploads/.gitkeep create mode 100644 spec/lib/gitlab/markdown/upload_link_filter_spec.rb create mode 100644 spec/lib/gitlab/uploads_transfer_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 31feb115d1e..87baa6b6621 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -53,6 +53,7 @@ v 8.1.0 (unreleased) - Add "New Page" button to Wiki Pages tab (Stan Hu) - Only render 404 page from /public - Hide passwords from services API (Alex Lossent) + - Fix: Images cannot show when projects' path was changed v 8.0.4 - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index bc8525df5a5..5782e649f8b 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -118,6 +118,8 @@ class Namespace < ActiveRecord::Base gitlab_shell.add_namespace(path_was) if gitlab_shell.mv_namespace(path_was, path) + Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) + # If repositories moved successfully we need to # send update instructions to users. # However we cannot allow rollback since we moved namespace dir diff --git a/app/models/project.rb b/app/models/project.rb index 021920008ad..cd30467fae3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -656,6 +656,8 @@ class Project < ActiveRecord::Base # db changes in order to prevent out of sync between db and fs raise Exception.new('repository cannot be renamed') end + + Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) end def hook_attrs diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index c327c244f0d..64ea6dd42eb 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -27,6 +27,7 @@ module Projects def transfer(project, new_namespace) Project.transaction do old_path = project.path_with_namespace + old_namespace = project.namespace new_path = File.join(new_namespace.try(:path) || '', project.path) if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present? @@ -51,6 +52,9 @@ module Projects # clear project cached events project.reset_events_cache + # Move uploads + Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) + true end end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index f9673abbfe8..e8211585834 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -26,7 +26,7 @@ class FileUploader < CarrierWave::Uploader::Base end def secure_url - File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename) + File.join("/uploads", @secret, file.filename) end def file_storage? diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb index 17233f89f38..5a1cc9aa151 100644 --- a/features/steps/admin/projects.rb +++ b/features/steps/admin/projects.rb @@ -41,6 +41,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps end step 'I transfer project to group \'Web\'' do + allow_any_instance_of(Projects::TransferService). + to receive(:move_uploads_to_new_namespace).and_return(true) find(:xpath, "//input[@id='new_namespace_id']").set group.id click_button 'Transfer' end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index ae5f2544691..32a368c2e2b 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -48,6 +48,7 @@ module Gitlab autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter' autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' + autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter' # Public: Parse the provided text with GitLab-Flavored Markdown # @@ -140,6 +141,7 @@ module Gitlab Gitlab::Markdown::SyntaxHighlightFilter, Gitlab::Markdown::SanitizationFilter, + Gitlab::Markdown::UploadLinkFilter, Gitlab::Markdown::RelativeLinkFilter, Gitlab::Markdown::EmojiFilter, Gitlab::Markdown::TableOfContentsFilter, diff --git a/lib/gitlab/markdown/upload_link_filter.rb b/lib/gitlab/markdown/upload_link_filter.rb new file mode 100644 index 00000000000..fbada73ab86 --- /dev/null +++ b/lib/gitlab/markdown/upload_link_filter.rb @@ -0,0 +1,47 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' +require 'uri' + +module Gitlab + module Markdown + # HTML filter that "fixes" relative upload links to files. + # Context options: + # :project (required) - Current project + # + class UploadLinkFilter < HTML::Pipeline::Filter + def call + doc.search('a').each do |el| + process_link_attr el.attribute('href') + end + + doc.search('img').each do |el| + process_link_attr el.attribute('src') + end + + doc + end + + protected + + def process_link_attr(html_attr) + return if html_attr.blank? + + uri = html_attr.value + if uri.starts_with?("/uploads/") + html_attr.value = build_url(uri).to_s + end + end + + def build_url(uri) + File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri) + end + + # Ensure that a :project key exists in context + # + # Note that while the key might exist, its value could be nil! + def validate + needs :project + end + end + end +end diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb new file mode 100644 index 00000000000..be8fcc7b2d2 --- /dev/null +++ b/lib/gitlab/uploads_transfer.rb @@ -0,0 +1,35 @@ +module Gitlab + class UploadsTransfer + def move_project(project_path, namespace_path_was, namespace_path) + new_namespace_folder = File.join(root_dir, namespace_path) + FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder) + from = File.join(root_dir, namespace_path_was, project_path) + to = File.join(root_dir, namespace_path, project_path) + move(from, to, "") + end + + def rename_project(path_was, path, namespace_path) + base_dir = File.join(root_dir, namespace_path) + move(path_was, path, base_dir) + end + + def rename_namespace(path_was, path) + move(path_was, path) + end + + private + + def move(path_was, path, base_dir = nil) + base_dir = root_dir unless base_dir + from = File.join(base_dir, path_was) + to = File.join(base_dir, path) + FileUtils.mv(from, to) + rescue Errno::ENOENT + false + end + + def root_dir + File.join(Rails.root, "public", "uploads") + end + end +end diff --git a/public/uploads/.gitkeep b/public/uploads/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index f51abfedae5..93c4494c660 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -33,7 +33,7 @@ describe Projects::UploadsController do it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"rails_sample\"' - expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" + expect(response.body).to match "\"url\":\"/uploads" expect(response.body).to match '\"is_image\":true' end end @@ -49,7 +49,7 @@ describe Projects::UploadsController do it 'returns a content with original filename, new link, and correct type.' do expect(response.body).to match '\"alt\":\"doc_sample.txt\"' - expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" + expect(response.body).to match "\"url\":\"/uploads" expect(response.body).to match '\"is_image\":false' end end diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb index e8208e15e29..8fb432367b6 100644 --- a/spec/lib/gitlab/email/attachment_uploader_spec.rb +++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb @@ -13,7 +13,6 @@ describe Gitlab::Email::AttachmentUploader do expect(link).not_to be_nil expect(link[:is_image]).to be_truthy expect(link[:alt]).to eq("bricks") - expect(link[:url]).to include("/#{project.path_with_namespace}") expect(link[:url]).to include("bricks.png") end end diff --git a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb new file mode 100644 index 00000000000..9ae45a6f559 --- /dev/null +++ b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb @@ -0,0 +1,75 @@ +# encoding: UTF-8 + +require 'spec_helper' + +module Gitlab::Markdown + describe UploadLinkFilter do + def filter(doc, contexts = {}) + contexts.reverse_merge!({ + project: project + }) + + described_class.call(doc, contexts) + end + + def image(path) + %() + end + + def link(path) + %(#{path}) + end + + let(:project) { create(:project) } + + shared_examples :preserve_unchanged do + it 'does not modify any relative URL in anchor' do + doc = filter(link('README.md')) + expect(doc.at_css('a')['href']).to eq 'README.md' + end + + it 'does not modify any relative URL in image' do + doc = filter(image('files/images/logo-black.png')) + expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png' + end + end + + it 'does not raise an exception on invalid URIs' do + act = link("://foo") + expect { filter(act) }.not_to raise_error + end + + context 'with a valid repository' do + it 'rebuilds relative URL for a link' do + doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('a')['href']). + to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + end + + it 'rebuilds relative URL for an image' do + doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('a')['href']). + to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" + end + + it 'does not modify absolute URL' do + doc = filter(link('http://example.com')) + expect(doc.at_css('a')['href']).to eq 'http://example.com' + end + + it 'supports Unicode filenames' do + path = '/uploads/한글.png' + escaped = Addressable::URI.escape(path) + + # Stub these methods so the file doesn't actually need to be in the repo + allow_any_instance_of(described_class). + to receive(:file_exists?).and_return(true) + allow_any_instance_of(described_class). + to receive(:image?).with(path).and_return(true) + + doc = filter(image(escaped)) + expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png" + end + end + end +end diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb new file mode 100644 index 00000000000..260364a513e --- /dev/null +++ b/spec/lib/gitlab/uploads_transfer_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Gitlab::UploadsTransfer do + before do + @root_dir = File.join(Rails.root, "public", "uploads") + @upload_transfer = Gitlab::UploadsTransfer.new + + @project_path_was = "test_project_was" + @project_path = "test_project" + @namespace_path_was = "test_namespace_was" + @namespace_path = "test_namespace" + end + + after do + FileUtils.rm_rf([ + File.join(@root_dir, @namespace_path), + File.join(@root_dir, @namespace_path_was) + ]) + end + + describe '#move_project' do + it "moves project upload to another namespace" do + FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) + @upload_transfer.move_project(@project_path, @namespace_path_was, @namespace_path) + + expected_path = File.join(@root_dir, @namespace_path, @project_path) + expect(Dir.exist?(expected_path)).to be_truthy + end + end + + describe '#rename_project' do + it "renames project" do + FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was)) + @upload_transfer.rename_project(@project_path_was, @project_path, @namespace_path) + + expected_path = File.join(@root_dir, @namespace_path, @project_path) + expect(Dir.exist?(expected_path)).to be_truthy + end + end + + describe '#rename_namespace' do + it "renames namespace" do + FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) + @upload_transfer.rename_namespace(@namespace_path_was, @namespace_path) + + expected_path = File.join(@root_dir, @namespace_path, @project_path) + expect(Dir.exist?(expected_path)).to be_truthy + end + end +end diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index f12e09c58c3..ddee2e62dfc 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -37,7 +37,6 @@ describe Projects::DownloadService do it { expect(@link_to_file).to have_key('url') } it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file['is_image']).to be true } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('rails_sample.jpg') } it { expect(@link_to_file['alt']).to eq('rails_sample') } end @@ -52,7 +51,6 @@ describe Projects::DownloadService do it { expect(@link_to_file).to have_key('url') } it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file['is_image']).to be false } - it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('doc_sample.txt') } it { expect(@link_to_file['alt']).to eq('doc_sample.txt') } end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index bb7da33b12e..47755bfc990 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -7,6 +7,8 @@ describe Projects::TransferService do context 'namespace -> namespace' do before do + allow_any_instance_of(Gitlab::UploadsTransfer). + to receive(:move_project).and_return(true) group.add_owner(user) @result = transfer_project(project, user, group) end diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index fa4ff6b01ad..1b1a80d1fe7 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -18,7 +18,6 @@ describe Projects::UploadService do it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('banana_sample') } it { expect(@link_to_file[:is_image]).to equal(true) } - it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match('banana_sample.gif') } end @@ -34,7 +33,6 @@ describe Projects::UploadService do it { expect(@link_to_file).to have_value('dk') } it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file[:is_image]).to equal(true) } - it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match('dk.png') } end @@ -49,7 +47,6 @@ describe Projects::UploadService do it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('rails_sample') } it { expect(@link_to_file[:is_image]).to equal(true) } - it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match('rails_sample.jpg') } end @@ -64,7 +61,6 @@ describe Projects::UploadService do it { expect(@link_to_file).to have_key(:is_image) } it { expect(@link_to_file).to have_value('doc_sample.txt') } it { expect(@link_to_file[:is_image]).to equal(false) } - it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match('doc_sample.txt') } end -- cgit v1.2.1 From b83a18a55cd03cfa6d0d631b8d4567ae29b5fe26 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 14 Oct 2015 19:21:27 +0300 Subject: Revert "Improve invalidation of stored service password if the endpoint URL is changed" This reverts commit b46397548056e4e8ef00efe4f641c61ba1dd5230. --- app/controllers/admin/services_controller.rb | 8 +---- app/controllers/projects/services_controller.rb | 8 +---- app/models/project_services/bamboo_service.rb | 2 +- app/models/project_services/teamcity_service.rb | 2 +- app/models/service.rb | 32 ++++---------------- spec/models/service_spec.rb | 39 ++----------------------- 6 files changed, 13 insertions(+), 78 deletions(-) diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 46133588332..a62170662e1 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -39,13 +39,7 @@ class Admin::ServicesController < Admin::ApplicationController end def application_services_params - application_services_params = params.permit(:id, + params.permit(:id, service: Projects::ServicesController::ALLOWED_PARAMS) - if application_services_params[:service].is_a?(Hash) - Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param| - application_services_params[:service].delete(param) if application_services_params[:service][param].blank? - end - end - application_services_params end end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 129068ef019..3047ee8a1ff 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -9,10 +9,6 @@ class Projects::ServicesController < Projects::ApplicationController :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, :server_host, :server_port, :default_irc_uri, :enable_ssl_verification] - - # Parameters to ignore if no value is specified - FILTER_BLANK_PARAMS = [:password] - # Authorize before_action :authorize_admin_project! before_action :service, only: [:edit, :update, :test] @@ -63,9 +59,7 @@ class Projects::ServicesController < Projects::ApplicationController def service_params service_params = params.require(:service).permit(ALLOWED_PARAMS) - FILTER_BLANK_PARAMS.each do |param| - service_params.delete(param) if service_params[param].blank? - end + service_params.delete("password") if service_params["password"].blank? service_params end end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 4a18c772e50..5f5255ab487 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -48,7 +48,7 @@ class BambooService < CiService end def reset_password - if prop_modified?(:bamboo_url) && !prop_updated?(:password) + if prop_updated?(:bamboo_url) self.password = nil end end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index a548a557dfe..fb11cad352e 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -45,7 +45,7 @@ class TeamcityService < CiService end def reset_password - if prop_modified?(:teamcity_url) && !prop_updated?(:password) + if prop_updated?(:teamcity_url) self.password = nil end end diff --git a/app/models/service.rb b/app/models/service.rb index 946ea1a096b..7e845d565b1 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -33,8 +33,6 @@ class Service < ActiveRecord::Base after_initialize :initialize_properties - after_commit :reset_updated_properties - belongs_to :project has_one :service_hook @@ -105,7 +103,6 @@ class Service < ActiveRecord::Base # Provide convenient accessor methods # for each serialized property. - # Also keep track of updated properties. def self.prop_accessor(*args) args.each do |arg| class_eval %{ @@ -114,36 +111,19 @@ class Service < ActiveRecord::Base end def #{arg}=(value) - updated_properties['#{arg}'] = #{arg} unless updated_properties.include?('#{arg}') self.properties['#{arg}'] = value end } end end - # Returns a hash of the properties that have been assigned a new value since last save, - # indicating their original values (attr => original value). - # ActiveRecord does not provide a mechanism to track changes in serialized keys, - # so we need a specific implementation for service properties. - # This allows to track changes to properties set with the accessor methods, - # but not direct manipulation of properties hash. - def updated_properties - @updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new - end - + # ActiveRecord does not provide a mechanism to track changes in serialized keys. + # This is why we need to perform extra query to do it mannually. def prop_updated?(prop_name) - # Check if a new value was set. - # The new value may or may not be the same as the old value - updated_properties.include?(prop_name) - end - - def prop_modified?(prop_name) - # Check if new value was set and it is different from the old value - prop_updated?(prop_name) && updated_properties[prop_name] != send(prop_name) - end - - def reset_updated_properties - @updated_properties = nil + relation_name = self.type.underscore + previous_value = project.send(relation_name).send(prop_name) + return false if previous_value.nil? + previous_value != send(prop_name) end def async_execute(data) diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index d42b96294ba..da87ea5b84f 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -116,47 +116,14 @@ describe Service do ) end - it "returns false when the property has not been assigned a new value" do + it "returns false" do service.username = "key_changed" expect(service.prop_updated?(:bamboo_url)).to be_falsy end - it "returns true when the property has been assigned a different value" do - service.bamboo_url = "http://example.com" + it "returns true" do + service.bamboo_url = "http://other.com" expect(service.prop_updated?(:bamboo_url)).to be_truthy end - - it "returns true when the property has been re-assigned the same value" do - service.bamboo_url = 'http://gitlab.com' - expect(service.prop_updated?(:bamboo_url)).to be_truthy - end - end - - describe "#prop_modified?" do - let(:service) do - BambooService.create( - project: create(:project), - properties: { - bamboo_url: 'http://gitlab.com', - username: 'mic', - password: "password" - } - ) - end - - it "returns false when the property has not been assigned a new value" do - service.username = "key_changed" - expect(service.prop_modified?(:bamboo_url)).to be_falsy - end - - it "returns true when the property has been assigned a different value" do - service.bamboo_url = "http://example.com" - expect(service.prop_modified?(:bamboo_url)).to be_truthy - end - - it "returns false when the property has been re-assigned the same value" do - service.bamboo_url = 'http://gitlab.com' - expect(service.prop_modified?(:bamboo_url)).to be_falsy - end end end -- cgit v1.2.1 From 0ff8975939846a3d013421d1f46baf3cbb77dec2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 19:57:20 +0200 Subject: Fix cancel_all specs --- app/views/projects/builds/index.html.haml | 2 +- config/routes.rb | 2 +- spec/features/builds_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 56bb7b11177..b04784025f1 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -6,7 +6,7 @@ - if @ci_project && current_user && can?(current_user, :manage_builds, @project) .pull-left.hidden-xs - if @all_builds.running_or_pending.any? - = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, method: :post, class: 'btn btn-danger' + = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' %ul.center-top-menu %li{class: ('active' if @scope.nil?)} diff --git a/config/routes.rb b/config/routes.rb index 3253d950f27..06abc2820a5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -587,7 +587,7 @@ Gitlab::Application.routes.draw do resources :builds, only: [:index, :show] do collection do - post :cancel_all + get :cancel_all end member do diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index a339a151112..941e45408a7 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -47,10 +47,10 @@ describe "Builds" do end end - describe "POST /:project/builds/:id/cancel_all" do + describe "GET /:project/builds/:id/cancel_all" do before do @build.run! - visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + visit cancel_all_namespace_project_builds_path(@gl_project.namespace, @gl_project) end it { expect(page).to have_content 'All' } -- cgit v1.2.1 From fc0d92746d8a228077c6602be1f77b679f5d196a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Oct 2015 14:55:40 -0400 Subject: Prevent a JS error in MergeRequestTabs When `window.location.hash` is pointing to a note, e.g. `#note_1234`, `scrollToElement` would throw an error because a selector such as `.commits #note_1234` doesn't exist, so `offset()` returned `undefined`. This error would prevent subsequent calls from running, which caused the loading spinner to never be hidden. Now we ensure the selector returns a valid element before trying to scroll to it. --- app/assets/javascripts/merge_request_tabs.js.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 3e77ea515f8..593a8f42130 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -68,8 +68,8 @@ class @MergeRequestTabs scrollToElement: (container) -> if window.location.hash - top = $(container + " " + window.location.hash).offset().top - $('body').scrollTo(top) + $el = $("#{container} #{window.location.hash}") + $('body').scrollTo($el.offset().top) if $el.length # Activate a tab based on the current action activateTab: (action) -> @@ -127,7 +127,7 @@ class @MergeRequestTabs document.getElementById('commits').innerHTML = data.html $('.js-timeago').timeago() @commitsLoaded = true - @scrollToElement(".commits") + @scrollToElement("#commits") loadDiff: (source) -> return if @diffsLoaded @@ -137,7 +137,7 @@ class @MergeRequestTabs success: (data) => document.getElementById('diffs').innerHTML = data.html @diffsLoaded = true - @scrollToElement(".diffs") + @scrollToElement("#diffs") # Show or hide the loading spinner # -- cgit v1.2.1 From 386b13d624e065920fd5730d9d6a0d7983f005cb Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Oct 2015 15:38:19 -0400 Subject: Allow highlight themes to override `pre` colors --- app/assets/stylesheets/framework/typography.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index bf36f96cc97..6ccc084526a 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -101,9 +101,9 @@ pre { margin: 12px 0 12px 0 !important; - background-color: #f8fafc !important; + background-color: #f8fafc; font-size: 13px !important; - color: #5b6169 !important; + color: #5b6169; line-height: 1.6em !important; @include border-radius(2px); } -- cgit v1.2.1 From d7f2a656f9b56a7d6531fd4eefcd747142b3034f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Oct 2015 15:39:02 -0400 Subject: Update highlight themes so they always have the correct colors --- app/assets/stylesheets/highlight/dark.scss | 11 +++++------ app/assets/stylesheets/highlight/monokai.scss | 13 ++++++------- app/assets/stylesheets/highlight/solarized_dark.scss | 9 ++++----- app/assets/stylesheets/highlight/solarized_light.scss | 9 ++++----- app/assets/stylesheets/highlight/white.scss | 18 +++++++----------- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 8323a8598ec..6a2b25ddc67 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,11 +1,10 @@ /* https://github.com/MozMorris/tomorrow-pygments */ -pre.code.highlight.dark, .code.dark { - background-color: #1d1f21; - color: #c5c8c6; + background-color: #1d1f21 !important; + color: #c5c8c6 !important; - pre.code, + pre.highlight, .line-numbers, .line-numbers a { background-color: #1d1f21 !important; @@ -23,8 +22,8 @@ pre.code.highlight.dark, // Search result highlight span.highlight_word { - background: #ffe792; - color: #000000; + background-color: #ffe792 !important; + color: #000000 !important; } .hll { background-color: #373b41 } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index e8381674336..8560c3c490f 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,15 +1,14 @@ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */ -pre.code.monokai, .code.monokai { - background: #272822; - color: #f8f8f2; + background-color: #272822 !important; + color: #f8f8f2 !important; pre.highlight, .line-numbers, .line-numbers a { - background:#272822 !important; - color:#f8f8f2 !important; + background-color :#272822 !important; + color: #f8f8f2 !important; } pre.code { @@ -23,8 +22,8 @@ pre.code.monokai, // Search result highlight span.highlight_word { - background: #ffe792; - color: #000000; + background-color: #ffe792 !important; + color: #000000 !important; } .hll { background-color: #49483e } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index bd41480aefb..7d489a9666b 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,11 +1,10 @@ /* https://gist.github.com/qguv/7936275 */ -pre.code.highlight.solarized-dark, .code.solarized-dark { - background-color: #002b36; - color: #93a1a1; + background-color: #002b36 !important; + color: #93a1a1 !important; - pre.code, + pre.highlight, .line-numbers, .line-numbers a { background-color: #002b36 !important; @@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark, // Search result highlight span.highlight_word { - background: #094554; + background-color: #094554 !important; } /* Solarized Dark diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 4cc62863870..200ed346446 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -1,11 +1,10 @@ /* https://gist.github.com/qguv/7936275 */ -pre.code.highlight.solarized-light, .code.solarized-light { - background-color: #fdf6e3; - color: #586e75; + background-color: #fdf6e3 !important; + color: #586e75 !important; - pre.code, + pre.highlight, .line-numbers, .line-numbers a { background-color: #fdf6e3 !important; @@ -23,7 +22,7 @@ pre.code.highlight.solarized-light, // Search result highlight span.highlight_word { - background: #eee8d5; + background-color: #eee8d5 !important; } /* Solarized Light diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 20a144ef952..e2626da7871 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,24 +1,20 @@ /* https://github.com/aahan/pygments-github-style */ -pre.code.highlight.white, .code.white { - background-color: #f8fafc; - font-size: 13px; - color: #5b6169; - line-height: 1.6em; + background-color: #f8fafc !important; + color: #5b6169 !important; + + pre.highlight, .line-numbers, .line-numbers a { background-color: $background-color !important; color: $gl-gray !important; } - pre.highlight { - background-color: #fff !important; - color: #333 !important; - } - pre.code { border-left: 1px solid $border-color; + background-color: #fff !important; + color: #333 !important; } // highlight line via anchor @@ -28,7 +24,7 @@ pre.code.highlight.white, // Search result highlight span.highlight_word { - background: #fafe3d; + background-color: #fafe3d !important; } .hll { background-color: #f8f8f8 } -- cgit v1.2.1 From 9750de4943f044499982d3d0b57425525ce5edc9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Oct 2015 16:30:19 -0400 Subject: Simplify the `issues_sentence` helper --- app/helpers/merge_requests_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 81773e7afcf..728d877ace2 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -47,7 +47,7 @@ module MergeRequestsHelper end def issues_sentence(issues) - issues.map { |i| "##{i.iid}" }.to_sentence + issues.map(&:to_reference).to_sentence end def mr_change_branches_path(merge_request) -- cgit v1.2.1 From 459b882131f5da02e86e89abde6c84dfee2d587c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Oct 2015 16:31:54 -0400 Subject: Shut up, Rubocop [ci skip] --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 432bf026ba2..d5493ca038d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -269,4 +269,4 @@ if Rails.env.test? Settings.gitlab['default_projects_limit'] = 42 Settings.gitlab['default_can_create_group'] = true Settings.gitlab['default_can_create_team'] = false -end \ No newline at end of file +end -- cgit v1.2.1 From 4271a2b8acf85f2ca31b0e377db5154a38576487 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Oct 2015 20:55:17 +0200 Subject: Fix tests --- spec/features/builds_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 941e45408a7..154857e77fe 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -53,8 +53,7 @@ describe "Builds" do visit cancel_all_namespace_project_builds_path(@gl_project.namespace, @gl_project) end - it { expect(page).to have_content 'All' } - it { expect(page).to have_content 'canceled' } + it { expect(page).to have_content 'No builds to show' } it { expect(page).to_not have_content 'Cancel all' } end -- cgit v1.2.1 From 83f04853e9a749c3397ee7683a78b986e1070904 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 15 Oct 2015 11:14:39 +0200 Subject: Update gitlab_git to 7.2.19 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 4938cbf8b80..d81cc4d540a 100644 --- a/Gemfile +++ b/Gemfile @@ -47,7 +47,7 @@ gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.17' +gem "gitlab_git", '~> 7.2.19' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 1dd56cd9c8c..f9421331e4e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -279,7 +279,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.1) gemojione (~> 2.0) - gitlab_git (7.2.17) + gitlab_git (7.2.19) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -836,7 +836,7 @@ DEPENDENCIES gitlab-flowdock-git-hook (~> 1.0.1) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.2.17) + gitlab_git (~> 7.2.19) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.0.2) -- cgit v1.2.1 From 9fa209e1d8748b1eb1ef041bb1c40d30914f2815 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Oct 2015 10:47:05 +0200 Subject: Remove unneeded change --- app/views/layouts/nav/_project_settings.html.haml | 2 +- app/views/projects/builds/_build.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index b12dcd84116..954dbe5d2b9 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -66,7 +66,7 @@ %span CI Services = nav_link path: 'events#index' do - = link_to ci_project_events_path(@project.ensure_gitlab_ci_project) do + = link_to ci_project_events_path(@project.gitlab_ci_project) do = icon('book fw') %span CI Events diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml index e74a3a62672..ff146f7389b 100644 --- a/app/views/projects/builds/_build.html.haml +++ b/app/views/projects/builds/_build.html.haml @@ -29,9 +29,9 @@ - build.tags.each do |tag| %span.label.label-primary = tag - - if build.try(:trigger_request) + - if build.trigger_request %span.label.label-info triggered - - if build.try(:allow_failure) + - if build.allow_failure %span.label.label-danger allowed to fail %td.duration -- cgit v1.2.1 From 72f428c7d217a5c40ed87d68ab9100e4c8754633 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 8 Oct 2015 13:28:26 +0200 Subject: Improve performance of User.by_login Performance is improved in two steps: 1. On PostgreSQL an expression index is used for checking lower(email) and lower(username). 2. The check to determine if we're searching for a username or Email is moved to Ruby. Thanks to @haynes for suggesting and writing the initial implementation of this. Moving the check to Ruby makes this method an additional 1.5 times faster compared to doing the check in the SQL query. With performance being improved I've now also tweaked the amount of iterations required by the User.by_login benchmark. This method now runs between 900 and 1000 iterations per second. --- CHANGELOG | 1 + app/models/user.rb | 10 ++++++++-- ...1008110232_add_users_lower_username_email_indexes.rb | 17 +++++++++++++++++ lib/tasks/migrate/setup_postgresql.rake | 2 ++ spec/benchmarks/models/user_spec.rb | 4 +++- 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20151008110232_add_users_lower_username_email_indexes.rb diff --git a/CHANGELOG b/CHANGELOG index 07ff3d6de42..12561d6ceec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.1.0 (unreleased) - Make diff file view easier to use on mobile screens (Stan Hu) + - Improved performance of finding users by username or Email address - Add support for creating directories from Files page (Stan Hu) - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) diff --git a/app/models/user.rb b/app/models/user.rb index 889d2d3b867..17ccb3b8788 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -68,6 +68,7 @@ class User < ActiveRecord::Base include Referable include Sortable include TokenAuthenticatable + include CaseSensitivity default_value_for :admin, false default_value_for :can_create_group, gitlab_config.default_can_create_group @@ -273,8 +274,13 @@ class User < ActiveRecord::Base end def by_login(login) - where('lower(username) = :value OR lower(email) = :value', - value: login.to_s.downcase).first + return nil unless login + + if login.include?('@'.freeze) + unscoped.iwhere(email: login).take + else + unscoped.iwhere(username: login).take + end end def find_by_username!(username) diff --git a/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb new file mode 100644 index 00000000000..2f2dc776785 --- /dev/null +++ b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb @@ -0,0 +1,17 @@ +class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration + disable_ddl_transaction! + + def up + return unless Gitlab::Database.postgresql? + + execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_username ON users (LOWER(username));' + execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_email ON users (LOWER(email));' + end + + def down + return unless Gitlab::Database.postgresql? + + remove_index :users, :index_on_users_lower_username + remove_index :users, :index_on_users_lower_email + end +end diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index bf6894a8351..141a0b74ec0 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -1,6 +1,8 @@ require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes') +require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes') desc 'GitLab | Sets up PostgreSQL' task setup_postgresql: :environment do NamespacesProjectsPathLowerIndexes.new.up + AddUsersLowerUsernameEmailIndexes.new.up end diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb index 168be20b7a5..cc5c3904193 100644 --- a/spec/benchmarks/models/user_spec.rb +++ b/spec/benchmarks/models/user_spec.rb @@ -11,7 +11,9 @@ describe User, benchmark: true do end end - let(:iterations) { 1000 } + # The iteration count is based on the query taking little over 1 ms when + # using PostgreSQL. + let(:iterations) { 900 } describe 'using a capitalized username' do benchmark_subject { User.by_login('Alice') } -- cgit v1.2.1 From 693e63f5234032aa1abbc226c0d6337d6ea810ed Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 12 Oct 2015 16:58:09 +0200 Subject: Allow avatar_icon to operate on a User If the User object is already known before calling this method being able to re-use said object can save us an extra SQL query. --- app/helpers/application_helper.rb | 10 +++++++--- spec/helpers/application_helper_spec.rb | 9 +++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cab2278adb7..8ecdeaf8e76 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -68,13 +68,17 @@ module ApplicationHelper end end - def avatar_icon(user_email = '', size = nil) - user = User.find_by(email: user_email) + def avatar_icon(user_or_email = nil, size = nil) + if user_or_email.is_a?(User) + user = user_or_email + else + user = User.find_by(email: user_or_email) + end if user user.avatar_url(size) || default_avatar else - gravatar_icon(user_email, size) + gravatar_icon(user_or_email, size) end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 742420f550e..1dfae0fbd3f 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -99,6 +99,15 @@ describe ApplicationHelper do helper.avatar_icon('foo@example.com', 20) end + + describe 'using a User' do + it 'should return an URL for the avatar' do + user = create(:user, avatar: File.open(avatar_file_path)) + + expect(helper.avatar_icon(user).to_s). + to match("/uploads/user/avatar/#{user.id}/banana_sample.gif") + end + end end describe 'gravatar_icon' do -- cgit v1.2.1 From 949635636728fa388d983b180b7ab4e55aa8caa9 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 12 Oct 2015 17:03:12 +0200 Subject: Re-use User objects for avatar_icon where possible This removes the need for running an extra SQL query in these cases. --- app/views/admin/users/show.html.haml | 2 +- app/views/dashboard/milestones/_issue.html.haml | 2 +- app/views/dashboard/milestones/_merge_request.html.haml | 2 +- app/views/dashboard/milestones/show.html.haml | 2 +- app/views/groups/group_members/_group_member.html.haml | 2 +- app/views/groups/milestones/_issue.html.haml | 2 +- app/views/groups/milestones/_merge_request.html.haml | 2 +- app/views/groups/milestones/show.html.haml | 2 +- app/views/layouts/_page.html.haml | 2 +- app/views/layouts/ci/_page.html.haml | 2 +- app/views/profiles/show.html.haml | 2 +- app/views/projects/milestones/_issue.html.haml | 2 +- app/views/projects/milestones/_merge_request.html.haml | 2 +- app/views/projects/milestones/show.html.haml | 2 +- app/views/projects/project_members/_project_member.html.haml | 2 +- app/views/users/show.html.haml | 4 ++-- 16 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index a383ea57384..231bcb0426f 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -8,7 +8,7 @@ = @user.name %ul.well-list %li - = image_tag avatar_icon(@user.email, 60), class: "avatar s60" + = image_tag avatar_icon(@user, 60), class: "avatar s60" %li %span.light Profile page: %strong diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml index f689b9698eb..1408ebdd5dc 100644 --- a/app/views/dashboard/milestones/_issue.html.haml +++ b/app/views/dashboard/milestones/_issue.html.haml @@ -7,4 +7,4 @@ = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title .pull-right.assignee-icon - if issue.assignee - = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" + = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16" diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml index 8f5c4cce529..77c46de030b 100644 --- a/app/views/dashboard/milestones/_merge_request.html.haml +++ b/app/views/dashboard/milestones/_merge_request.html.haml @@ -7,4 +7,4 @@ = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title .pull-right.assignee-icon - if merge_request.assignee - = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16" + = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16" diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 0d204ced7ea..d5c4a44fef6 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -79,7 +79,7 @@ - @dashboard_milestone.participants.each do |user| %li = link_to user, title: user.name, class: "darken" do - = image_tag avatar_icon(user.email, 32), class: "avatar s32" + = image_tag avatar_icon(user, 32), class: "avatar s32" %strong= truncate(user.name, lenght: 40) %br %small.cgray= user.username diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index b5f359279d5..3c19381321a 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -5,7 +5,7 @@ %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %span{class: ("list-item-name" if show_controls)} - if member.user - = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(user, 16), class: "avatar s16", alt: '' %strong = link_to user.name, user_path(user) %span.cgray= user.username diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml index 09f9b4b8969..9b85d83d6d8 100644 --- a/app/views/groups/milestones/_issue.html.haml +++ b/app/views/groups/milestones/_issue.html.haml @@ -7,4 +7,4 @@ = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title .pull-right.assignee-icon - if issue.assignee - = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: '' diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml index d0d1426762b..e3aa4aad198 100644 --- a/app/views/groups/milestones/_merge_request.html.haml +++ b/app/views/groups/milestones/_merge_request.html.haml @@ -7,4 +7,4 @@ = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title .pull-right.assignee-icon - if merge_request.assignee - = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: '' diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 0c213f42186..c6cde97c585 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -87,7 +87,7 @@ - @group_milestone.participants.each do |user| %li = link_to user, title: user.name, class: "darken" do - = image_tag avatar_icon(user.email, 32), class: "avatar s32" + = image_tag avatar_icon(user, 32), class: "avatar s32" %strong= truncate(user.name, lenght: 40) %br %small.cgray= user.username diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 1a883e20e89..352b8040cf4 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -18,7 +18,7 @@ = render partial: 'layouts/collapse_button' - if current_user = link_to current_user, class: 'sidebar-user' do - = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36' + = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36' .username = current_user.username .content-wrapper diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml index bb5ec727bff..ab3e29c3f42 100644 --- a/app/views/layouts/ci/_page.html.haml +++ b/app/views/layouts/ci/_page.html.haml @@ -15,7 +15,7 @@ = render partial: 'layouts/collapse_button' - if current_user = link_to current_user, class: 'sidebar-user' do - = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36' + = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36' .username = current_user.username .content-wrapper diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 47412e2ef0c..ac7355dde1f 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -68,7 +68,7 @@ .col-md-5 .light-well - = image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160' + = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' .clearfix .profile-avatar-form-option diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml index 88fccfe4981..133d802aaca 100644 --- a/app/views/projects/milestones/_issue.html.haml +++ b/app/views/projects/milestones/_issue.html.haml @@ -1,7 +1,7 @@ %li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) } .pull-right.assignee-icon - if issue.assignee - = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: '' %span = link_to [@project.namespace.becomes(Namespace), @project, issue] do %span.cgray ##{issue.iid} diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index 0d7a118569a..a1033607c5d 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -5,4 +5,4 @@ = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title .pull-right.assignee-icon - if merge_request.assignee - = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: '' diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 4eeb0621e52..3a898dfbcfd 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -104,7 +104,7 @@ - @users.each do |user| %li = link_to user, title: user.name, class: "darken" do - = image_tag avatar_icon(user.email, 32), class: "avatar s32" + = image_tag avatar_icon(user, 32), class: "avatar s32" %strong= truncate(user.name, lenght: 40) %br %small.cgray= user.username diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml index 860a997cff8..76c46d1d806 100644 --- a/app/views/projects/project_members/_project_member.html.haml +++ b/app/views/projects/project_members/_project_member.html.haml @@ -4,7 +4,7 @@ %li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)} %span.list-item-name - if member.user - = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(user, 16), class: "avatar s16", alt: '' %strong = link_to user.name, user_path(user) %span.cgray= user.username diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 11beb3e3239..2a64708d07c 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -9,8 +9,8 @@ .row %section.col-md-7 .header-with-avatar - = link_to avatar_icon(@user.email, 400), target: '_blank' do - = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' + = link_to avatar_icon(@user, 400), target: '_blank' do + = image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: '' %h3 = @user.name - if @user == current_user -- cgit v1.2.1 From ff8f7fb0a111a5db2a50aa40e967cae699b0b245 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 12 Oct 2015 17:22:22 +0200 Subject: Re-use User for avatars in link_to_member --- app/helpers/projects_helper.rb | 2 +- spec/helpers/projects_helper_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a0220af4c30..6dc2c381c29 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -29,7 +29,7 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] + author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] # Build name span tag author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 53e56ebff44..f2efb528aeb 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -70,4 +70,18 @@ describe ProjectsHelper do expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme") end end + + describe 'link_to_member' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, group: group) } + let(:user) { create(:user) } + + describe 'using the default options' do + it 'returns an HTML link to the user' do + link = helper.link_to_member(project, user) + + expect(link).to match(%r{/u/#{user.username}}) + end + end + end end -- cgit v1.2.1 From 1554786c6ac49b452697d2f7a3e8daf6e3ac36d3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 13 Oct 2015 11:49:01 +0200 Subject: Eager load various issue/note associations This ensures we don't end up running N+1 queries for the objects in the affected collections. --- app/controllers/projects/issues_controller.rb | 2 +- app/models/concerns/issuable.rb | 7 ++++++- app/models/group.rb | 2 +- app/models/note.rb | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4612abcbae8..27aa70a992b 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -57,7 +57,7 @@ class Projects::IssuesController < Projects::ApplicationController def show @participants = @issue.participants(current_user) @note = @project.notes.new(noteable: @issue) - @notes = @issue.notes.inc_author.fresh + @notes = @issue.notes.inc_associations.fresh @noteable = @issue respond_with(@issue) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 4db4ffb2e79..0e8bcc1a4ec 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -47,7 +47,8 @@ module Issuable prefix: true attr_mentionable :title, :description - participant :author, :assignee, :notes, :mentioned_users + + participant :author, :assignee, :notes_with_associations, :mentioned_users end module ClassMethods @@ -176,6 +177,10 @@ module Issuable self.class.to_s.underscore end + def notes_with_associations + notes.includes(:author, :project) + end + private def filter_superceded_votes(votes, notes) diff --git a/app/models/group.rb b/app/models/group.rb index 9cd146bb73b..465c22d23ac 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -64,7 +64,7 @@ class Group < Namespace end def owners - @owners ||= group_members.owners.map(&:user) + @owners ||= group_members.owners.includes(:user).map(&:user) end def add_users(user_ids, access_level, current_user = nil) diff --git a/app/models/note.rb b/app/models/note.rb index ee0c14598f3..3ad9895a935 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -59,6 +59,7 @@ class Note < ActiveRecord::Base scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author, ->{ includes(:author) } + scope :inc_associations, ->{ includes(:author, :noteable, :updated_by) } serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } -- cgit v1.2.1 From 39fcd445fa5a6af19ead78b47de84a199e7e7d50 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 13 Oct 2015 11:49:50 +0200 Subject: Use note.author for issue comment avatars This removes the need for running a query to find the User object again based on the supplied Email address. --- app/views/projects/notes/_note.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 1638ad6891a..71347faea90 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -2,7 +2,7 @@ .timeline-entry-inner .timeline-icon = link_to user_path(note.author) do - = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: '' + = image_tag avatar_icon(note.author), class: 'avatar s40', alt: '' .timeline-content .note-header - if note_editable?(note) -- cgit v1.2.1 From fa3d7db39077611703edf7d328e33f0049f5d341 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 13 Oct 2015 11:54:06 +0200 Subject: Added Bullet to the Gemfile This can be used to resolve N+1 query problems. Bullet is disabled by default and can be enabled by starting Rails with the environment variable ENABLE_BULLET set to a non empty value (e.g. "true"). --- Gemfile | 1 + Gemfile.lock | 5 +++++ config/initializers/bullet.rb | 6 ++++++ 3 files changed, 12 insertions(+) create mode 100644 config/initializers/bullet.rb diff --git a/Gemfile b/Gemfile index 9b2416ab45f..d3c1fd50e3a 100644 --- a/Gemfile +++ b/Gemfile @@ -224,6 +224,7 @@ group :development do gem 'quiet_assets', '~> 1.0.2' gem 'rack-mini-profiler', '~> 0.9.0', require: false gem 'rerun', '~> 0.10.0' + gem 'bullet', require: false # Better errors handler gem 'better_errors', '~> 1.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 8cc400aa55c..48804dba8ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,6 +87,9 @@ GEM terminal-table (~> 1.4) browser (1.0.0) builder (3.2.2) + bullet (4.14.9) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.9.0) byebug (6.0.2) cal-heatmap-rails (0.0.1) capybara (2.4.4) @@ -755,6 +758,7 @@ GEM unicorn-worker-killer (0.4.3) get_process_mem (~> 0) unicorn (~> 4) + uniform_notifier (1.9.0) uuid (2.3.8) macaddr (~> 1.0) version_sorter (2.0.0) @@ -800,6 +804,7 @@ DEPENDENCIES bootstrap-sass (~> 3.0) brakeman (= 3.0.1) browser (~> 1.0.0) + bullet byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.4.0) diff --git a/config/initializers/bullet.rb b/config/initializers/bullet.rb new file mode 100644 index 00000000000..95e82966c7a --- /dev/null +++ b/config/initializers/bullet.rb @@ -0,0 +1,6 @@ +if ENV['ENABLE_BULLET'] + require 'bullet' + + Bullet.enable = true + Bullet.console = true +end -- cgit v1.2.1 From 7971ed5daca4bfb1310d6458f323377391a99429 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 13 Oct 2015 17:21:15 +0200 Subject: Added active_record_query_trace This can be used to track down where queries originate from, regardless of whether they're caused by N+1 problems or not. This can be enabled by setting the environment variable ENABLE_QUERY_TRACE to a non-empty value (e.g. "true"). --- Gemfile | 1 + Gemfile.lock | 2 ++ config/initializers/active_record_query_trace.rb | 5 +++++ 3 files changed, 8 insertions(+) create mode 100644 config/initializers/active_record_query_trace.rb diff --git a/Gemfile b/Gemfile index d3c1fd50e3a..0524803f319 100644 --- a/Gemfile +++ b/Gemfile @@ -225,6 +225,7 @@ group :development do gem 'rack-mini-profiler', '~> 0.9.0', require: false gem 'rerun', '~> 0.10.0' gem 'bullet', require: false + gem 'active_record_query_trace', require: false # Better errors handler gem 'better_errors', '~> 1.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 48804dba8ec..ed7ea5bb78b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,6 +17,7 @@ GEM activesupport (= 4.1.12) builder (~> 3.1) erubis (~> 2.7.0) + active_record_query_trace (1.5) activemodel (4.1.12) activesupport (= 4.1.12) builder (~> 3.1) @@ -788,6 +789,7 @@ PLATFORMS DEPENDENCIES RedCloth (~> 4.2.9) ace-rails-ap (~> 2.0.1) + active_record_query_trace activerecord-deprecated_finders (~> 1.0.3) activerecord-session_store (~> 0.1.0) acts-as-taggable-on (~> 3.4) diff --git a/config/initializers/active_record_query_trace.rb b/config/initializers/active_record_query_trace.rb new file mode 100644 index 00000000000..4b3c2803b3b --- /dev/null +++ b/config/initializers/active_record_query_trace.rb @@ -0,0 +1,5 @@ +if ENV['ENABLE_QUERY_TRACE'] + require 'active_record_query_trace' + + ActiveRecordQueryTrace.enabled = 'true' +end -- cgit v1.2.1 From d4832b0341643d90df8323a5564521d3bcd3abc1 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 14 Oct 2015 12:21:57 +0200 Subject: Added rack-lineprof for development This can be used to measure the time (roughly) spent on a per line basis. This can also be used to measure timings for views, for example by adding the following to a URL: ?lineprof=app/views/projects/notes/_note rack-lineprof is only enabled when: 1. The application runs in development mode 2. The used Ruby is MRI 3. The environment variable ENABLE_LINEPROF is set to a non-empty value --- Gemfile | 1 + Gemfile.lock | 8 ++++++++ config/environments/development.rb | 2 +- config/initializers/rack_lineprof.rb | 31 +++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 config/initializers/rack_lineprof.rb diff --git a/Gemfile b/Gemfile index 0524803f319..a58fc34bb74 100644 --- a/Gemfile +++ b/Gemfile @@ -226,6 +226,7 @@ group :development do gem 'rerun', '~> 0.10.0' gem 'bullet', require: false gem 'active_record_query_trace', require: false + gem 'rack-lineprof', platform: :mri # Better errors handler gem 'better_errors', '~> 1.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index ed7ea5bb78b..d7feaa0ec58 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -138,6 +138,7 @@ GEM daemons (1.2.3) database_cleaner (1.4.1) debug_inspector (0.0.2) + debugger-ruby_core_source (1.3.8) default_value_for (3.0.1) activerecord (>= 3.2.0, < 5.0) descendants_tracker (0.0.4) @@ -506,6 +507,10 @@ GEM rack-attack (4.3.0) rack rack-cors (0.4.0) + rack-lineprof (0.0.3) + rack (~> 1.5) + rblineprof (~> 0.3.6) + term-ansicolor (~> 1.3) rack-mini-profiler (0.9.7) rack (>= 1.1.3) rack-mount (0.8.3) @@ -544,6 +549,8 @@ GEM rb-fsevent (0.9.5) rb-inotify (0.9.5) ffi (>= 0.5.0) + rblineprof (0.3.6) + debugger-ruby_core_source (~> 1.3) rbvmomi (1.8.2) builder nokogiri (>= 1.4.1) @@ -889,6 +896,7 @@ DEPENDENCIES quiet_assets (~> 1.0.2) rack-attack (~> 4.3.0) rack-cors (~> 0.4.0) + rack-lineprof rack-mini-profiler (~> 0.9.0) rack-oauth2 (~> 1.0.5) rails (= 4.1.12) diff --git a/config/environments/development.rb b/config/environments/development.rb index d7d6aed1602..827a110c249 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -24,7 +24,7 @@ Gitlab::Application.configure do # Expands the lines which load the assets # config.assets.debug = true - + # Adds additional error checking when serving assets at runtime. # Checks for improperly declared sprockets dependencies. # Raises helpful error messages. diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb new file mode 100644 index 00000000000..80d232c3d36 --- /dev/null +++ b/config/initializers/rack_lineprof.rb @@ -0,0 +1,31 @@ +# The default colors of rack-lineprof can be very hard to look at in terminals +# with darker backgrounds. This patch tweaks the colors a bit so the output is +# actually readable. +if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF'] + Gitlab::Application.config.middleware.use(Rack::Lineprof) + + module Rack + class Lineprof + class Sample < Rack::Lineprof::Sample.superclass + def format(*) + formatted = if level == CONTEXT + sprintf " | % 3i %s", line, code + else + sprintf "% 8.1fms %5i | % 3i %s", ms, calls, line, code + end + + case level + when CRITICAL + color.red formatted + when WARNING + color.yellow formatted + when NOMINAL + color.white formatted + else # CONTEXT + formatted + end + end + end + end + end +end -- cgit v1.2.1 From 8237da0d4a250b4cb07e85caac3c43e11e282ebb Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 14 Oct 2015 12:44:10 +0200 Subject: Eager load note projects when viewing issues --- app/models/note.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/note.rb b/app/models/note.rb index 3ad9895a935..d0b30c55791 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -59,7 +59,10 @@ class Note < ActiveRecord::Base scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author, ->{ includes(:author) } - scope :inc_associations, ->{ includes(:author, :noteable, :updated_by) } + + scope :inc_associations, -> do + includes(:author, :noteable, :updated_by, :project) + end serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } -- cgit v1.2.1 From b5f8161daeefeaa66e810e9dddec43959333d8a7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 14 Oct 2015 14:53:06 +0200 Subject: Eager load project associations for notes This ensures that when viewing an issue each note already has the associated project, project members, group and group members available. Since this information is requres for every note this results in quite the reduction of SQL queries being executed. --- app/models/note.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/note.rb b/app/models/note.rb index d0b30c55791..196512c4715 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -61,7 +61,8 @@ class Note < ActiveRecord::Base scope :inc_author, ->{ includes(:author) } scope :inc_associations, -> do - includes(:author, :noteable, :updated_by, :project) + includes(:author, :noteable, :updated_by, + project: [:project_members, {group: [:group_members]}]) end serialize :st_diff -- cgit v1.2.1 From 3025b711419fd1813baea5e2a2925c6ccf8d455a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 14 Oct 2015 14:54:04 +0200 Subject: Improve ProjectTeam#max_member_access performance By comparing objects in Ruby we can greatly improve the performance of this method. In the worst case (should no data be eager loaded) this will run the same amount of queries as before, in the best case (when data _is_ eager loadeD) it requires no queries at all. The added benchmark used to produce around 273 iterations per second. With this commit this has been increased to almost 40 000 iterations per second: a speedup of roughly 145 times. Combined with eager loading Note associations this results in about 30 queries less when viewing a single issue, this in turn cuts down the loading time by 30-40%. --- app/models/project_team.rb | 19 ++++++++++++++++--- spec/benchmarks/models/project_team_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 spec/benchmarks/models/project_team_spec.rb diff --git a/app/models/project_team.rb b/app/models/project_team.rb index f602a965364..9f380a382cb 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -139,15 +139,28 @@ class ProjectTeam Gitlab::Access.options.key max_member_access(user_id) end + # This method assumes project and group members are eager loaded for optimal + # performance. def max_member_access(user_id) access = [] - access << project.project_members.find_by(user_id: user_id).try(:access_field) + + project.project_members.each do |member| + if member.user_id == user_id + access << member.access_field if member.access_field + break + end + end if group - access << group.group_members.find_by(user_id: user_id).try(:access_field) + group.group_members.each do |member| + if member.user_id == user_id + access << member.access_field if member.access_field + break + end + end end - access.compact.max + access.max end private diff --git a/spec/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb new file mode 100644 index 00000000000..8b039ef7317 --- /dev/null +++ b/spec/benchmarks/models/project_team_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe ProjectTeam, benchmark: true do + describe '#max_member_access' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, group: group) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + + 5.times do + project.team << [create(:user), :reporter] + + project.group.add_user(create(:user), :reporter) + end + end + + benchmark_subject { project.team.max_member_access(user.id) } + + it { is_expected.to iterate_per_second(35000) } + end +end -- cgit v1.2.1 From f3980e230f31d6589592b965700936a7bf2a9731 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 14 Oct 2015 16:03:22 +0200 Subject: Don't use link_to/image_tag where not needed In these particular instances we can just use HAML tags directly. This can shave off some time spent loading issue pages, though this depends on the amount of comments being displayed. When viewing https://gitlab.com/gitlab-org/gitlab-ce/issues/2164 locally these changes reduce loading time by about 400 ms in total. --- app/views/projects/_md_preview.html.haml | 4 ++-- app/views/projects/_zen.html.haml | 6 +++--- app/views/projects/notes/_note.html.haml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 507757f6a2b..7b21095ea3e 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -2,10 +2,10 @@ .md-header.clearfix %ul.center-top-menu %li.active - = link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do + %a.js-md-write-button(href="#md-write-holder" tabindex="-1") Write %li - = link_to '#md-preview-holder', class: 'js-md-preview-button', tabindex: '-1' do + %a.js-md-preview-button(href="md-preview-holder" tabindex="-1") Preview - if defined?(referenced_users) && referenced_users diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index 6a41cdbc907..63ebfc9381f 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,10 +1,10 @@ .zennable - %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' } + %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox") .zen-backdrop - classes << ' js-gfm-input markdown-area' = f.text_area attr, class: classes, placeholder: '' - = link_to nil, class: 'zen-enter-link', tabindex: '-1' do + %a.zen-enter-link(tabindex="-1" href="#") %i.fa.fa-expand Edit in fullscreen - = link_to nil, class: 'zen-leave-link' do + %a.zen-leave-link(href="#") %i.fa.fa-compress diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 71347faea90..5d184730796 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,8 +1,8 @@ %li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } } .timeline-entry-inner .timeline-icon - = link_to user_path(note.author) do - = image_tag avatar_icon(note.author), class: 'avatar s40', alt: '' + %a{href: user_path(note.author)} + %img.avatar.s40{src: avatar_icon(note.author), alt: ''} .timeline-content .note-header - if note_editable?(note) @@ -25,7 +25,7 @@ = '@' + note.author.username %span.note-last-update - = link_to "##{dom_id(note)}", name: dom_id(note), title: "Link here" do + %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'} = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago') - if note.updated_at != note.created_at %span -- cgit v1.2.1 From 9f5811116ce56afe8bd2e46d92523e7240670016 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 14 Oct 2015 17:16:16 +0200 Subject: Changelog entry for issue page speedups --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 07ff3d6de42..52437a241b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.1.0 (unreleased) + - Speed up load times of issue detail pages by roughly 1.5x - Make diff file view easier to use on mobile screens (Stan Hu) - Add support for creating directories from Files page (Stan Hu) - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) -- cgit v1.2.1 From e5925d073ea09072790856da1569865d5c45e408 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 15 Oct 2015 10:41:09 +0200 Subject: Renamed Note.inc_associations to with_associations --- app/controllers/projects/issues_controller.rb | 2 +- app/models/note.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 27aa70a992b..97485c101fb 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -57,7 +57,7 @@ class Projects::IssuesController < Projects::ApplicationController def show @participants = @issue.participants(current_user) @note = @project.notes.new(noteable: @issue) - @notes = @issue.notes.inc_associations.fresh + @notes = @issue.notes.with_associations.fresh @noteable = @issue respond_with(@issue) diff --git a/app/models/note.rb b/app/models/note.rb index 196512c4715..dc110f4dc35 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -60,7 +60,7 @@ class Note < ActiveRecord::Base scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author, ->{ includes(:author) } - scope :inc_associations, -> do + scope :with_associations, -> do includes(:author, :noteable, :updated_by, project: [:project_members, {group: [:group_members]}]) end -- cgit v1.2.1 From bed29940efc0c76dd966b26acb5dfb00d61e601e Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 15 Oct 2015 10:42:48 +0200 Subject: Fixed Rubocop styling issues --- app/models/note.rb | 2 +- config/initializers/rack_lineprof.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index dc110f4dc35..ebbdd2f33a1 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -62,7 +62,7 @@ class Note < ActiveRecord::Base scope :with_associations, -> do includes(:author, :noteable, :updated_by, - project: [:project_members, {group: [:group_members]}]) + project: [:project_members, { group: [:group_members] }]) end serialize :st_diff diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb index 80d232c3d36..f0c006d811b 100644 --- a/config/initializers/rack_lineprof.rb +++ b/config/initializers/rack_lineprof.rb @@ -9,10 +9,10 @@ if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF'] class Sample < Rack::Lineprof::Sample.superclass def format(*) formatted = if level == CONTEXT - sprintf " | % 3i %s", line, code - else - sprintf "% 8.1fms %5i | % 3i %s", ms, calls, line, code - end + sprintf " | % 3i %s", line, code + else + sprintf "% 8.1fms %5i | % 3i %s", ms, calls, line, code + end case level when CRITICAL -- cgit v1.2.1 From 98e666ab6a61ef67c2ba15d31839fd1cf414d587 Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Thu, 15 Oct 2015 09:09:01 +0200 Subject: Improve invalidation of stored service password if the endpoint URL is changed Password can now be specified at the same time as the new URL, and the service template admin pages now work. --- app/controllers/admin/services_controller.rb | 8 +- app/controllers/projects/services_controller.rb | 8 +- app/models/project_services/bamboo_service.rb | 2 +- app/models/project_services/teamcity_service.rb | 2 +- app/models/service.rb | 35 +++++-- .../models/project_services/bamboo_service_spec.rb | 74 ++++++++++---- .../project_services/teamcity_service_spec.rb | 73 ++++++++++---- spec/models/service_spec.rb | 110 +++++++++++++++++++-- 8 files changed, 259 insertions(+), 53 deletions(-) diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index a62170662e1..46133588332 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController end def application_services_params - params.permit(:id, + application_services_params = params.permit(:id, service: Projects::ServicesController::ALLOWED_PARAMS) + if application_services_params[:service].is_a?(Hash) + Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param| + application_services_params[:service].delete(param) if application_services_params[:service][param].blank? + end + end + application_services_params end end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 3047ee8a1ff..129068ef019 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, :server_host, :server_port, :default_irc_uri, :enable_ssl_verification] + + # Parameters to ignore if no value is specified + FILTER_BLANK_PARAMS = [:password] + # Authorize before_action :authorize_admin_project! before_action :service, only: [:edit, :update, :test] @@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController def service_params service_params = params.require(:service).permit(ALLOWED_PARAMS) - service_params.delete("password") if service_params["password"].blank? + FILTER_BLANK_PARAMS.each do |param| + service_params.delete(param) if service_params[param].blank? + end service_params end end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 5f5255ab487..d31b12f539e 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -48,7 +48,7 @@ class BambooService < CiService end def reset_password - if prop_updated?(:bamboo_url) + if bamboo_url_changed? && !password_touched? self.password = nil end end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index fb11cad352e..0b022461250 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -45,7 +45,7 @@ class TeamcityService < CiService end def reset_password - if prop_updated?(:teamcity_url) + if teamcity_url_changed? && !password_touched? self.password = nil end end diff --git a/app/models/service.rb b/app/models/service.rb index 7e845d565b1..d610abd1683 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -33,6 +33,8 @@ class Service < ActiveRecord::Base after_initialize :initialize_properties + after_commit :reset_updated_properties + belongs_to :project has_one :service_hook @@ -103,6 +105,7 @@ class Service < ActiveRecord::Base # Provide convenient accessor methods # for each serialized property. + # Also keep track of updated properties in a similar way as ActiveModel::Dirty def self.prop_accessor(*args) args.each do |arg| class_eval %{ @@ -111,21 +114,39 @@ class Service < ActiveRecord::Base end def #{arg}=(value) + updated_properties['#{arg}'] = #{arg} unless #{arg}_changed? self.properties['#{arg}'] = value end + + def #{arg}_changed? + #{arg}_touched? && #{arg} != #{arg}_was + end + + def #{arg}_touched? + updated_properties.include?('#{arg}') + end + + def #{arg}_was + updated_properties['#{arg}'] + end } end end - # ActiveRecord does not provide a mechanism to track changes in serialized keys. - # This is why we need to perform extra query to do it mannually. - def prop_updated?(prop_name) - relation_name = self.type.underscore - previous_value = project.send(relation_name).send(prop_name) - return false if previous_value.nil? - previous_value != send(prop_name) + # Returns a hash of the properties that have been assigned a new value since last save, + # indicating their original values (attr => original value). + # ActiveRecord does not provide a mechanism to track changes in serialized keys, + # so we need a specific implementation for service properties. + # This allows to track changes to properties set with the accessor methods, + # but not direct manipulation of properties hash. + def updated_properties + @updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new end + def reset_updated_properties + @updated_properties = nil + end + def async_execute(data) return unless supported_events.include?(data[:object_kind]) diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index f8a3493f52d..c34b2487ecf 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -30,27 +30,65 @@ describe BambooService, models: true do let(:user) { create(:user) } let(:project) { create(:project) } - before do - @bamboo_service = BambooService.create( - project: create(:project), - properties: { - bamboo_url: 'http://gitlab.com', - username: 'mic', - password: "password" - } - ) - end + context "when a password was previously set" do + before do + @bamboo_service = BambooService.create( + project: create(:project), + properties: { + bamboo_url: 'http://gitlab.com', + username: 'mic', + password: "password" + } + ) + end + + it "reset password if url changed" do + @bamboo_service.bamboo_url = 'http://gitlab1.com' + @bamboo_service.save + expect(@bamboo_service.password).to be_nil + end + + it "does not reset password if username changed" do + @bamboo_service.username = "some_name" + @bamboo_service.save + expect(@bamboo_service.password).to eq("password") + end + + it "does not reset password if new url is set together with password, even if it's the same password" do + @bamboo_service.bamboo_url = 'http://gitlab_edited.com' + @bamboo_service.password = 'password' + @bamboo_service.save + expect(@bamboo_service.password).to eq("password") + expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com") + end - it "reset password if url changed" do - @bamboo_service.bamboo_url = 'http://gitlab1.com' - @bamboo_service.save - expect(@bamboo_service.password).to be_nil + it "should reset password if url changed, even if setter called multiple times" do + @bamboo_service.bamboo_url = 'http://gitlab1.com' + @bamboo_service.bamboo_url = 'http://gitlab1.com' + @bamboo_service.save + expect(@bamboo_service.password).to be_nil + end end + + context "when no password was previously set" do + before do + @bamboo_service = BambooService.create( + project: create(:project), + properties: { + bamboo_url: 'http://gitlab.com', + username: 'mic' + } + ) + end + + it "saves password if new url is set together with password" do + @bamboo_service.bamboo_url = 'http://gitlab_edited.com' + @bamboo_service.password = 'password' + @bamboo_service.save + expect(@bamboo_service.password).to eq("password") + expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com") + end - it "does not reset password if username changed" do - @bamboo_service.username = "some_name" - @bamboo_service.save - expect(@bamboo_service.password).to eq("password") end end end diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 3dbd2346bcc..f26b47a856c 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -30,27 +30,64 @@ describe TeamcityService, models: true do let(:user) { create(:user) } let(:project) { create(:project) } - before do - @teamcity_service = TeamcityService.create( - project: create(:project), - properties: { - teamcity_url: 'http://gitlab.com', - username: 'mic', - password: "password" - } - ) - end + context "when a password was previously set" do + before do + @teamcity_service = TeamcityService.create( + project: create(:project), + properties: { + teamcity_url: 'http://gitlab.com', + username: 'mic', + password: "password" + } + ) + end + + it "reset password if url changed" do + @teamcity_service.teamcity_url = 'http://gitlab1.com' + @teamcity_service.save + expect(@teamcity_service.password).to be_nil + end + + it "does not reset password if username changed" do + @teamcity_service.username = "some_name" + @teamcity_service.save + expect(@teamcity_service.password).to eq("password") + end + + it "does not reset password if new url is set together with password, even if it's the same password" do + @teamcity_service.teamcity_url = 'http://gitlab_edited.com' + @teamcity_service.password = 'password' + @teamcity_service.save + expect(@teamcity_service.password).to eq("password") + expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com") + end - it "reset password if url changed" do - @teamcity_service.teamcity_url = 'http://gitlab1.com' - @teamcity_service.save - expect(@teamcity_service.password).to be_nil + it "should reset password if url changed, even if setter called multiple times" do + @teamcity_service.teamcity_url = 'http://gitlab1.com' + @teamcity_service.teamcity_url = 'http://gitlab1.com' + @teamcity_service.save + expect(@teamcity_service.password).to be_nil + end end + + context "when no password was previously set" do + before do + @teamcity_service = TeamcityService.create( + project: create(:project), + properties: { + teamcity_url: 'http://gitlab.com', + username: 'mic' + } + ) + end - it "does not reset password if username changed" do - @teamcity_service.username = "some_name" - @teamcity_service.save - expect(@teamcity_service.password).to eq("password") + it "saves password if new url is set together with password" do + @teamcity_service.teamcity_url = 'http://gitlab_edited.com' + @teamcity_service.password = 'password' + @teamcity_service.save + expect(@teamcity_service.password).to eq("password") + expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com") + end end end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index da87ea5b84f..692e5fda3ba 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -104,7 +104,7 @@ describe Service do end end - describe "#prop_updated?" do + describe "{property}_changed?" do let(:service) do BambooService.create( project: create(:project), @@ -116,14 +116,112 @@ describe Service do ) end - it "returns false" do + it "returns false when the property has not been assigned a new value" do service.username = "key_changed" - expect(service.prop_updated?(:bamboo_url)).to be_falsy + expect(service.bamboo_url_changed?).to be_falsy end - it "returns true" do - service.bamboo_url = "http://other.com" - expect(service.prop_updated?(:bamboo_url)).to be_truthy + it "returns true when the property has been assigned a different value" do + service.bamboo_url = "http://example.com" + expect(service.bamboo_url_changed?).to be_truthy + end + + it "returns true when the property has been assigned a different value twice" do + service.bamboo_url = "http://example.com" + service.bamboo_url = "http://example.com" + expect(service.bamboo_url_changed?).to be_truthy + end + + it "returns false when the property has been re-assigned the same value" do + service.bamboo_url = 'http://gitlab.com' + expect(service.bamboo_url_changed?).to be_falsy + end + + it "returns false when the property has been assigned a new value then saved" do + service.bamboo_url = 'http://example.com' + service.save + expect(service.bamboo_url_changed?).to be_falsy + end + end + + describe "{property}_touched?" do + let(:service) do + BambooService.create( + project: create(:project), + properties: { + bamboo_url: 'http://gitlab.com', + username: 'mic', + password: "password" + } + ) + end + + it "returns false when the property has not been assigned a new value" do + service.username = "key_changed" + expect(service.bamboo_url_touched?).to be_falsy + end + + it "returns true when the property has been assigned a different value" do + service.bamboo_url = "http://example.com" + expect(service.bamboo_url_touched?).to be_truthy + end + + it "returns true when the property has been assigned a different value twice" do + service.bamboo_url = "http://example.com" + service.bamboo_url = "http://example.com" + expect(service.bamboo_url_touched?).to be_truthy + end + + it "returns true when the property has been re-assigned the same value" do + service.bamboo_url = 'http://gitlab.com' + expect(service.bamboo_url_touched?).to be_truthy + end + + it "returns false when the property has been assigned a new value then saved" do + service.bamboo_url = 'http://example.com' + service.save + expect(service.bamboo_url_changed?).to be_falsy + end + end + + describe "{property}_was" do + let(:service) do + BambooService.create( + project: create(:project), + properties: { + bamboo_url: 'http://gitlab.com', + username: 'mic', + password: "password" + } + ) + end + + + it "returns nil when the property has not been assigned a new value" do + service.username = "key_changed" + expect(service.bamboo_url_was).to be_nil + end + + it "returns the previous value when the property has been assigned a different value" do + service.bamboo_url = "http://example.com" + expect(service.bamboo_url_was).to eq('http://gitlab.com') + end + + it "returns initial value when the property has been re-assigned the same value" do + service.bamboo_url = 'http://gitlab.com' + expect(service.bamboo_url_was).to eq('http://gitlab.com') + end + + it "returns initial value when the property has been assigned multiple values" do + service.bamboo_url = "http://example.com" + service.bamboo_url = "http://example2.com" + expect(service.bamboo_url_was).to eq('http://gitlab.com') + end + + it "returns nil when the property has been assigned a new value then saved" do + service.bamboo_url = 'http://example.com' + service.save + expect(service.bamboo_url_was).to be_nil end end end -- cgit v1.2.1 From 97e02f4bc99d391c5e1dc0ce8e317be105e6d2b0 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 15 Oct 2015 12:15:18 +0200 Subject: Added documentation on the various profiling tools --- doc/development/profiling.md | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 doc/development/profiling.md diff --git a/doc/development/profiling.md b/doc/development/profiling.md new file mode 100644 index 00000000000..80c86ef921e --- /dev/null +++ b/doc/development/profiling.md @@ -0,0 +1,56 @@ +# Profiling + +To make it easier to track down performance problems GitLab comes with a set of +profiling tools, some of these are available by default while others need to be +explicitly enabled. + +## rack-mini-profiler + +This Gem is enabled by default in development only. It allows you to see the +timings of the various components that made up a web request (e.g. the SQL +queries executed and their execution timings). + +## Bullet + +Bullet is a Gem that can be used to track down N+1 query problems. Because +Bullet adds quite a bit of logging noise it's disabled by default. To enable +Bullet, set the environment variable `ENABLE_BULLET` to a non-empty value before +starting GitLab. For example: + + ENABLE_BULLET=true bundle exec rails s + +Bullet will log query problems to both the Rails log as well as the Chrome +console. + +## ActiveRecord Query Trace + +This Gem adds backtraces for every ActiveRecord query in the Rails console. This +can be useful to track down where a query was executed. Because this Gem adds +quite a bit of noise (5-10 extra lines per ActiveRecord query) it's disabled by +default. To use this Gem you'll need to set `ENABLE_QUERY_TRACE` to a non empty +file before starting GitLab. For example: + + ENABLE_QUERY_TRACE=true bundle exec rails s + +## rack-lineprof + +This is a Gem that can trace the execution time of code on a per line basis. +Because this Gem can add quite a bit of overhead it's disabled by default. To +enable it, set the environment variable `ENABLE_LINEPROF` to a non-empty value. +For example: + + ENABLE_LINEPROF=true bundle exec rails s + +Once enabled you'll need to add a query string parameter to a request to +actually profile code execution. The name of the parameter is `lineprof` and +should be set to a regular expression (minus the starting/ending slash) used to +select what files to profile. To profile all files containing "foo" somewhere in +the path you'd use the following parameter: + + ?lineprof=foo + +Or when filtering for files containing "foo" and "bar" in their path: + + ?lineprof=foo|bar + +Once set the profiling output will be displayed in your terminal. -- cgit v1.2.1 From f7e0357840bb0780ca3ba997aa1e2f4c045f08c7 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 15 Oct 2015 13:22:28 +0200 Subject: Track compatible gitlab-git-http-server version --- GITLAB_GIT_HTTP_SERVER_VERSION | 1 + doc/install/installation.md | 1 + doc/update/7.14-to-8.0.md | 1 + 3 files changed, 3 insertions(+) create mode 100644 GITLAB_GIT_HTTP_SERVER_VERSION diff --git a/GITLAB_GIT_HTTP_SERVER_VERSION b/GITLAB_GIT_HTTP_SERVER_VERSION new file mode 100644 index 00000000000..769ed6ae790 --- /dev/null +++ b/GITLAB_GIT_HTTP_SERVER_VERSION @@ -0,0 +1 @@ +0.2.14 diff --git a/doc/install/installation.md b/doc/install/installation.md index 3c62b11988e..acc4e505971 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -325,6 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git cd gitlab-git-http-server + sudo -u git -H git checkout 0.2.14 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/7.14-to-8.0.md b/doc/update/7.14-to-8.0.md index 7ad4935e839..305017b7048 100644 --- a/doc/update/7.14-to-8.0.md +++ b/doc/update/7.14-to-8.0.md @@ -84,6 +84,7 @@ Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git cd gitlab-git-http-server +sudo -u git -H git checkout 0.2.14 sudo -u git -H make ``` -- cgit v1.2.1 From d843cbe08d2af5af0ab6a0270aca25dad993e8ed Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 15 Oct 2015 13:44:28 +0200 Subject: Sentences end in periods. --- app/views/projects/merge_requests/_show.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index e7ac7a0eaa4..eeaa72ed21b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -36,7 +36,8 @@ - if @merge_request.open? && @merge_request.can_be_merged? .light.append-bottom-20 You can also accept this merge request manually using the - = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" + = succeed '.' do + = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits.present? %ul.merge-request-tabs -- cgit v1.2.1 From 999051bc91f02292e28582ce538f0d147c7b665e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 15 Oct 2015 14:59:06 +0200 Subject: Use gitlab-git-http-server 0.3.0 --- GITLAB_GIT_HTTP_SERVER_VERSION | 2 +- doc/install/installation.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITLAB_GIT_HTTP_SERVER_VERSION b/GITLAB_GIT_HTTP_SERVER_VERSION index 769ed6ae790..0d91a54c7d4 100644 --- a/GITLAB_GIT_HTTP_SERVER_VERSION +++ b/GITLAB_GIT_HTTP_SERVER_VERSION @@ -1 +1 @@ -0.2.14 +0.3.0 diff --git a/doc/install/installation.md b/doc/install/installation.md index acc4e505971..e09890dc296 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -325,7 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git cd gitlab-git-http-server - sudo -u git -H git checkout 0.2.14 + sudo -u git -H git checkout 0.3.0 sudo -u git -H make ### Initialize Database and Activate Advanced Features -- cgit v1.2.1 From 0d09b5fefc635120cf6e4234a401028f815fb326 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Oct 2015 15:49:52 +0200 Subject: Fix builds view count indicator --- app/controllers/projects/builds_controller.rb | 4 ++-- app/views/projects/builds/index.html.haml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 54c01ddf238..816012762ce 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -8,8 +8,7 @@ class Projects::BuildsController < Projects::ApplicationController def index @scope = params[:scope] - @all_builds = project.ci_builds.order('created_at DESC').page(params[:page]).per(30) - + @all_builds = project.ci_builds @builds = case @scope when 'all' @@ -19,6 +18,7 @@ class Projects::BuildsController < Projects::ApplicationController else @all_builds.running_or_pending end + @builds = @builds.order('created_at DESC').page(params[:page]).per(30) end def cancel_all diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index b04784025f1..4d8ca16d98a 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -12,17 +12,17 @@ %li{class: ('active' if @scope.nil?)} = link_to project_builds_path(@project) do Running - %span.badge.js-running-count= @all_builds.running_or_pending.size + %span.badge.js-running-count= @all_builds.running_or_pending.count(:id) %li{class: ('active' if @scope == 'finished')} = link_to project_builds_path(@project, scope: :finished) do Finished - %span.badge.js-running-count= @all_builds.finished.size + %span.badge.js-running-count= @all_builds.finished.count(:id) %li{class: ('active' if @scope == 'all')} = link_to project_builds_path(@project, scope: :all) do All - %span.badge.js-totalbuilds-count= @all_builds.size + %span.badge.js-totalbuilds-count= @all_builds.count(:id) .gray-content-block List of #{@scope || 'running'} builds from this project -- cgit v1.2.1 From 71d0cfcff62e1e86401a10233d78c032a793a008 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Oct 2015 17:13:36 +0200 Subject: Make the builds view and warning notice nicer --- app/helpers/runners_helper.rb | 2 +- app/views/projects/builds/_build.html.haml | 3 +++ app/views/projects/builds/show.html.haml | 8 +++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb index bf551778cb3..46eb82a354f 100644 --- a/app/helpers/runners_helper.rb +++ b/app/helpers/runners_helper.rb @@ -15,7 +15,7 @@ module RunnersHelper end def runner_link(runner) - display_name = truncate(runner.display_name, length: 20) + display_name = truncate(runner.display_name, length: 15) id = "\##{runner.id}" if current_user && current_user.admin diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml index ff146f7389b..4ce4ed63b40 100644 --- a/app/views/projects/builds/_build.html.haml +++ b/app/views/projects/builds/_build.html.haml @@ -9,6 +9,9 @@ - else %strong Build ##{build.id} + - if build.show_warning? + %i.fa.fa-warning.text-warning + %td = link_to build.short_sha, namespace_project_commit_path(@project.namespace, @project, build.sha) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 91c1b16c9f6..138dcddb8ed 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -44,16 +44,14 @@ .bs-callout.bs-callout-warning %p - if no_runners_for_project?(@build.project) - This build is stuck, because the project doesn't have runners assigned. + This build is stuck because the project doesn't have any runners online assigned to it. - elsif @build.tags.any? - This build is stuck. - %br - This build is stuck, because you don't have any active runners online with these tags assigned to the project: + This build 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 online that can run this build. + This build is stuck, because you don't have any active runners that can run this build. %br Go to -- cgit v1.2.1 From c82755f539fdcea08bb668d2b5abb4304787c96e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 15 Oct 2015 18:01:24 +0200 Subject: Update redcarpet gem. Fixes gem memory leak Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 4fbd37f6c8b..9254ce2ccfa 100644 --- a/Gemfile +++ b/Gemfile @@ -94,7 +94,7 @@ gem "seed-fu", '~> 2.3.5' gem 'html-pipeline', '~> 1.11.0' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'github-markup', '~> 1.3.1' -gem 'redcarpet', '~> 3.3.2' +gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.2.9' gem 'rdoc', '~>3.6' gem 'org-ruby', '~> 0.9.12' diff --git a/Gemfile.lock b/Gemfile.lock index b92d9766d3a..53122898b07 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -539,7 +539,7 @@ GEM trollop rdoc (3.12.2) json (~> 1.4) - redcarpet (3.3.2) + redcarpet (3.3.3) redis (3.2.1) redis-actionpack (4.0.0) actionpack (~> 4) @@ -881,7 +881,7 @@ DEPENDENCIES rails (= 4.1.12) raphael-rails (~> 2.1.2) rdoc (~> 3.6) - redcarpet (~> 3.3.2) + redcarpet (~> 3.3.3) redis-rails (~> 4.0.0) request_store (~> 1.2.0) rerun (~> 0.10.0) -- cgit v1.2.1 From 567460d16dd721e52b164828e854cb8ac87fdf43 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Oct 2015 18:11:46 +0200 Subject: Added missing comma [ci skip] --- app/views/projects/builds/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 138dcddb8ed..c45bfb27b8f 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -44,7 +44,7 @@ .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 build 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: - @build.tags.each do |tag| -- cgit v1.2.1 From 9fd48229860636fecc07d3dde7cb4fe7624ce8f9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 15 Oct 2015 19:02:29 +0200 Subject: Show last commit from default branch on project home page Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/blocks.scss | 5 +++++ app/assets/stylesheets/pages/projects.scss | 29 ++++++++++++++++++++++++++++ app/views/projects/_last_commit.html.haml | 12 ++++++++++++ app/views/projects/show.html.haml | 4 ++++ 4 files changed, 50 insertions(+) create mode 100644 app/views/projects/_last_commit.html.haml diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 6ce34b5c3e8..32d219d4d60 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -18,6 +18,7 @@ line-height: 36px; } +.content-block, .gray-content-block { margin: -$gl-padding; background-color: $background-color; @@ -27,6 +28,10 @@ border-bottom: 1px solid $border-color; color: $gl-gray; + &.white { + background-color: white; + } + &.top-block { border-top: none; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f7a22849003..b6d53acd111 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -511,3 +511,32 @@ pre.light-well { margin-top: -1px; } } + +.project-last-commit { + .ci-status { + margin-right: 16px; + } + + .commit-row-message { + color: $gl-gray; + } + + .commit_short_id { + margin-left: 5px; + color: $gl-link-color; + font-weight: 600; + } + + .commit-author-link { + margin-left: 7px; + text-decoration: none; + .avatar { + float: none; + margin-right: 4px; + } + + .commit-author-name { + font-weight: 600; + } + } +} diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml new file mode 100644 index 00000000000..0c16c3ccbf7 --- /dev/null +++ b/app/views/projects/_last_commit.html.haml @@ -0,0 +1,12 @@ +.project-last-commit + - ci_commit = project.ci_commit(commit.sha) + - if ci_commit + = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do + = ci_status_icon(ci_commit) + = ci_commit.status + + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" + · + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by + = commit_author_link(commit, avatar: true, size: 24) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index e95d987d74c..e20b1fc49c0 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -64,6 +64,10 @@ = icon("exclamation-triangle fw") Archived project! Repository is read-only +- if @repository.commit + .content-block.second-block.white + = render 'projects/last_commit', commit: @repository.commit, project: @project + %section - if prefer_readme? .project-show-readme -- cgit v1.2.1 From 6348f654cdd76b062c18ae1bce3eecf0b6dd0c6c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 15 Oct 2015 15:59:06 -0400 Subject: Update Installation doc for 8-1-stable --- doc/install/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 3c62b11988e..b154d280471 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -211,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-0-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable gitlab -**Note:** You can change `8-0-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It -- cgit v1.2.1 From facf8826daae0c5bad9854bb903101dadc04acb8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 15 Oct 2015 16:06:34 -0400 Subject: Add 8.0-to-8.1 update guide --- doc/update/8.0-to-8.1.md | 228 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 doc/update/8.0-to-8.1.md diff --git a/doc/update/8.0-to-8.1.md b/doc/update/8.0-to-8.1.md new file mode 100644 index 00000000000..34d7c25a461 --- /dev/null +++ b/doc/update/8.0-to-8.1.md @@ -0,0 +1,228 @@ +# From 8.0 to 8.1 + +### 0. Double-check your Git version + +**This notice applies only to /usr/local/bin/git** + +If you compiled Git from source on your GitLab server then please double-check +that you are using a version that protects against CVE-2014-9390. For six +months after this vulnerability became known the GitLab installation guide +still contained instructions that would install the outdated, 'vulnerable' Git +version 2.1.2. + +Run the following command to get your current Git version: + +```sh +/usr/local/bin/git --version +``` + +If you see 'No such file or directory' then you did not install Git according +to the outdated instructions from the GitLab installation guide and you can go +to the next step 'Stop server' below. + +If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4, +v2.2.1 or newer. You can use the [instructions in the GitLab source +installation +guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies) +to install a newer version of Git. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-1-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-1-stable-ee +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.6.5 +``` + +### 5. Install gitlab-git-http-server + +First we download Go 1.5 and install it into `/usr/local/go`: + +```bash +curl -O --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz +echo '5817fa4b2252afdb02e11e8b9dc1d9173ef3bd5a go1.5.linux-amd64.tar.gz' | shasum -c - && \ + sudo tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.5.linux-amd64.tar.gz +``` + +Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git-http-server`: + +```bash +cd /home/git +sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git +cd gitlab-git-http-server +sudo -u git -H make +``` + +Make sure your unicorn.rb file contains a 'listen' line for +'127.0.0.1:8080' and that this line is not commented out. + +``` +cd /home/git/gitlab +grep ^listen config/unicorn.rb + +# If there is no 'listen' line for 127.0.0.1:8080, add it: +sudo -u git tee -a config/unicorn.rb < true +EOF +``` + +If your Git repositories are in a directory other than `/home/git/repositories`, +you need to tell `gitlab-git-http-server` about it via `/etc/default/gitlab`. +See `lib/support/init.d/gitlab.default.example` for the options. + +### 6. Copy secrets + +The `secrets.yml` file is used to store keys to encrypt sessions and encrypt secure variables. +When you run migrations make sure to store it someplace safe. +Don't store it in the same place as your database backups, +otherwise your secrets are exposed if one of your backups is compromised. + +``` +cd /home/git/gitlab +sudo -u git -H cp config/secrets.yml.example config/secrets.yml +sudo -u git -H chmod 0600 config/secrets.yml +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 8. Update config files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example +``` + +The new options include configuration of GitLab CI that are now being part of GitLab CE and EE. + +#### New Nginx configuration + +Because of the new `gitlab-git-http-server` you need to update your Nginx +configuration. If you skip this step 'git clone' and 'git push' over HTTP(S) +will stop working. + +View changes between the previous recommended Nginx configuration and the +current one: + +```sh +# For HTTPS configurations +git diff origin/8-0-stable:lib/support/nginx/gitlab-ssl origin/8-1-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-0-stable:lib/support/nginx/gitlab origin/8-1-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache). +Also note that because Apache does not support upstreams behind Unix sockets you will need to let gitlab-git-http-server listen on a TCP port. You can do this via [/etc/default/gitlab](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-0-stable/lib/support/init.d/gitlab.default.example#L34). + +### 9. Migrate GitLab CI to GitLab CE/EE + +Now, GitLab CE and EE has CI integrated. However, migrations don't happen automatically and you need to do it manually. +Please follow the following guide [to migrate](../migrate_ci_to_ce/README.md) your GitLab CI instance to GitLab CE/EE. + +### 10. Use Redis v2.4.0+ + +Previous versions of GitLab allowed Redis versions >= 2.0 to be used, but +Sidekiq jobs could fail due to lack of support for the SREM command. GitLab +8.0 now checks that Redis >= 2.4.0 is used. You can check your Redis version +with the following command: + + redis-cli info | grep redis_version + +### 11. Start application + + sudo service gitlab start + sudo service nginx restart + +### 12. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.0) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 7.14 to 8.0](7.14-to-8.0.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. + +## Troubleshooting + +### "You appear to have cloned an empty repository." + +If you see this message when attempting to clone a repository hosted by GitLab, +this is likely due to an outdated Nginx or Apache configuration, or a missing or +misconfigured `gitlab-git-http-server` instance. Double-check that you correctly +completed [Step 5](#5-install-gitlab-git-http-server) to install the daemon and +[Step 8](#new-nginx-configuration) to reconfigure Nginx. -- cgit v1.2.1 From 0aa6061d6ab0ab921ad585329b43b84d20da873e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Oct 2015 15:08:31 +0200 Subject: Implement when syntax in .gitlab-ci.yml --- CHANGELOG | 1 + app/models/ci/build.rb | 5 +- app/models/ci/commit.rb | 52 ++----- app/models/commit_status.rb | 2 +- app/services/ci/create_builds_service.rb | 14 +- doc/ci/yaml/README.md | 49 ++++++ lib/ci/gitlab_ci_yaml_processor.rb | 7 +- lib/ci/status.rb | 21 +++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 28 +++- spec/models/ci/commit_spec.rb | 224 +++++++++++++++++---------- 10 files changed, 279 insertions(+), 124 deletions(-) create mode 100644 lib/ci/status.rb diff --git a/CHANGELOG b/CHANGELOG index aba823948a7..39926692147 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.1.0 (unreleased) - Add first and last to pagination (Zeger-Jan van de Weg) - Added Commit Status API - Added Builds View + - Added when to .gitlab-ci.yml - Show CI status on commit page - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds - Show CI status on Your projects page and Starred projects page diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5f8d44148ca..b19e2ac1363 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -93,10 +93,7 @@ module Ci Ci::WebHookService.new.build_end(build) end - if build.commit.should_create_next_builds?(build) - build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request) - end - + build.commit.create_next_builds(build) project.execute_services(build) if project.coverage_enabled? diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index cd45366b34e..13437b2483f 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -91,19 +91,28 @@ module Ci def create_builds(ref, tag, user, trigger_request = nil) return unless config_processor config_processor.stages.any? do |stage| - CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? + CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present? end end - def create_next_builds(ref, tag, user, trigger_request) + def create_next_builds(build) return unless config_processor - stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage) + # don't create other builds if this one is retried + latest_builds = builds.similar(build).latest + return unless latest_builds.exists?(build.id) - config_processor.stages.any? do |stage| - unless stages.include?(stage) - CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? - end + # get list of stages after this build + next_stages = config_processor.stages.drop_while { |stage| stage != build.stage } + next_stages.delete(build.stage) + + # get status for all prior builds + prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } + status = Ci::Status.get_status(prior_builds) + + # create builds for next stages based + next_stages.any? do |stage| + CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present? end end @@ -132,24 +141,7 @@ module Ci return 'failed' end - @status ||= begin - latest = latest_statuses - latest.reject! { |status| status.try(&:allow_failure?) } - - if latest.none? - 'skipped' - elsif latest.all?(&:success?) - 'success' - elsif latest.all?(&:pending?) - 'pending' - elsif latest.any?(&:running?) || latest.any?(&:pending?) - 'running' - elsif latest.all?(&:canceled?) - 'canceled' - else - 'failed' - end - end + @status ||= Ci::Status.get_status(latest_statuses) end def pending? @@ -219,16 +211,6 @@ module Ci update!(committed_at: DateTime.now) end - def should_create_next_builds?(build) - # don't create other builds if this one is retried - other_builds = builds.similar(build).latest - return false unless other_builds.include?(build) - - other_builds.all? do |build| - build.success? || build.ignored? - end - end - private def save_yaml_error(error) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 0b71838d515..8188ba3a28e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -28,7 +28,7 @@ class CommitStatus < ActiveRecord::Base end event :drop do - transition running: :failed + transition [:pending, :running] => :failed end event :success do diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index c420f3268fd..912eb6258a4 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -1,8 +1,20 @@ module Ci class CreateBuildsService - def execute(commit, stage, ref, tag, user, trigger_request) + def execute(commit, stage, ref, tag, user, trigger_request, status) builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag) + # check when to create next build + builds_attrs = builds_attrs.select do |build_attrs| + case build_attrs[:when] + when 'on_success' + status == 'success' + when 'on_failure' + status == 'failed' + when 'always' + %w(success failed).include?(status) + end + end + builds_attrs.map do |build_attrs| # don't create the same build twice unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 4caeccacb7f..8507389f1ce 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -140,6 +140,7 @@ job_name: | except | optional | Defines a list of git refs for which build is not created | | tags | optional | Defines a list of tags which are used to select runner | | allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status | +| when | optional | Define when to run build. Can be on_success, on_failure or always | ### script `script` is a shell script which is executed by runner. The shell script is prepended with `before_script`. @@ -196,6 +197,54 @@ job: The above specification will make sure that `job` is built by a runner that have `ruby` AND `postgres` tags defined. +### when +`when` is used to implement jobs that are run in case of failure or despite the failure. + +The `when` can be set to one of the following values: +1. `on_success` - execute build only when all builds from prior stages succeeded. This is default. +1. `on_failure` - execute build only when at least one of the build from prior stages failed. +1. `always` - execute build despite the status of builds from prior stages. + +``` +stages: +- build +- cleanup_build +- test +- deploy +- cleanup + +build: + stage: build + script: + - make build + +cleanup_build: + stage: cleanup_build + script: + - cleanup build when failed + when: on_failure + +test: + stage: test + script: + - make test + +deploy: + stage: deploy + script: + - make deploy + +cleanup: + stage: cleanup + script: + - cleanup after builds + when: always +``` + +The above script will: +1. Execute `cleanup_build` only when the `build` failed, +2. Always execute `cleanup` as the last step in pipeline. + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. You can find the link to the Lint in the project's settings page or use short url `/lint`. diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index c47951bc5d1..58be188387f 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -5,7 +5,7 @@ module Ci DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables] - ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage] + ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when] attr_reader :before_script, :image, :services, :variables @@ -93,6 +93,7 @@ module Ci only: job[:only], except: job[:except], allow_failure: job[:allow_failure] || false, + when: job[:when] || 'on_success', options: { image: job[:image] || @image, services: job[:services] || @services @@ -184,6 +185,10 @@ module Ci if job[:allow_failure] && !job[:allow_failure].in?([true, false]) raise ValidationError, "#{name}: allow_failure parameter should be an boolean" end + + if job[:when] && !job[:when].in?(%w(on_success on_failure always)) + raise ValidationError, "#{name}: when should be on_success, on_failure or always" + end end private diff --git a/lib/ci/status.rb b/lib/ci/status.rb new file mode 100644 index 00000000000..94c94261d83 --- /dev/null +++ b/lib/ci/status.rb @@ -0,0 +1,21 @@ +module Ci + class Status + def self.get_status(statuses) + statuses.reject! { |status| status.try(&:allow_failure?) } + + if statuses.none? + 'skipped' + elsif statuses.all?(&:success?) + 'success' + elsif statuses.all?(&:pending?) + 'pending' + elsif statuses.any?(&:running?) || statuses.any?(&:pending?) + 'running' + elsif statuses.all?(&:canceled?) + 'canceled' + else + 'failed' + end + end + end +end \ No newline at end of file diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index aba957da488..65696cb1ed3 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -125,7 +125,8 @@ module Ci image: "ruby:2.1", services: ["mysql"] }, - allow_failure: false + allow_failure: false, + when: "on_success" }) end @@ -152,7 +153,8 @@ module Ci image: "ruby:2.5", services: ["postgresql"] }, - allow_failure: false + allow_failure: false, + when: "on_success" }) end end @@ -174,6 +176,21 @@ module Ci end end + describe "When" do + %w(on_success on_failure always).each do |when_state| + it "returns #{when_state} when defined" do + config = YAML.dump({ + rspec: { script: "rspec", when: when_state } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + builds = config_processor.builds_for_stage_and_ref("test", "master") + expect(builds.size).to eq(1) + expect(builds.first[:when]).to eq(when_state) + end + end + end + describe "Error handling" do it "indicates that object is invalid" do expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError) @@ -311,6 +328,13 @@ module Ci GitlabCiYamlProcessor.new(config) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end + + it "returns errors if job when is not on_success, on_failure or always" do + config = YAML.dump({ rspec: { script: "test", when: false } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") + end end end end diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index d1cecce5a6d..9ad30407769 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -161,28 +161,28 @@ describe Ci::Commit do end describe :create_builds do - let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } + let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } def create_builds(trigger_request = nil) commit.create_builds('master', false, nil, trigger_request) end - def create_next_builds(trigger_request = nil) - commit.create_next_builds('master', false, nil, trigger_request) + def create_next_builds + commit.create_next_builds(commit.builds.order(:id).last) end it 'creates builds' do expect(create_builds).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(2) + commit.builds.update_all(status: "success") + expect(commit.builds.count(:all)).to eq(2) expect(create_next_builds).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(4) + commit.builds.update_all(status: "success") + expect(commit.builds.count(:all)).to eq(4) expect(create_next_builds).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(5) + commit.builds.update_all(status: "success") + expect(commit.builds.count(:all)).to eq(5) expect(create_next_builds).to be_falsey end @@ -194,12 +194,12 @@ describe Ci::Commit do it 'creates builds' do expect(create_builds).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(2) + commit.builds.update_all(status: "success") + expect(commit.builds.count(:all)).to eq(2) expect(create_develop_builds).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(4) + commit.builds.update_all(status: "success") + expect(commit.builds.count(:all)).to eq(4) expect(commit.refs.size).to eq(2) expect(commit.builds.pluck(:name).uniq.size).to eq(2) end @@ -211,28 +211,24 @@ describe Ci::Commit do it 'creates builds' do expect(create_builds(trigger_request)).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(2) + expect(commit.builds.count(:all)).to eq(2) end it 'rebuilds commit' do expect(create_builds).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(2) + expect(commit.builds.count(:all)).to eq(2) expect(create_builds(trigger_request)).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(4) + expect(commit.builds.count(:all)).to eq(4) end it 'creates next builds' do expect(create_builds(trigger_request)).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(2) + expect(commit.builds.count(:all)).to eq(2) + commit.builds.update_all(status: "success") - expect(create_next_builds(trigger_request)).to be_truthy - commit.builds.reload - expect(commit.builds.size).to eq(4) + expect(create_next_builds).to be_truthy + expect(commit.builds.count(:all)).to eq(4) end context 'for [ci skip]' do @@ -242,7 +238,7 @@ describe Ci::Commit do it 'rebuilds commit' do expect(commit.status).to eq('skipped') - expect(create_builds(trigger_request)).to be_truthy + expect(create_builds).to be_truthy # since everything in Ci::Commit is cached we need to fetch a new object new_commit = Ci::Commit.find_by_id(commit.id) @@ -250,6 +246,129 @@ describe Ci::Commit do end end end + + context 'properly creates builds "when" is defined' do + let(:yaml) { + { + stages: ["build", "test", "test_failure", "deploy", "cleanup"], + build: { + stage: "build", + script: "BUILD", + }, + test: { + stage: "test", + script: "TEST", + }, + test_failure: { + stage: "test_failure", + script: "ON test failure", + when: "on_failure", + }, + deploy: { + stage: "deploy", + script: "PUBLISH", + }, + cleanup: { + stage: "cleanup", + script: "TIDY UP", + when: "always", + } + } + } + + before do + stub_ci_commit_yaml_file(YAML.dump(yaml)) + end + + it 'properly creates builds' do + expect(create_builds).to be_truthy + expect(commit.builds.pluck(:name)).to contain_exactly('build') + expect(commit.builds.pluck(:status)).to contain_exactly('pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending') + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success') + expect(commit.status).to eq('success') + end + + it 'properly creates builds when test fails' do + expect(create_builds).to be_truthy + expect(commit.builds.pluck(:name)).to contain_exactly('build') + expect(commit.builds.pluck(:status)).to contain_exactly('pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending') + commit.builds.running_or_pending.each(&:drop) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success') + expect(commit.status).to eq('failed') + end + + it 'properly creates builds when test and test_failure fails' do + expect(create_builds).to be_truthy + expect(commit.builds.pluck(:name)).to contain_exactly('build') + expect(commit.builds.pluck(:status)).to contain_exactly('pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending') + commit.builds.running_or_pending.each(&:drop) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending') + commit.builds.running_or_pending.each(&:drop) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success') + expect(commit.status).to eq('failed') + end + + it 'properly creates builds when deploy fails' do + expect(create_builds).to be_truthy + expect(commit.builds.pluck(:name)).to contain_exactly('build') + expect(commit.builds.pluck(:status)).to contain_exactly('pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending') + commit.builds.running_or_pending.each(&:drop) + + expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup') + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending') + commit.builds.running_or_pending.each(&:success) + + expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success') + expect(commit.status).to eq('failed') + end + end end describe "#finished_at" do @@ -299,59 +418,4 @@ describe Ci::Commit do expect(commit.coverage).to be_nil end end - - describe :should_create_next_builds? do - before do - @build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: 'success' - @build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: 'failed' - @build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: 'failed' - @build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'success' - end - - context 'for success' do - it 'to create if all succeeded' do - expect(commit.should_create_next_builds?(@build4)).to be_truthy - end - end - - context 'for failed' do - before do - @build4.update_attributes(status: 'failed') - end - - it 'to not create' do - expect(commit.should_create_next_builds?(@build4)).to be_falsey - end - - context 'and ignore failures for current' do - before do - @build4.update_attributes(allow_failure: true) - end - - it 'to create' do - expect(commit.should_create_next_builds?(@build4)).to be_truthy - end - end - end - - context 'for running' do - before do - @build4.update_attributes(status: 'running') - end - - it 'to not create' do - expect(commit.should_create_next_builds?(@build4)).to be_falsey - end - end - - context 'for retried' do - before do - @build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'failed' - end - - it 'to not create' do - expect(commit.should_create_next_builds?(@build4)).to be_falsey - end - end - end end -- cgit v1.2.1 From 9419046196dc1a09716d5b2bbc424b4f89d696ae Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Oct 2015 23:49:27 +0200 Subject: Fix specs --- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 5 +++-- spec/models/ci/commit_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 58be188387f..0da73e387e1 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -187,7 +187,7 @@ module Ci end if job[:when] && !job[:when].in?(%w(on_success on_failure always)) - raise ValidationError, "#{name}: when should be on_success, on_failure or always" + raise ValidationError, "#{name}: when parameter should be on_success, on_failure or always" 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 65696cb1ed3..2260a6f8130 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -24,7 +24,8 @@ module Ci commands: "pwd\nrspec", tag_list: [], options: {}, - allow_failure: false + allow_failure: false, + when: "on_success" }) end @@ -330,7 +331,7 @@ module Ci end it "returns errors if job when is not on_success, on_failure or always" do - config = YAML.dump({ rspec: { script: "test", when: false } }) + config = YAML.dump({ rspec: { script: "test", when: 1 } }) expect do GitlabCiYamlProcessor.new(config) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 9ad30407769..94fc21b4ea9 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -248,7 +248,7 @@ describe Ci::Commit do end context 'properly creates builds "when" is defined' do - let(:yaml) { + let(:yaml) do { stages: ["build", "test", "test_failure", "deploy", "cleanup"], build: { @@ -274,7 +274,7 @@ describe Ci::Commit do when: "always", } } - } + end before do stub_ci_commit_yaml_file(YAML.dump(yaml)) -- cgit v1.2.1 From 374ea21f32f2977533be2c9b124867bf7c72407c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Oct 2015 23:56:42 +0200 Subject: Backticks around when types --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 8507389f1ce..d37e616bbac 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -140,7 +140,7 @@ job_name: | except | optional | Defines a list of git refs for which build is not created | | tags | optional | Defines a list of tags which are used to select runner | | allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status | -| when | optional | Define when to run build. Can be on_success, on_failure or always | +| when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` | ### script `script` is a shell script which is executed by runner. The shell script is prepended with `before_script`. -- cgit v1.2.1 From 6232bb1ef39c310f5cd7fb6d11d75b9d51e55e58 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 15 Oct 2015 18:18:41 -0400 Subject: Remove notes about CI migration from 8.1 update guide [ci skip] --- doc/update/8.0-to-8.1.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/doc/update/8.0-to-8.1.md b/doc/update/8.0-to-8.1.md index 34d7c25a461..43c06609f4d 100644 --- a/doc/update/8.0-to-8.1.md +++ b/doc/update/8.0-to-8.1.md @@ -148,8 +148,6 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example ``` -The new options include configuration of GitLab CI that are now being part of GitLab CE and EE. - #### New Nginx configuration Because of the new `gitlab-git-http-server` you need to update your Nginx @@ -170,12 +168,7 @@ git diff origin/8-0-stable:lib/support/nginx/gitlab origin/8-1-stable:lib/suppor If you are using Apache instead of NGINX please see the updated [Apache templates](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache). Also note that because Apache does not support upstreams behind Unix sockets you will need to let gitlab-git-http-server listen on a TCP port. You can do this via [/etc/default/gitlab](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-0-stable/lib/support/init.d/gitlab.default.example#L34). -### 9. Migrate GitLab CI to GitLab CE/EE - -Now, GitLab CE and EE has CI integrated. However, migrations don't happen automatically and you need to do it manually. -Please follow the following guide [to migrate](../migrate_ci_to_ce/README.md) your GitLab CI instance to GitLab CE/EE. - -### 10. Use Redis v2.4.0+ +### 9. Use Redis v2.4.0+ Previous versions of GitLab allowed Redis versions >= 2.0 to be used, but Sidekiq jobs could fail due to lack of support for the SREM command. GitLab @@ -184,12 +177,12 @@ with the following command: redis-cli info | grep redis_version -### 11. Start application +### 10. Start application sudo service gitlab start sudo service nginx restart -### 12. Check application status +### 11. Check application status Check if GitLab and its environment are configured correctly: -- cgit v1.2.1 From c2c9f6d52d392be8bb6bce5366cdcbcfdf38c9e5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 15 Oct 2015 18:49:37 -0400 Subject: Remove 8.0-only steps from the 8.1 update guide Also adds a preamble note that a working 8.0 installation (i.e., gitlab-git-http-server, updated Nginx/Apache configs) is required before proceeding. [ci skip] --- doc/update/8.0-to-8.1.md | 98 +++++------------------------------------------- 1 file changed, 9 insertions(+), 89 deletions(-) diff --git a/doc/update/8.0-to-8.1.md b/doc/update/8.0-to-8.1.md index 43c06609f4d..4dacc97f7f7 100644 --- a/doc/update/8.0-to-8.1.md +++ b/doc/update/8.0-to-8.1.md @@ -1,5 +1,9 @@ # From 8.0 to 8.1 +**NOTE:** GitLab 8.0 introduced several significant changes related to +installation and configuration which *are not duplicated here*. Be sure you're +already running a working version of 8.0 before proceeding with this guide. + ### 0. Double-check your Git version **This notice applies only to /usr/local/bin/git** @@ -66,58 +70,7 @@ sudo -u git -H git fetch sudo -u git -H git checkout v2.6.5 ``` -### 5. Install gitlab-git-http-server - -First we download Go 1.5 and install it into `/usr/local/go`: - -```bash -curl -O --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz -echo '5817fa4b2252afdb02e11e8b9dc1d9173ef3bd5a go1.5.linux-amd64.tar.gz' | shasum -c - && \ - sudo tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz -sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ -rm go1.5.linux-amd64.tar.gz -``` - -Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git-http-server`: - -```bash -cd /home/git -sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git -cd gitlab-git-http-server -sudo -u git -H make -``` - -Make sure your unicorn.rb file contains a 'listen' line for -'127.0.0.1:8080' and that this line is not commented out. - -``` -cd /home/git/gitlab -grep ^listen config/unicorn.rb - -# If there is no 'listen' line for 127.0.0.1:8080, add it: -sudo -u git tee -a config/unicorn.rb < true -EOF -``` - -If your Git repositories are in a directory other than `/home/git/repositories`, -you need to tell `gitlab-git-http-server` about it via `/etc/default/gitlab`. -See `lib/support/init.d/gitlab.default.example` for the options. - -### 6. Copy secrets - -The `secrets.yml` file is used to store keys to encrypt sessions and encrypt secure variables. -When you run migrations make sure to store it someplace safe. -Don't store it in the same place as your database backups, -otherwise your secrets are exposed if one of your backups is compromised. - -``` -cd /home/git/gitlab -sudo -u git -H cp config/secrets.yml.example config/secrets.yml -sudo -u git -H chmod 0600 config/secrets.yml -``` - -### 7. Install libs, migrations, etc. +### 5. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -138,7 +91,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` -### 8. Update config files +### 6. Update configuration files #### New configuration options for `gitlab.yml` @@ -148,41 +101,12 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example ``` -#### New Nginx configuration - -Because of the new `gitlab-git-http-server` you need to update your Nginx -configuration. If you skip this step 'git clone' and 'git push' over HTTP(S) -will stop working. - -View changes between the previous recommended Nginx configuration and the -current one: - -```sh -# For HTTPS configurations -git diff origin/8-0-stable:lib/support/nginx/gitlab-ssl origin/8-1-stable:lib/support/nginx/gitlab-ssl - -# For HTTP configurations -git diff origin/8-0-stable:lib/support/nginx/gitlab origin/8-1-stable:lib/support/nginx/gitlab -``` - -If you are using Apache instead of NGINX please see the updated [Apache templates](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache). -Also note that because Apache does not support upstreams behind Unix sockets you will need to let gitlab-git-http-server listen on a TCP port. You can do this via [/etc/default/gitlab](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-0-stable/lib/support/init.d/gitlab.default.example#L34). - -### 9. Use Redis v2.4.0+ - -Previous versions of GitLab allowed Redis versions >= 2.0 to be used, but -Sidekiq jobs could fail due to lack of support for the SREM command. GitLab -8.0 now checks that Redis >= 2.4.0 is used. You can check your Redis version -with the following command: - - redis-cli info | grep redis_version - -### 10. Start application +### 7. Start application sudo service gitlab start sudo service nginx restart -### 11. Check application status +### 8. Check application status Check if GitLab and its environment are configured correctly: @@ -214,8 +138,4 @@ If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of ### "You appear to have cloned an empty repository." -If you see this message when attempting to clone a repository hosted by GitLab, -this is likely due to an outdated Nginx or Apache configuration, or a missing or -misconfigured `gitlab-git-http-server` instance. Double-check that you correctly -completed [Step 5](#5-install-gitlab-git-http-server) to install the daemon and -[Step 8](#new-nginx-configuration) to reconfigure Nginx. +See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting). -- cgit v1.2.1 From 62377d17548ca41b4c563ec1c7331df97f1054ca Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 15 Oct 2015 19:44:15 -0400 Subject: Shut up, Rubocop --- lib/ci/status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ci/status.rb b/lib/ci/status.rb index 94c94261d83..c02b3b8f3e4 100644 --- a/lib/ci/status.rb +++ b/lib/ci/status.rb @@ -18,4 +18,4 @@ module Ci end end end -end \ No newline at end of file +end -- cgit v1.2.1 From 6fe2a679a799c0914b8c32e011343939800c5480 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 15 Oct 2015 19:46:01 -0400 Subject: Update CI YAML docs --- doc/ci/yaml/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index d37e616bbac..ea8f72bc135 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -200,9 +200,10 @@ The above specification will make sure that `job` is built by a runner that have ### when `when` is used to implement jobs that are run in case of failure or despite the failure. -The `when` can be set to one of the following values: -1. `on_success` - execute build only when all builds from prior stages succeeded. This is default. -1. `on_failure` - execute build only when at least one of the build from prior stages failed. +`when` can be set to one of the following values: + +1. `on_success` - execute build only when all builds from prior stages succeeded. This is the default. +1. `on_failure` - execute build only when at least one build from prior stages failed. 1. `always` - execute build despite the status of builds from prior stages. ``` @@ -250,4 +251,4 @@ Each instance of GitLab CI has an embedded debug tool called Lint. You can find the link to the Lint in the project's settings page or use short url `/lint`. ## Skipping builds -There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped \ No newline at end of file +There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped -- cgit v1.2.1 From 64352d25b33274a2f298347bae5f53176085b80d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 15 Oct 2015 21:30:47 -0400 Subject: Correct spec description typo [ci skip] --- spec/models/ci/commit_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 94fc21b4ea9..44dbd083f06 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -247,7 +247,7 @@ describe Ci::Commit do end end - context 'properly creates builds "when" is defined' do + context 'properly creates builds when "when" is defined' do let(:yaml) do { stages: ["build", "test", "test_failure", "deploy", "cleanup"], -- cgit v1.2.1 From a317c5296e9c142cfd04168eced7d7bfd9458ffa Mon Sep 17 00:00:00 2001 From: Ashley Hindle Date: Fri, 16 Oct 2015 07:36:20 +0100 Subject: Changed loose to lose --- doc/raketasks/backup_restore.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index db3f6bb40bd..06f582dcee8 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -16,7 +16,7 @@ You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json` from source). This file contains the database encryption key used for two-factor authentication. If you restore a GitLab backup without restoring the database encryption key, users who have two-factor -authentication enabled will loose access to your GitLab server. +authentication enabled will lose access to your GitLab server. If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)* -- cgit v1.2.1 From 2611d9f63cb6c22a00eccdac75535f93890b176c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 15 Oct 2015 01:41:46 -0700 Subject: Add a system note and update relevant merge requests when a branch is deleted or re-added If a branch is deleted with an open merge request, amended offline, and then pushed again, GitLab doesn't bother to update the merge request even though the last commit ID and/or code may have changed. This MR ensures that each push will update any relevant merge requests and adds a system note if this happens as well. Closes #2926 --- CHANGELOG | 1 + app/models/merge_request_diff.rb | 3 +- app/services/git_push_service.rb | 5 +- app/services/merge_requests/refresh_service.rb | 55 +++++++++++++--------- app/services/system_note_service.rb | 19 ++++++++ spec/services/git_push_service_spec.rb | 8 ++++ .../merge_requests/refresh_service_spec.rb | 19 ++++++++ spec/services/system_note_service_spec.rb | 12 +++++ 8 files changed, 98 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 39926692147..814a6772cfd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.1.0 (unreleased) - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) - Speed up load times of issue detail pages by roughly 1.5x + - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu) - Make diff file view easier to use on mobile screens (Stan Hu) - Improved performance of finding users by username or Email address - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index c9ef8023aea..bc2d691ece0 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -163,7 +163,8 @@ class MergeRequestDiff < ActiveRecord::Base merge_request.fetch_ref # Get latest sha of branch from source project - source_sha = merge_request.source_project.commit(source_branch).sha + source_commit = merge_request.source_project.commit(source_branch) + source_sha = source_commit.try(:sha) Gitlab::CompareResult.new( Gitlab::Git::Compare.new( diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 81d47602f13..e54044365b9 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -49,10 +49,13 @@ class GitPushService elsif push_to_existing_branch?(ref, oldrev) # Collect data for this git push @push_commits = project.repository.commits_between(oldrev, newrev) - project.update_merge_requests(oldrev, newrev, ref, @user) process_commit_messages(ref) end + # Update merge requests that may be affected by this push. A new branch + # could cause the last commit of a merge request to change. + project.update_merge_requests(oldrev, newrev, ref, @user) + @push_data = build_push_data(oldrev, newrev, ref) # If CI was disabled but .gitlab-ci.yml file was pushed diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index e903e48e3cd..802d02b0790 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -6,12 +6,22 @@ module MergeRequests @oldrev, @newrev = oldrev, newrev @branch_name = Gitlab::Git.ref_name(ref) @fork_merge_requests = @project.fork_merge_requests.opened - @commits = @project.repository.commits_between(oldrev, newrev) + @commits = [] + @merge_requests = merge_requests_for_branch + + # Leave a system note if a branch were deleted/added + if Gitlab::Git.blank_ref?(oldrev) or Gitlab::Git.blank_ref?(newrev) + presence = Gitlab::Git.blank_ref?(oldrev) ? 'added' : 'deleted' + comment_mr_branch_presence_changed(presence) + else + @commits = @project.repository.commits_between(oldrev, newrev) + + close_merge_requests + comment_mr_with_commits + end - close_merge_requests reload_merge_requests execute_mr_web_hooks - comment_mr_with_commits true end @@ -31,7 +41,6 @@ module MergeRequests commit_ids.include?(merge_request.last_commit.id) end - merge_requests.uniq.select(&:source_project).each do |merge_request| MergeRequests::PostMergeService. new(merge_request.target_project, @current_user). @@ -46,11 +55,7 @@ module MergeRequests # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too def reload_merge_requests - merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a - merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a - merge_requests = filter_merge_requests(merge_requests) - - merge_requests.each do |merge_request| + @merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? merge_request.reload_code @@ -70,13 +75,20 @@ module MergeRequests end end - # Add comment about pushing new commits to merge requests - def comment_mr_with_commits - merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a - merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a - merge_requests = filter_merge_requests(merge_requests) + # Add comment about branches being deleted or added to merge requests + def comment_mr_branch_presence_changed(presence) + merge_requests = merge_requests_for_branch merge_requests.each do |merge_request| + SystemNoteService.change_branch_presence( + merge_request, merge_request.project, @current_user, + 'source', @branch_name, presence) + end + end + + # Add comment about pushing new commits to merge requests + def comment_mr_with_commits + @merge_requests.each do |merge_request| mr_commit_ids = Set.new(merge_request.commits.map(&:id)) new_commits, existing_commits = @commits.partition do |commit| @@ -91,14 +103,7 @@ module MergeRequests # Call merge request webhook with update branches def execute_mr_web_hooks - merge_requests = @project.origin_merge_requests.opened - .where(source_branch: @branch_name) - .to_a - merge_requests += @fork_merge_requests.where(source_branch: @branch_name) - .to_a - merge_requests = filter_merge_requests(merge_requests) - - merge_requests.each do |merge_request| + @merge_requests.each do |merge_request| execute_hooks(merge_request, 'update') end end @@ -106,5 +111,11 @@ module MergeRequests def filter_merge_requests(merge_requests) merge_requests.uniq.select(&:source_project) end + + def merge_requests_for_branch + merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a + merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a + filter_merge_requests(merge_requests) + end end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 8253c1f780d..24e06504078 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -168,6 +168,25 @@ class SystemNoteService create_note(noteable: noteable, project: project, author: author, note: body) end + # Called when a branch in Noteable is added or deleted + # + # noteable - Noteable object + # project - Project owning noteable + # author - User performing the change + # branch_type - 'source' or 'target' + # branch - branch name + # presence - 'deleted' or 'created' + # + # Example Note text: + # + # "Target branch `feature` deleted" + # + # Returns the created Note object + def self.change_branch_presence(noteable, project, author, branch_type, branch, presence) + body = "#{branch_type} branch `#{branch}` #{presence}".capitalize + create_note(noteable: noteable, project: project, author: author, note: body) + end + # Called when a Mentionable references a Noteable # # noteable - Noteable object being referenced diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index fd72905c331..17015d29e51 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -112,6 +112,14 @@ describe GitPushService do it { expect(@event.project).to eq(project) } it { expect(@event.action).to eq(Event::PUSHED) } it { expect(@event.data).to eq(service.push_data) } + + context "Updates merge requests" do + it "when pushing a new branch for the first time" do + expect(project).to receive(:update_merge_requests). + with(@blankrev, 'newrev', 'refs/heads/master', user) + service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') + end + end end describe "Web Hooks" do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 9516e7936d8..edb9fd0b43c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -106,6 +106,25 @@ describe MergeRequests::RefreshService do it { expect(@fork_merge_request.notes).to be_empty } end + context 'push new branch that exists in a merge request' do + let(:refresh_service) { service.new(@fork_project, @user) } + before do + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master') + reload_mrs + end + + it 'should execute hooks with update action' do + expect(refresh_service).to have_received(:execute_hooks). + with(@fork_merge_request, 'update') + end + + it { expect(@merge_request.notes).to be_empty } + it { expect(@merge_request).to be_open } + it { expect(@fork_merge_request.notes.last.note).to include('Source branch `master` added') } + it { expect(@fork_merge_request).to be_open } + end + def reload_mrs @merge_request.reload @fork_merge_request.reload diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 2658576640c..108bc5995df 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -242,6 +242,18 @@ describe SystemNoteService do end end + describe '.change_branch_presence' do + subject { described_class.change_branch_presence(noteable, project, author, 'source', 'feature', 'deleted') } + + it_behaves_like 'a system note' + + context 'when source branch deleted' do + it 'sets the note text' do + expect(subject.note).to eq "Source branch `feature` deleted" + end + end + end + describe '.cross_reference' do subject { described_class.cross_reference(noteable, mentioner, author) } -- cgit v1.2.1 From 22775c596f9f8be79ec7599b561aa0ccb40bdc42 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 15 Oct 2015 01:57:45 -0700 Subject: Preserve target branch --- app/services/merge_requests/refresh_service.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 802d02b0790..8139aef2bb9 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -55,7 +55,11 @@ module MergeRequests # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too def reload_merge_requests - @merge_requests.each do |merge_request| + merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a + merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? merge_request.reload_code -- cgit v1.2.1 From effa94bb878f4e9c208640c0f067b20cc004db2c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 15 Oct 2015 02:08:22 -0700 Subject: Improve SystemNote interface for branch add/restore case --- app/services/merge_requests/refresh_service.rb | 16 +++++++--------- app/services/system_note_service.rb | 12 +++++++++--- spec/services/merge_requests/refresh_service_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 4 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 8139aef2bb9..fd15889343e 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -7,11 +7,11 @@ module MergeRequests @branch_name = Gitlab::Git.ref_name(ref) @fork_merge_requests = @project.fork_merge_requests.opened @commits = [] - @merge_requests = merge_requests_for_branch + @source_merge_requests = merge_requests_for_source_branch # Leave a system note if a branch were deleted/added if Gitlab::Git.blank_ref?(oldrev) or Gitlab::Git.blank_ref?(newrev) - presence = Gitlab::Git.blank_ref?(oldrev) ? 'added' : 'deleted' + presence = Gitlab::Git.blank_ref?(oldrev) ? :add : :delete comment_mr_branch_presence_changed(presence) else @commits = @project.repository.commits_between(oldrev, newrev) @@ -81,18 +81,16 @@ module MergeRequests # Add comment about branches being deleted or added to merge requests def comment_mr_branch_presence_changed(presence) - merge_requests = merge_requests_for_branch - - merge_requests.each do |merge_request| + @source_merge_requests.each do |merge_request| SystemNoteService.change_branch_presence( merge_request, merge_request.project, @current_user, - 'source', @branch_name, presence) + :source, @branch_name, presence) end end # Add comment about pushing new commits to merge requests def comment_mr_with_commits - @merge_requests.each do |merge_request| + @source_merge_requests.each do |merge_request| mr_commit_ids = Set.new(merge_request.commits.map(&:id)) new_commits, existing_commits = @commits.partition do |commit| @@ -107,7 +105,7 @@ module MergeRequests # Call merge request webhook with update branches def execute_mr_web_hooks - @merge_requests.each do |merge_request| + @source_merge_requests.each do |merge_request| execute_hooks(merge_request, 'update') end end @@ -116,7 +114,7 @@ module MergeRequests merge_requests.uniq.select(&:source_project) end - def merge_requests_for_branch + def merge_requests_for_source_branch merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a filter_merge_requests(merge_requests) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 24e06504078..c0a5c3aeb53 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -173,9 +173,9 @@ class SystemNoteService # noteable - Noteable object # project - Project owning noteable # author - User performing the change - # branch_type - 'source' or 'target' + # branch_type - :source or :target # branch - branch name - # presence - 'deleted' or 'created' + # presence - :add or :delete # # Example Note text: # @@ -183,7 +183,13 @@ class SystemNoteService # # Returns the created Note object def self.change_branch_presence(noteable, project, author, branch_type, branch, presence) - body = "#{branch_type} branch `#{branch}` #{presence}".capitalize + verb = + if presence == :add + 'restored' + else + 'deleted' + end + body = "#{branch_type.to_s} branch `#{branch}` #{verb}".capitalize create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index edb9fd0b43c..41eb5d41b2e 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -121,7 +121,7 @@ describe MergeRequests::RefreshService do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('Source branch `master` added') } + it { expect(@fork_merge_request.notes.last.note).to include('Source branch `master` restored') } it { expect(@fork_merge_request).to be_open } end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 108bc5995df..16b1c66ff9a 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -229,7 +229,7 @@ describe SystemNoteService do end describe '.change_branch' do - subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) } + subject { described_class.change_branch(noteable, project, author, :target, old_branch, new_branch) } let(:old_branch) { 'old_branch'} let(:new_branch) { 'new_branch'} @@ -243,7 +243,7 @@ describe SystemNoteService do end describe '.change_branch_presence' do - subject { described_class.change_branch_presence(noteable, project, author, 'source', 'feature', 'deleted') } + subject { described_class.change_branch_presence(noteable, project, author, 'source', 'feature', :delete) } it_behaves_like 'a system note' -- cgit v1.2.1 From 9c67f4fb51719116737f9812d312f20eefebeacd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 15 Oct 2015 02:24:30 -0700 Subject: Memoize merge request source branches --- app/services/merge_requests/refresh_service.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index fd15889343e..0362b2d0ea9 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -7,10 +7,9 @@ module MergeRequests @branch_name = Gitlab::Git.ref_name(ref) @fork_merge_requests = @project.fork_merge_requests.opened @commits = [] - @source_merge_requests = merge_requests_for_source_branch # Leave a system note if a branch were deleted/added - if Gitlab::Git.blank_ref?(oldrev) or Gitlab::Git.blank_ref?(newrev) + if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev) presence = Gitlab::Git.blank_ref?(oldrev) ? :add : :delete comment_mr_branch_presence_changed(presence) else @@ -81,7 +80,7 @@ module MergeRequests # Add comment about branches being deleted or added to merge requests def comment_mr_branch_presence_changed(presence) - @source_merge_requests.each do |merge_request| + merge_requests_for_source_branch.each do |merge_request| SystemNoteService.change_branch_presence( merge_request, merge_request.project, @current_user, :source, @branch_name, presence) @@ -90,7 +89,7 @@ module MergeRequests # Add comment about pushing new commits to merge requests def comment_mr_with_commits - @source_merge_requests.each do |merge_request| + merge_requests_for_source_branch.each do |merge_request| mr_commit_ids = Set.new(merge_request.commits.map(&:id)) new_commits, existing_commits = @commits.partition do |commit| @@ -105,7 +104,7 @@ module MergeRequests # Call merge request webhook with update branches def execute_mr_web_hooks - @source_merge_requests.each do |merge_request| + merge_requests_for_source_branch.each do |merge_request| execute_hooks(merge_request, 'update') end end @@ -115,9 +114,11 @@ module MergeRequests end def merge_requests_for_source_branch - merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a - merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a - filter_merge_requests(merge_requests) + @source_merge_requests ||= begin + merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a + merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a + filter_merge_requests(merge_requests) + end end end end -- cgit v1.2.1 From 577565d939d60cd48ce9e8aebc44663076af5aa2 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 15 Oct 2015 22:45:06 -0700 Subject: Add system notes for restored branches --- app/models/repository.rb | 4 ++++ app/services/merge_requests/refresh_service.rb | 30 +++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 8b51602bc23..921e1a9e426 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -480,6 +480,10 @@ class Repository end end + def merge_base(first_commit_id, second_commit_id) + rugged.merge_base(first_commit_id, second_commit_id) + end + def search_files(query, ref) offset = 2 args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref}) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 0362b2d0ea9..7c4cb35ec6b 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -10,13 +10,12 @@ module MergeRequests # Leave a system note if a branch were deleted/added if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev) - presence = Gitlab::Git.blank_ref?(oldrev) ? :add : :delete - comment_mr_branch_presence_changed(presence) + comment_mr_branch_presence_changed + comment_mr_with_commits if @commits.present? else @commits = @project.repository.commits_between(oldrev, newrev) - - close_merge_requests comment_mr_with_commits + close_merge_requests end reload_merge_requests @@ -79,8 +78,29 @@ module MergeRequests end # Add comment about branches being deleted or added to merge requests - def comment_mr_branch_presence_changed(presence) + def comment_mr_branch_presence_changed + presence = Gitlab::Git.blank_ref?(@oldrev) ? :add : :delete + merge_requests_for_source_branch.each do |merge_request| + last_commit = merge_request.last_commit + + # Only look at changed commits in restore branch case + unless Gitlab::Git.blank_ref?(@newrev) + begin + # Since any number of commits could have been made to the restored branch, + # find the common root to see what has been added. + common_ref = @project.repository.merge_base(last_commit.id, @newrev) + # If the last_commit no longer exists in this new branch, + # gitlab_git throws a Rugged::OdbError + # This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 + @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref + rescue => e + end + + # Prevent system notes from seeing a blank SHA + @oldrev = nil + end + SystemNoteService.change_branch_presence( merge_request, merge_request.project, @current_user, :source, @branch_name, presence) -- cgit v1.2.1 From bf290a52b7589ccd0e37a224ec36cec28acfb6a8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 15 Oct 2015 23:19:34 -0700 Subject: Rubocop fix --- app/services/merge_requests/refresh_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 7c4cb35ec6b..c378f14c265 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -94,7 +94,7 @@ module MergeRequests # gitlab_git throws a Rugged::OdbError # This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref - rescue => e + rescue end # Prevent system notes from seeing a blank SHA -- cgit v1.2.1 From 888c1a3fc53ff0318cd69d0e7f1edad25f306713 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 16 Oct 2015 00:25:19 -0700 Subject: Add spec for refresh service adding notes to restored branches --- app/services/merge_requests/refresh_service.rb | 5 ++--- .../merge_requests/refresh_service_spec.rb | 24 ++++++++++++---------- spec/services/system_note_service_spec.rb | 4 ++-- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index c378f14c265..121f6899011 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -90,9 +90,8 @@ module MergeRequests # Since any number of commits could have been made to the restored branch, # find the common root to see what has been added. common_ref = @project.repository.merge_base(last_commit.id, @newrev) - # If the last_commit no longer exists in this new branch, - # gitlab_git throws a Rugged::OdbError - # This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 + # If the a commit no longer exists in this repo, gitlab_git throws + # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref rescue end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 41eb5d41b2e..463cd594fb0 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -108,21 +108,23 @@ describe MergeRequests::RefreshService do context 'push new branch that exists in a merge request' do let(:refresh_service) { service.new(@fork_project, @user) } - before do - allow(refresh_service).to receive(:execute_hooks) + + it 'refreshes the merge request' do + expect(refresh_service).to receive(:execute_hooks). + with(@fork_merge_request, 'update') + allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev) + refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master') reload_mrs - end - it 'should execute hooks with update action' do - expect(refresh_service).to have_received(:execute_hooks). - with(@fork_merge_request, 'update') - end + expect(@merge_request.notes).to be_empty + expect(@merge_request).to be_open - it { expect(@merge_request.notes).to be_empty } - it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('Source branch `master` restored') } - it { expect(@fork_merge_request).to be_open } + notes = @fork_merge_request.notes.reorder(:created_at).map(&:note) + expect(notes[0]).to include('Source branch `master` restored') + expect(notes[1]).to include('Added 4 commits') + expect(@fork_merge_request).to be_open + end end def reload_mrs diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 16b1c66ff9a..699b2e3441f 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -229,7 +229,7 @@ describe SystemNoteService do end describe '.change_branch' do - subject { described_class.change_branch(noteable, project, author, :target, old_branch, new_branch) } + subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) } let(:old_branch) { 'old_branch'} let(:new_branch) { 'new_branch'} @@ -243,7 +243,7 @@ describe SystemNoteService do end describe '.change_branch_presence' do - subject { described_class.change_branch_presence(noteable, project, author, 'source', 'feature', :delete) } + subject { described_class.change_branch_presence(noteable, project, author, :source, 'feature', :delete) } it_behaves_like 'a system note' -- cgit v1.2.1 From f726df26c28666e640e566d50f363994e71cf681 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 16 Oct 2015 00:51:25 -0700 Subject: Reorder system note verb to say "Restored source branch X" instead of "Source branch X restored" --- app/services/system_note_service.rb | 4 ++-- spec/services/merge_requests/refresh_service_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index c0a5c3aeb53..37f454cfc3f 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -179,7 +179,7 @@ class SystemNoteService # # Example Note text: # - # "Target branch `feature` deleted" + # "Restored target branch `feature`" # # Returns the created Note object def self.change_branch_presence(noteable, project, author, branch_type, branch, presence) @@ -189,7 +189,7 @@ class SystemNoteService else 'deleted' end - body = "#{branch_type.to_s} branch `#{branch}` #{verb}".capitalize + body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 463cd594fb0..227ac995ec2 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -121,7 +121,7 @@ describe MergeRequests::RefreshService do expect(@merge_request).to be_open notes = @fork_merge_request.notes.reorder(:created_at).map(&:note) - expect(notes[0]).to include('Source branch `master` restored') + expect(notes[0]).to include('Restored source branch `master`') expect(notes[1]).to include('Added 4 commits') expect(@fork_merge_request).to be_open end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 699b2e3441f..a45130bd473 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -249,7 +249,7 @@ describe SystemNoteService do context 'when source branch deleted' do it 'sets the note text' do - expect(subject.note).to eq "Source branch `feature` deleted" + expect(subject.note).to eq "Deleted source branch `feature`" end end end -- cgit v1.2.1 From d1450af81f472ff0221454fe048e96cdd8dd11e7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 10:10:33 +0200 Subject: Add tests for last commit info on project home page Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/projects.scss | 2 ++ features/project/project.feature | 6 ++++++ features/steps/shared/project.rb | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index b6d53acd111..9fa853a6a39 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -513,6 +513,8 @@ pre.light-well { } .project-last-commit { + margin: 0 7px; + .ci-status { margin-right: 16px; } diff --git a/features/project/project.feature b/features/project/project.feature index b3fb0794547..1a53945eb04 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -31,6 +31,12 @@ Feature: Project And I visit project "Shop" page Then I should see project "Shop" README + Scenario: I should see last commit with CI + Given project "Shop" has CI enabled + Given project "Shop" has CI build + And I visit project "Shop" page + And I should see last commit with CI status + @javascript Scenario: I should see project activity When I visit project "Shop" activity page diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 5744e455ebd..7021fac5fe4 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -206,4 +206,11 @@ module SharedProject project = Project.find_by(name: "Shop") create :ci_commit, gl_project: project, sha: project.commit.sha end + + step 'I should see last commit with CI status' do + page.within ".project-last-commit" do + expect(page).to have_content(project.commit.sha[0..6]) + expect(page).to have_content("skipped") + end + end end -- cgit v1.2.1 From 66584f72fd7e9fa8657a711aa12864b76fb71ce4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 11:15:12 +0200 Subject: Add changelog item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index aba823948a7..cca8b38ef7e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ Please view this file on the master branch, on stable branches it's out of date. +v 8.2.0 (unreleased) + - Show last project commit to default branch on project home page + v 8.1.0 (unreleased) - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) - Speed up load times of issue detail pages by roughly 1.5x -- cgit v1.2.1 From 47e17103f589b51aa2f6267f56a67c1eb400054f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 13:36:48 +0200 Subject: Change order of sha and commit message on project home page Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/projects.scss | 2 +- app/views/projects/_last_commit.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 9fa853a6a39..0dddb6b6ed4 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -524,7 +524,7 @@ pre.light-well { } .commit_short_id { - margin-left: 5px; + margin-right: 5px; color: $gl-link-color; font-weight: 600; } diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 0c16c3ccbf7..d7b20bfc6b1 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -5,8 +5,8 @@ = ci_status_icon(ci_commit) = ci_commit.status - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" · #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by = commit_author_link(commit, avatar: true, size: 24) -- cgit v1.2.1 From c6be5006daa8a5a198bc06f8b8f07109535189ca Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 16 Oct 2015 15:29:50 +0200 Subject: Add index on ci_commits.gl_project_id Fixes #3086 --- db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb diff --git a/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb new file mode 100644 index 00000000000..52a47aa9c54 --- /dev/null +++ b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb @@ -0,0 +1,5 @@ +class AddCiProjectsGlProjectIdIndex < ActiveRecord::Migration + def change + add_index :ci_commits, :gl_project_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a11dfca034..885756fef12 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151008130321) do +ActiveRecord::Schema.define(version: 20151016131433) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -130,6 +130,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do t.integer "gl_project_id" end + add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree -- cgit v1.2.1 From a7fafee52124df6ffa9e6e1ecee6ff6884c6ce95 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 16 Oct 2015 08:45:50 -0500 Subject: Fix import from SVN link --- app/views/projects/imports/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index f8f2e192e29..92a87690c54 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -17,6 +17,6 @@ This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. %br The import will time out after 4 minutes. For big repositories, use a clone/push combination. - For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} + For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"} .form-actions = f.submit 'Start import', class: "btn btn-create", tabindex: 4 -- cgit v1.2.1 From 46d748dc2e7329fede6fd2c20038e98100b90fec Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 16:06:11 +0200 Subject: Highlight comment based on anchor in URL Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/assets/stylesheets/framework/timeline.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ea9f55ad08e..57768d6b3eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Show last project commit to default branch on project home page + - Highlight comment based on anchor in URL v 8.1.0 (unreleased) - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index bf21d7fce76..9d6f053aefe 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -13,6 +13,10 @@ border-bottom: 1px solid #ECEEF1; border-right: 1px solid #ECEEF1; + &:target { + background: $hover; + } + &:last-child { border-bottom: none; } -- cgit v1.2.1 From f5b9c3d59d0a9fff4641e6028357213998137897 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 16:15:30 +0200 Subject: Make selectbox options more compact Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/selects.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index cba621635b6..5bdd81777a7 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -32,7 +32,7 @@ } .select2-results .select2-result-label { - padding: 16px; + padding: 6px; } .select2-drop{ -- cgit v1.2.1 From db1e7fb8ddb019c12fee0e6c51426dcad0ce099b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 16:25:57 +0200 Subject: Dont put padding on typography but on holder elements instead Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/typography.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 6ccc084526a..1f4341b2462 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -207,9 +207,7 @@ a > code { */ .wiki { @include md-typography; - word-wrap: break-word; - padding: 7px; /* Link to current header. */ h1, h2, h3, h4, h5, h6 { -- cgit v1.2.1 From b4cc05e56e6178b55d554ab95da051fe91a4765b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 16:36:37 +0200 Subject: Add extra padding to some markdown pages Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/help.scss | 4 ++++ app/assets/stylesheets/pages/projects.scss | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 6da7a2511a2..bd224705f04 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -68,3 +68,7 @@ body.modal-open { .modal .modal-dialog { width: 860px; } + +.documentation { + padding: 7px; +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 0dddb6b6ed4..48b87750264 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -542,3 +542,7 @@ pre.light-well { } } } + +.project-show-readme .readme-holder { + padding: 7px; +} -- cgit v1.2.1 From 1543989d63981c65424ae053e6b8aa85ad850113 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 17:06:24 +0200 Subject: Improve markdown typography scss Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/typography.scss | 76 +++++++++++------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 1f4341b2462..1857c1659aa 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -1,5 +1,6 @@ @mixin md-typography { color: $md-text-color; + word-wrap: break-word; a { color: $md-link-color; @@ -73,6 +74,8 @@ } blockquote { + color: #7f8fa4; + font-size: inherit; padding: 8px 21px; margin: 12px 0 12px; border-left: 3px solid #e7e9ed; @@ -80,7 +83,7 @@ blockquote p { color: #7f8fa4 !important; - font-size: 15px; + font-size: inherit; line-height: 1.5; } @@ -112,9 +115,9 @@ font-weight: inherit; } - - ul { - color: #5c5d5e; + ul, ol { + padding: 0; + margin: 6px 0 6px 18px !important; } li { @@ -136,6 +139,33 @@ text-decoration: none; } } + + /* Link to current header. */ + h1, h2, h3, h4, h5, h6 { + position: relative; + + a.anchor { + // Setting `display: none` would prevent the anchor being scrolled to, so + // instead we set the height to 0 and it gets updated on hover. + height: 0; + } + + &:hover > a.anchor { + $size: 16px; + position: absolute; + right: 100%; + top: 50%; + margin-top: -$size/2; + margin-right: 0px; + padding-right: 20px; + display: inline-block; + width: $size; + height: $size; + background-image: image-url("icon-link.png"); + background-size: contain; + background-repeat: no-repeat; + } + } } @@ -202,47 +232,11 @@ a > code { } /** - * Wiki typography + * Apply Markdown typography * */ .wiki { @include md-typography; - word-wrap: break-word; - - /* Link to current header. */ - h1, h2, h3, h4, h5, h6 { - position: relative; - - a.anchor { - // Setting `display: none` would prevent the anchor being scrolled to, so - // instead we set the height to 0 and it gets updated on hover. - height: 0; - } - - &:hover > a.anchor { - $size: 16px; - position: absolute; - right: 100%; - top: 50%; - margin-top: -$size/2; - margin-right: 0px; - padding-right: 20px; - display: inline-block; - width: $size; - height: $size; - background-image: image-url("icon-link.png"); - background-size: contain; - background-repeat: no-repeat; - } - } - - ul,ol { - padding: 0; - margin: 6px 0 6px 18px !important; - } - ol { - color: #5c5d5e; - } } .md-area { -- cgit v1.2.1 From f6664601719925834fbf6784545ee64ad9413a12 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 16 Oct 2015 15:18:21 +0000 Subject: Increase dropdown padding a bit --- app/assets/stylesheets/framework/selects.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 5bdd81777a7..78fff58d232 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -32,7 +32,7 @@ } .select2-results .select2-result-label { - padding: 6px; + padding: 9px; } .select2-drop{ @@ -143,4 +143,4 @@ .ajax-users-dropdown { min-width: 250px !important; -} +} \ No newline at end of file -- cgit v1.2.1 From f405954764b8bea32e54b3cf59e06a5469781bf3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 16 Oct 2015 21:58:30 +0200 Subject: Added indexes for notes.line_code and CI columns This adds indexes for the following columns: * notes.line_code * ci_projects.gitlab_id * ci_projects.shared_runners_enabled * ci_builds.type * ci_builds.status --- db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb | 9 +++++++++ db/migrate/20151016195706_add_notes_line_code_index.rb | 5 +++++ db/schema.rb | 8 +++++++- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb create mode 100644 db/migrate/20151016195706_add_notes_line_code_index.rb diff --git a/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb new file mode 100644 index 00000000000..7f1af1c7583 --- /dev/null +++ b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb @@ -0,0 +1,9 @@ +class AddCiBuildsAndProjectsIndexes < ActiveRecord::Migration + def change + add_index :ci_projects, :gitlab_id + add_index :ci_projects, :shared_runners_enabled + + add_index :ci_builds, :type + add_index :ci_builds, :status + end +end diff --git a/db/migrate/20151016195706_add_notes_line_code_index.rb b/db/migrate/20151016195706_add_notes_line_code_index.rb new file mode 100644 index 00000000000..aeeb1a759fa --- /dev/null +++ b/db/migrate/20151016195706_add_notes_line_code_index.rb @@ -0,0 +1,5 @@ +class AddNotesLineCodeIndex < ActiveRecord::Migration + def change + add_index :notes, :line_code + end +end diff --git a/db/schema.rb b/db/schema.rb index 885756fef12..886b05f3e56 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151016131433) do +ActiveRecord::Schema.define(version: 20151016195706) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -115,6 +115,8 @@ ActiveRecord::Schema.define(version: 20151016131433) do add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree + add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree + add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree create_table "ci_commits", force: true do |t| t.integer "project_id" @@ -190,6 +192,9 @@ ActiveRecord::Schema.define(version: 20151016131433) do t.text "generated_yaml_config" end + add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree + add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree + create_table "ci_runner_projects", force: true do |t| t.integer "runner_id", null: false t.integer "project_id", null: false @@ -530,6 +535,7 @@ ActiveRecord::Schema.define(version: 20151016131433) do add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree + add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree -- cgit v1.2.1