From e3f8ed5cefcae3df9179f7b1198bf8026918653e Mon Sep 17 00:00:00 2001 From: Felix Ruess Date: Thu, 4 Feb 2016 17:51:12 +0000 Subject: [PATCH 0001/1431] doc: fix git lfs workaround for using http instead of https the url should only point to `info/lfs` instead of `info/lfs/batch/objects` --- doc/workflow/lfs/manage_large_binaries_with_git_lfs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index b59e92cb317..596478bf3cd 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -104,7 +104,7 @@ To prevent this from happening, set the lfs url in project Git config: ```bash -git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/objects/batch" +git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs" ``` ### Credentials are always required when pushing an object From 33520f1f699502740e67682b17ccd9432b6e693b Mon Sep 17 00:00:00 2001 From: a-tal Date: Fri, 26 Feb 2016 20:49:50 -0800 Subject: [PATCH 0002/1431] fix example urls for (de)associating runners to projects --- doc/api/runners.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index cc6c6b7cb2f..ddfa298f79d 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -275,7 +275,7 @@ POST /projects/:id/runners | `runner_id` | integer | yes | The ID of a runner | ``` -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" -F "runner_id=9" +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners" -F "runner_id=9" ``` Example response: @@ -306,7 +306,7 @@ DELETE /projects/:id/runners/:runner_id | `runner_id` | integer | yes | The ID of a runner | ``` -curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners/9" ``` Example response: From 6a0ea605e8b48deacbb4e93f7bb1d9b9abd2f7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= Date: Wed, 16 Mar 2016 03:16:25 +0200 Subject: [PATCH 0003/1431] Change deprecated usage of rendering without response body `render nothing: true` has been deprecated. For more information see [pr](https://github.com/rails/rails/pull/20336) --- app/controllers/admin/abuse_reports_controller.rb | 2 +- app/controllers/admin/broadcast_messages_controller.rb | 2 +- app/controllers/admin/keys_controller.rb | 2 +- app/controllers/admin/spam_logs_controller.rb | 2 +- app/controllers/admin/users_controller.rb | 2 +- app/controllers/concerns/toggle_subscription_action.rb | 2 +- app/controllers/dashboard/todos_controller.rb | 4 ++-- app/controllers/groups/group_members_controller.rb | 2 +- app/controllers/profiles/emails_controller.rb | 2 +- app/controllers/profiles/keys_controller.rb | 2 +- app/controllers/projects/milestones_controller.rb | 2 +- app/controllers/projects/notes_controller.rb | 4 ++-- app/controllers/projects/project_members_controller.rb | 4 ++-- app/controllers/projects/protected_branches_controller.rb | 2 +- spec/controllers/projects/raw_controller_spec.rb | 2 +- 15 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb index 2463cfa87be..76fc10bcc10 100644 --- a/app/controllers/admin/abuse_reports_controller.rb +++ b/app/controllers/admin/abuse_reports_controller.rb @@ -9,6 +9,6 @@ class Admin::AbuseReportsController < Admin::ApplicationController abuse_report.remove_user if params[:remove_user] abuse_report.destroy - render nothing: true + head :ok end end diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb index fc342924987..82055006ac0 100644 --- a/app/controllers/admin/broadcast_messages_controller.rb +++ b/app/controllers/admin/broadcast_messages_controller.rb @@ -32,7 +32,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController respond_to do |format| format.html { redirect_back_or_default(default: { action: 'index' }) } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb index cb33fdd9763..054bb52b696 100644 --- a/app/controllers/admin/keys_controller.rb +++ b/app/controllers/admin/keys_controller.rb @@ -6,7 +6,7 @@ class Admin::KeysController < Admin::ApplicationController respond_to do |format| format.html - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb index 377e9741e5f..3a2f0185315 100644 --- a/app/controllers/admin/spam_logs_controller.rb +++ b/app/controllers/admin/spam_logs_controller.rb @@ -11,7 +11,7 @@ class Admin::SpamLogsController < Admin::ApplicationController redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed." else spam_log.destroy - render nothing: true + head :ok end end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 87f4fb455b8..39c0c22f9b6 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -135,7 +135,7 @@ class Admin::UsersController < Admin::ApplicationController respond_to do |format| format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/concerns/toggle_subscription_action.rb b/app/controllers/concerns/toggle_subscription_action.rb index 8a43c0b93c4..9e3b9be2ff4 100644 --- a/app/controllers/concerns/toggle_subscription_action.rb +++ b/app/controllers/concerns/toggle_subscription_action.rb @@ -6,7 +6,7 @@ module ToggleSubscriptionAction subscribable_resource.toggle_subscription(current_user) - render nothing: true + head :ok end private diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 43cf8fa71af..d8ba51294cf 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -10,7 +10,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController respond_to do |format| format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' } - format.js { render nothing: true } + format.js { head :ok } end end @@ -19,7 +19,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController respond_to do |format| format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 0e902c4bb43..68f70120894 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -43,7 +43,7 @@ class Groups::GroupMembersController < Groups::ApplicationController respond_to do |format| format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb index 0ede9b8e21b..1c24c4db993 100644 --- a/app/controllers/profiles/emails_controller.rb +++ b/app/controllers/profiles/emails_controller.rb @@ -24,7 +24,7 @@ class Profiles::EmailsController < Profiles::ApplicationController respond_to do |format| format.html { redirect_to profile_emails_url } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index b88c080352b..9906493666a 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -27,7 +27,7 @@ class Profiles::KeysController < Profiles::ApplicationController respond_to do |format| format.html { redirect_to profile_keys_url } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index da46731d945..6579f4f8c89 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -68,7 +68,7 @@ class Projects::MilestonesController < Projects::ApplicationController respond_to do |format| format.html { redirect_to namespace_project_milestones_path } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 1b9dd568043..d91ab1cee1d 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -44,7 +44,7 @@ class Projects::NotesController < Projects::ApplicationController end respond_to do |format| - format.js { render nothing: true } + format.js { head :ok } end end @@ -53,7 +53,7 @@ class Projects::NotesController < Projects::ApplicationController note.update_attribute(:attachment, nil) respond_to do |format| - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index e7bddc4a6f1..b150e9ef029 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -55,7 +55,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController format.html do redirect_to namespace_project_project_members_path(@project.namespace, @project) end - format.js { render nothing: true } + format.js { head :ok } end end @@ -81,7 +81,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController respond_to do |format| format.html { redirect_to dashboard_projects_path, notice: "You left the project." } - format.js { render nothing: true } + format.js { head :ok } end else if current_user == @project.owner diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index e49259c34b6..efa7bf14d0f 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -39,7 +39,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController respond_to do |format| format.html { redirect_to namespace_project_protected_branches_path } - format.js { render nothing: true } + format.js { head :ok } end end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 1caa476d37d..fb29274c687 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -42,7 +42,7 @@ describe Projects::RawController do before do public_project.lfs_objects << lfs_object allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true) - allow(controller).to receive(:send_file) { controller.render nothing: true } + allow(controller).to receive(:send_file) { controller.head :ok } end it 'serves the file' do From 19a5e7c95e91baca58836ad3ae189190c9ba4ca2 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 23 Mar 2016 14:04:09 +0100 Subject: [PATCH 0004/1431] Test Grack::Auth via a request spec --- .../git_http_spec.rb} | 167 ++++++++---------- 1 file changed, 74 insertions(+), 93 deletions(-) rename spec/{lib/gitlab/backend/grack_auth_spec.rb => requests/git_http_spec.rb} (57%) diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/requests/git_http_spec.rb similarity index 57% rename from spec/lib/gitlab/backend/grack_auth_spec.rb rename to spec/requests/git_http_spec.rb index cd26dca0998..7e274b4209b 100644 --- a/spec/lib/gitlab/backend/grack_auth_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -1,83 +1,60 @@ require "spec_helper" -describe Grack::Auth, lib: true do +describe 'Git HTTP requests', lib: true do let(:user) { create(:user) } let(:project) { create(:project) } - let(:app) { lambda { |env| [200, {}, "Success!"] } } - let!(:auth) { Grack::Auth.new(app) } - let(:env) do - { - 'rack.input' => '', - 'REQUEST_METHOD' => 'GET', - 'QUERY_STRING' => 'service=git-upload-pack' - } - end - let(:status) { auth.call(env).first } - describe "#call" do context "when the project doesn't exist" do - before do - env["PATH_INFO"] = "doesnt/exist.git" - end - context "when no authentication is provided" do it "responds with status 401" do - expect(status).to eq(401) + clone_get '/doesnt/exist.git/info/refs' + + expect(response.status).to eq(401) end end context "when username and password are provided" do context "when authentication fails" do - before do - env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope") - end - it "responds with status 401" do - expect(status).to eq(401) + clone_get '/doesnt/exist.git/info/refs', user: user.username, password: "nope" + + expect(response.status).to eq(401) end end context "when authentication succeeds" do - before do - env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) - end - it "responds with status 404" do - expect(status).to eq(404) + clone_get '/doesnt/exist.git/info/refs', user: user.username, password: user.password + + expect(response.status).to eq(404) end end end end context "when the Wiki for a project exists" do - before do - @wiki = ProjectWiki.new(project) - env["PATH_INFO"] = "#{@wiki.repository.path_with_namespace}.git/info/refs" - project.update_attribute(:visibility_level, Project::PUBLIC) - end - it "responds with the right project" do - response = auth.call(env) - json_body = ActiveSupport::JSON.decode(response[2][0]) + wiki = ProjectWiki.new(project) + project.update_attribute(:visibility_level, Project::PUBLIC) - expect(response.first).to eq(200) - expect(json_body['RepoPath']).to include(@wiki.repository.path_with_namespace) + clone_get "/#{wiki.repository.path_with_namespace}.git/info/refs" + json_body = ActiveSupport::JSON.decode(response.body) + + expect(response.status).to eq(200) + expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) end end context "when the project exists" do - before do - env["PATH_INFO"] = project.path_with_namespace + ".git" - end + let(:path) { clone_path(project) } context "when the project is public" do - before do - project.update_attribute(:visibility_level, Project::PUBLIC) - end - it "responds with status 200" do - expect(status).to eq(200) + project.update_attribute(:visibility_level, Project::PUBLIC) + clone_get path + + expect(response.status).to eq(200) end end @@ -88,85 +65,74 @@ describe Grack::Auth, lib: true do context "when no authentication is provided" do it "responds with status 401" do - expect(status).to eq(401) + clone_get path + + expect(response.status).to eq(401) end end context "when username and password are provided" do context "when authentication fails" do - before do - env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope") - end - it "responds with status 401" do - expect(status).to eq(401) + clone_get path, user: user.username, password: 'nope' + + expect(response.status).to eq(401) end context "when the user is IP banned" do - before do + it "responds with status 401" do expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') - end - it "responds with status 401" do - expect(status).to eq(401) + clone_get path, user: user.username, password: 'nope' + + expect(response.status).to eq(401) end end end context "when authentication succeeds" do - before do - env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) - end - context "when the user has access to the project" do before do project.team << [user, :master] end context "when the user is blocked" do - before do + it "responds with status 404" do user.block project.team << [user, :master] - end - it "responds with status 404" do - expect(status).to eq(404) + clone_get path, user: user.username, password: user.password + + expect(response.status).to eq(404) end end context "when the user isn't blocked" do - before do - expect(Rack::Attack::Allow2Ban).to receive(:reset) - end - it "responds with status 200" do - expect(status).to eq(200) + expect(Rack::Attack::Allow2Ban).to receive(:reset) + + clone_get path, user: user.username, password: user.password + + expect(response.status).to eq(200) end end context "when blank password attempts follow a valid login" do - let(:options) { Gitlab.config.rack_attack.git_basic_auth } - let(:maxretry) { options[:maxretry] - 1 } - let(:ip) { '1.2.3.4' } - - before do - allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) - Rack::Attack::Allow2Ban.reset(ip, options) - end - - after do - Rack::Attack::Allow2Ban.reset(ip, options) - end - def attempt_login(include_password) password = include_password ? user.password : "" - env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, password) - Grack::Auth.new(app) - auth.call(env).first + clone_get path, user: user.username, password: password + response.status end it "repeated attempts followed by successful attempt" do + options = Gitlab.config.rack_attack.git_basic_auth + maxretry = options[:maxretry] - 1 + ip = '1.2.3.4' + + allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) + Rack::Attack::Allow2Ban.reset(ip, options) + maxretry.times.each do expect(attempt_login(false)).to eq(401) end @@ -177,33 +143,48 @@ describe Grack::Auth, lib: true do maxretry.times.each do expect(attempt_login(false)).to eq(401) end + + Rack::Attack::Allow2Ban.reset(ip, options) end end end context "when the user doesn't have access to the project" do it "responds with status 404" do - expect(status).to eq(404) + clone_get path, user: user.username, password: user.password + + expect(response.status).to eq(404) end end end end context "when a gitlab ci token is provided" do - let(:token) { "123" } - let(:project) { FactoryGirl.create :empty_project } - - before do + it "responds with status 200" do + token = "123" + project = FactoryGirl.create :empty_project project.update_attributes(runners_token: token, builds_enabled: true) - env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials("gitlab-ci-token", token) - end + clone_get clone_path(project), user: 'gitlab-ci-token', password: token - it "responds with status 200" do - expect(status).to eq(200) + expect(response.status).to eq(200) end end end end end + + def clone_get(url, user: nil, password: nil) + if user && password + env = { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } + else + env = {} + end + + get url, { 'service' => 'git-upload-pack' }, env + end + + def clone_path(project) + "/#{project.path_with_namespace}.git/info/refs" + end end From 55f5a68f092cc64ae4782c0d7fbbf1d3d1ce6284 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 23 Mar 2016 18:34:16 +0100 Subject: [PATCH 0005/1431] Get Grack::Auth tests to pass --- .../projects/application_controller.rb | 22 ++- .../projects/git_http_controller.rb | 167 ++++++++++++++++++ config/routes.rb | 10 +- 3 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 app/controllers/projects/git_http_controller.rb diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 657ee94cfd7..5f5dc1adadf 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -10,9 +10,6 @@ class Projects::ApplicationController < ApplicationController def project unless @project - namespace = params[:namespace_id] - id = params[:project_id] || params[:id] - # Redirect from # localhost/group/project.git # to @@ -23,8 +20,7 @@ class Projects::ApplicationController < ApplicationController return end - project_path = "#{namespace}/#{id}" - @project = Project.find_with_namespace(project_path) + @project = find_project if @project && can?(current_user, :read_project, @project) if @project.path_with_namespace != project_path @@ -44,6 +40,22 @@ class Projects::ApplicationController < ApplicationController @project end + def id + params[:project_id] || params[:id] + end + + def namespace + params[:namespace_id] + end + + def project_path + "#{namespace}/#{id}" + end + + def find_project + Project.find_with_namespace(project_path) + end + def repository @repository ||= project.repository end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb new file mode 100644 index 00000000000..129e87dbf13 --- /dev/null +++ b/app/controllers/projects/git_http_controller.rb @@ -0,0 +1,167 @@ +class Projects::GitHttpController < Projects::ApplicationController + skip_before_action :repository + before_action :authenticate_user + before_action :project_found? + + def git_rpc + if upload_pack? && upload_pack_allowed? + render_ok and return + end + + render_not_found + end + + %i{info_refs git_receive_pack git_upload_pack}.each do |method| + alias_method method, :git_rpc + end + + private + + def authenticate_user + return if project && project.public? && upload_pack? + + authenticate_or_request_with_http_basic do |login, password| + return @ci = true if ci_request?(login, password) + + @user = Gitlab::Auth.new.find(login, password) + @user ||= oauth_access_token_check(login, password) + rate_limit_ip!(login, @user) + end + end + + def project_found? + render_not_found if project.nil? + end + + def ci_request?(login, password) + matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) + + if project && matched_login.present? && upload_pack? + underscored_service = matched_login['s'].underscore + + if underscored_service == 'gitlab_ci' + return project && project.valid_build_token?(password) + elsif Service.available_services_names.include?(underscored_service) + service_method = "#{underscored_service}_service" + service = project.send(service_method) + + return service && service.activated? && service.valid_token?(password) + end + end + + false + end + + def oauth_access_token_check(login, password) + if login == "oauth2" && upload_pack? && password.present? + token = Doorkeeper::AccessToken.by_token(password) + token && token.accessible? && User.find_by(id: token.resource_owner_id) + end + end + + def rate_limit_ip!(login, user) + # If the user authenticated successfully, we reset the auth failure count + # from Rack::Attack for that IP. A client may attempt to authenticate + # with a username and blank password first, and only after it receives + # a 401 error does it present a password. Resetting the count prevents + # false positives from occurring. + # + # Otherwise, we let Rack::Attack know there was a failed authentication + # attempt from this IP. This information is stored in the Rails cache + # (Redis) and will be used by the Rack::Attack middleware to decide + # whether to block requests from this IP. + + config = Gitlab.config.rack_attack.git_basic_auth + return user unless config.enabled + + if user + # A successful login will reset the auth failure count from this IP + Rack::Attack::Allow2Ban.reset(request.ip, config) + else + banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do + # Unless the IP is whitelisted, return true so that Allow2Ban + # increments the counter (stored in Rails.cache) for the IP + if config.ip_whitelist.include?(request.ip) + false + else + true + end + end + + if banned + Rails.logger.info "IP #{request.ip} failed to login " \ + "as #{login} but has been temporarily banned from Git auth" + end + end + + user + end + + def project + return @project if defined?(@project) + @project = find_project + end + + def id + id = params[:project_id] + return if id.nil? + + if id.end_with?('.wiki.git') + id.slice(0, id.length - 9) + elsif id.end_with?('.git') + id.slice(0, id.length - 4) + end + end + + def repo_path + @repo_path ||= begin + if params[:project_id].end_with?('.wiki.git') + project.wiki.wiki.path + else + repository.path_to_repo + end + end + end + + def upload_pack? + if action_name == 'info_refs' + params[:service] == 'git-upload-pack' + else + action_name == 'git_upload_pack' + end + end + + def render_ok + render json: { + 'GL_ID' => Gitlab::ShellEnv.gl_id(@user), + 'RepoPath' => repo_path, + } + end + + def render_not_found + render text: 'Not Found', status: :not_found + end + + def ci? + !!@ci + end + + def user + @user + end + + def upload_pack_allowed? + if !Gitlab.config.gitlab_shell.upload_pack + false + elsif ci? + true + elsif user + Gitlab::GitAccess.new(user, project).download_access_check.allowed? + elsif project.public? + # Allow clone/fetch for public projects + true + else + false + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 4a3c23b7c1c..47ab1a89b8d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -59,9 +59,6 @@ Rails.application.routes.draw do mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end - # Enable Grack support - mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put] - # Help get 'help' => 'help#index' get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ } @@ -426,6 +423,13 @@ Rails.application.routes.draw do end scope module: :projects do + # Git HTTP clients ('git clone' etc.) + scope constraints: { format: /(git|wiki\.git)/ } do + get '/info/refs', to: 'git_http#info_refs', only: :get + get '/git-upload-pack', to: 'git_http#git_upload_pack', only: :post + get '/git-receive-pack', to: 'git_http#git_receive_pack', only: :post + end + # Blob routes: get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob' post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob' From 8f3e86d72c16294c8bcec8c9a3af86ec99d66ee8 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 14:53:20 +0100 Subject: [PATCH 0006/1431] Keep Grack::Auth in the routes for LFS only --- config/routes.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 47ab1a89b8d..021eab89c2a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -59,6 +59,9 @@ Rails.application.routes.draw do mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end + # Enable Grack support (for LFS only) + mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put] + # Help get 'help' => 'help#index' get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ } From 31bc876b7b34fa1785be022e9cffdc601f2192d7 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 16:14:09 +0100 Subject: [PATCH 0007/1431] Test both GET and POST for git-upload-pack --- config/routes.rb | 4 +- spec/requests/git_http_spec.rb | 112 ++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 021eab89c2a..eace7516e91 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -429,8 +429,8 @@ Rails.application.routes.draw do # Git HTTP clients ('git clone' etc.) scope constraints: { format: /(git|wiki\.git)/ } do get '/info/refs', to: 'git_http#info_refs', only: :get - get '/git-upload-pack', to: 'git_http#git_upload_pack', only: :post - get '/git-receive-pack', to: 'git_http#git_receive_pack', only: :post + post '/git-upload-pack', to: 'git_http#git_upload_pack', only: :post + post '/git-receive-pack', to: 'git_http#git_receive_pack', only: :post end # Blob routes: diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 7e274b4209b..ef0b83fd475 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -8,26 +8,26 @@ describe 'Git HTTP requests', lib: true do context "when the project doesn't exist" do context "when no authentication is provided" do it "responds with status 401" do - clone_get '/doesnt/exist.git/info/refs' - - expect(response.status).to eq(401) + download('doesnt/exist.git') do |response| + expect(response.status).to eq(401) + end end end context "when username and password are provided" do context "when authentication fails" do it "responds with status 401" do - clone_get '/doesnt/exist.git/info/refs', user: user.username, password: "nope" - - expect(response.status).to eq(401) + download('doesnt/exist.git', user: user.username, password: "nope") do |response| + expect(response.status).to eq(401) + end end end context "when authentication succeeds" do it "responds with status 404" do - clone_get '/doesnt/exist.git/info/refs', user: user.username, password: user.password - - expect(response.status).to eq(404) + download('/doesnt/exist.git', user: user.username, password: user.password) do |response| + expect(response.status).to eq(404) + end end end end @@ -38,23 +38,25 @@ describe 'Git HTTP requests', lib: true do wiki = ProjectWiki.new(project) project.update_attribute(:visibility_level, Project::PUBLIC) - clone_get "/#{wiki.repository.path_with_namespace}.git/info/refs" - json_body = ActiveSupport::JSON.decode(response.body) - - expect(response.status).to eq(200) - expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) + download("/#{wiki.repository.path_with_namespace}.git") do |response| + json_body = ActiveSupport::JSON.decode(response.body) + + expect(response.status).to eq(200) + expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) + end end end context "when the project exists" do - let(:path) { clone_path(project) } + let(:path) { "#{project.path_with_namespace}.git" } + let(:env) { {} } context "when the project is public" do it "responds with status 200" do project.update_attribute(:visibility_level, Project::PUBLIC) - clone_get path - - expect(response.status).to eq(200) + download(path, env) do |response| + expect(response.status).to eq(200) + end end end @@ -65,33 +67,37 @@ describe 'Git HTTP requests', lib: true do context "when no authentication is provided" do it "responds with status 401" do - clone_get path - - expect(response.status).to eq(401) + download(path, env) do |response| + expect(response.status).to eq(401) + end end end context "when username and password are provided" do + let(:env) { { user: user.username, password: 'nope' } } + context "when authentication fails" do it "responds with status 401" do - clone_get path, user: user.username, password: 'nope' - - expect(response.status).to eq(401) + download(path, env) do |response| + expect(response.status).to eq(401) + end end context "when the user is IP banned" do it "responds with status 401" do expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') - - clone_get path, user: user.username, password: 'nope' - + + clone_get(path, env) + expect(response.status).to eq(401) end end end context "when authentication succeeds" do + let(:env) { { user: user.username, password: user.password } } + context "when the user has access to the project" do before do project.team << [user, :master] @@ -102,18 +108,18 @@ describe 'Git HTTP requests', lib: true do user.block project.team << [user, :master] - clone_get path, user: user.username, password: user.password - - expect(response.status).to eq(404) + download(path, env) do |response| + expect(response.status).to eq(404) + end end end context "when the user isn't blocked" do it "responds with status 200" do expect(Rack::Attack::Allow2Ban).to receive(:reset) - - clone_get path, user: user.username, password: user.password - + + clone_get(path, env) + expect(response.status).to eq(200) end end @@ -151,9 +157,9 @@ describe 'Git HTTP requests', lib: true do context "when the user doesn't have access to the project" do it "responds with status 404" do - clone_get path, user: user.username, password: user.password - - expect(response.status).to eq(404) + download(path, user: user.username, password: user.password) do |response| + expect(response.status).to eq(404) + end end end end @@ -165,7 +171,7 @@ describe 'Git HTTP requests', lib: true do project = FactoryGirl.create :empty_project project.update_attributes(runners_token: token, builds_enabled: true) - clone_get clone_path(project), user: 'gitlab-ci-token', password: token + clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token expect(response.status).to eq(200) end @@ -174,17 +180,33 @@ describe 'Git HTTP requests', lib: true do end end - def clone_get(url, user: nil, password: nil) - if user && password - env = { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } - else - env = {} - end - - get url, { 'service' => 'git-upload-pack' }, env + def clone_get(project, options={}) + get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password)) + end + + def clone_post(project, options={}) + post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password)) end def clone_path(project) "/#{project.path_with_namespace}.git/info/refs" end + + def download(project, user: nil, password: nil) + args = [project, {user: user, password: password}] + + clone_get *args + yield response + + clone_post *args + yield response + end + + def auth_env(user, password) + if user && password + { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } + else + {} + end + end end From 0f8fe93c26f00eac14cbc33e9ed2e2260b7014cc Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 16:21:19 +0100 Subject: [PATCH 0008/1431] Whitespace, remove unused method --- spec/requests/git_http_spec.rb | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index ef0b83fd475..1e3f3f3e617 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -40,7 +40,7 @@ describe 'Git HTTP requests', lib: true do download("/#{wiki.repository.path_with_namespace}.git") do |response| json_body = ActiveSupport::JSON.decode(response.body) - + expect(response.status).to eq(200) expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) end @@ -75,7 +75,7 @@ describe 'Git HTTP requests', lib: true do context "when username and password are provided" do let(:env) { { user: user.username, password: 'nope' } } - + context "when authentication fails" do it "responds with status 401" do download(path, env) do |response| @@ -87,9 +87,9 @@ describe 'Git HTTP requests', lib: true do it "responds with status 401" do expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') - + clone_get(path, env) - + expect(response.status).to eq(401) end end @@ -97,7 +97,7 @@ describe 'Git HTTP requests', lib: true do context "when authentication succeeds" do let(:env) { { user: user.username, password: user.password } } - + context "when the user has access to the project" do before do project.team << [user, :master] @@ -117,9 +117,9 @@ describe 'Git HTTP requests', lib: true do context "when the user isn't blocked" do it "responds with status 200" do expect(Rack::Attack::Allow2Ban).to receive(:reset) - + clone_get(path, env) - + expect(response.status).to eq(200) end end @@ -183,25 +183,21 @@ describe 'Git HTTP requests', lib: true do def clone_get(project, options={}) get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password)) end - + def clone_post(project, options={}) post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password)) end - def clone_path(project) - "/#{project.path_with_namespace}.git/info/refs" - end - def download(project, user: nil, password: nil) args = [project, {user: user, password: password}] clone_get *args yield response - + clone_post *args yield response end - + def auth_env(user, password) if user && password { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } From 1433068ad951045a3440d58b86e9489001ff3774 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 16:28:18 +0100 Subject: [PATCH 0009/1431] Remove useles only: --- config/routes.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index eace7516e91..40c149abda2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -428,9 +428,9 @@ Rails.application.routes.draw do scope module: :projects do # Git HTTP clients ('git clone' etc.) scope constraints: { format: /(git|wiki\.git)/ } do - get '/info/refs', to: 'git_http#info_refs', only: :get - post '/git-upload-pack', to: 'git_http#git_upload_pack', only: :post - post '/git-receive-pack', to: 'git_http#git_receive_pack', only: :post + get '/info/refs', to: 'git_http#info_refs' + post '/git-upload-pack', to: 'git_http#git_upload_pack' + post '/git-receive-pack', to: 'git_http#git_receive_pack' end # Blob routes: From aae577f92141f3ec973b4dd362452502274147f5 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 17:34:56 +0100 Subject: [PATCH 0010/1431] Add test for gitlab_shell.upload_pack config setting --- spec/requests/git_http_spec.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 1e3f3f3e617..3a6a9b7a70d 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -52,12 +52,25 @@ describe 'Git HTTP requests', lib: true do let(:env) { {} } context "when the project is public" do - it "responds with status 200" do + before do project.update_attribute(:visibility_level, Project::PUBLIC) + end + + it "responds with status 200" do download(path, env) do |response| expect(response.status).to eq(200) end end + + context 'but git-upload-pack is disabled' do + it "responds with status 404" do + allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) + + download(path, env) do |response| + expect(response.status).to eq(404) + end + end + end end context "when the project is private" do From ccf5b21f28d41e10de450e31d6e8855d1ee2f81e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 17:38:30 +0100 Subject: [PATCH 0011/1431] Remove useless "describe" --- spec/requests/git_http_spec.rb | 351 ++++++++++++++++----------------- 1 file changed, 174 insertions(+), 177 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 3a6a9b7a70d..a26b986aeb0 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -4,195 +4,192 @@ describe 'Git HTTP requests', lib: true do let(:user) { create(:user) } let(:project) { create(:project) } - describe "#call" do - context "when the project doesn't exist" do - context "when no authentication is provided" do + context "when the project doesn't exist" do + context "when no authentication is provided" do + it "responds with status 401" do + download('doesnt/exist.git') do |response| + expect(response.status).to eq(401) + end + end + end + + context "when username and password are provided" do + context "when authentication fails" do it "responds with status 401" do - download('doesnt/exist.git') do |response| + download('doesnt/exist.git', user: user.username, password: "nope") do |response| expect(response.status).to eq(401) end end end - context "when username and password are provided" do - context "when authentication fails" do - it "responds with status 401" do - download('doesnt/exist.git', user: user.username, password: "nope") do |response| - expect(response.status).to eq(401) - end - end - end - - context "when authentication succeeds" do - it "responds with status 404" do - download('/doesnt/exist.git', user: user.username, password: user.password) do |response| - expect(response.status).to eq(404) - end - end - end - end - end - - context "when the Wiki for a project exists" do - it "responds with the right project" do - wiki = ProjectWiki.new(project) - project.update_attribute(:visibility_level, Project::PUBLIC) - - download("/#{wiki.repository.path_with_namespace}.git") do |response| - json_body = ActiveSupport::JSON.decode(response.body) - - expect(response.status).to eq(200) - expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) - end - end - end - - context "when the project exists" do - let(:path) { "#{project.path_with_namespace}.git" } - let(:env) { {} } - - context "when the project is public" do - before do - project.update_attribute(:visibility_level, Project::PUBLIC) - end - - it "responds with status 200" do - download(path, env) do |response| - expect(response.status).to eq(200) - end - end - - context 'but git-upload-pack is disabled' do - it "responds with status 404" do - allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) - - download(path, env) do |response| - expect(response.status).to eq(404) - end - end - end - end - - context "when the project is private" do - before do - project.update_attribute(:visibility_level, Project::PRIVATE) - end - - context "when no authentication is provided" do - it "responds with status 401" do - download(path, env) do |response| - expect(response.status).to eq(401) - end - end - end - - context "when username and password are provided" do - let(:env) { { user: user.username, password: 'nope' } } - - context "when authentication fails" do - it "responds with status 401" do - download(path, env) do |response| - expect(response.status).to eq(401) - end - end - - context "when the user is IP banned" do - it "responds with status 401" do - expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) - allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') - - clone_get(path, env) - - expect(response.status).to eq(401) - end - end - end - - context "when authentication succeeds" do - let(:env) { { user: user.username, password: user.password } } - - context "when the user has access to the project" do - before do - project.team << [user, :master] - end - - context "when the user is blocked" do - it "responds with status 404" do - user.block - project.team << [user, :master] - - download(path, env) do |response| - expect(response.status).to eq(404) - end - end - end - - context "when the user isn't blocked" do - it "responds with status 200" do - expect(Rack::Attack::Allow2Ban).to receive(:reset) - - clone_get(path, env) - - expect(response.status).to eq(200) - end - end - - context "when blank password attempts follow a valid login" do - def attempt_login(include_password) - password = include_password ? user.password : "" - clone_get path, user: user.username, password: password - response.status - end - - it "repeated attempts followed by successful attempt" do - options = Gitlab.config.rack_attack.git_basic_auth - maxretry = options[:maxretry] - 1 - ip = '1.2.3.4' - - allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) - Rack::Attack::Allow2Ban.reset(ip, options) - - maxretry.times.each do - expect(attempt_login(false)).to eq(401) - end - - expect(attempt_login(true)).to eq(200) - expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey - - maxretry.times.each do - expect(attempt_login(false)).to eq(401) - end - - Rack::Attack::Allow2Ban.reset(ip, options) - end - end - end - - context "when the user doesn't have access to the project" do - it "responds with status 404" do - download(path, user: user.username, password: user.password) do |response| - expect(response.status).to eq(404) - end - end - end - end - end - - context "when a gitlab ci token is provided" do - it "responds with status 200" do - token = "123" - project = FactoryGirl.create :empty_project - project.update_attributes(runners_token: token, builds_enabled: true) - - clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token - - expect(response.status).to eq(200) + context "when authentication succeeds" do + it "responds with status 404" do + download('/doesnt/exist.git', user: user.username, password: user.password) do |response| + expect(response.status).to eq(404) end end end end end + context "when the Wiki for a project exists" do + it "responds with the right project" do + wiki = ProjectWiki.new(project) + project.update_attribute(:visibility_level, Project::PUBLIC) + + download("/#{wiki.repository.path_with_namespace}.git") do |response| + json_body = ActiveSupport::JSON.decode(response.body) + + expect(response.status).to eq(200) + expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) + end + end + end + + context "when the project exists" do + let(:path) { "#{project.path_with_namespace}.git" } + let(:env) { {} } + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + it "responds with status 200" do + download(path, env) do |response| + expect(response.status).to eq(200) + end + end + + context 'but git-upload-pack is disabled' do + it "responds with status 404" do + allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) + + download(path, env) do |response| + expect(response.status).to eq(404) + end + end + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when no authentication is provided" do + it "responds with status 401" do + download(path, env) do |response| + expect(response.status).to eq(401) + end + end + end + + context "when username and password are provided" do + let(:env) { { user: user.username, password: 'nope' } } + + context "when authentication fails" do + it "responds with status 401" do + download(path, env) do |response| + expect(response.status).to eq(401) + end + end + + context "when the user is IP banned" do + it "responds with status 401" do + expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) + allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') + + clone_get(path, env) + + expect(response.status).to eq(401) + end + end + end + + context "when authentication succeeds" do + let(:env) { { user: user.username, password: user.password } } + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + it "responds with status 404" do + user.block + project.team << [user, :master] + + download(path, env) do |response| + expect(response.status).to eq(404) + end + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + expect(Rack::Attack::Allow2Ban).to receive(:reset) + + clone_get(path, env) + + expect(response.status).to eq(200) + end + end + + context "when blank password attempts follow a valid login" do + def attempt_login(include_password) + password = include_password ? user.password : "" + clone_get path, user: user.username, password: password + response.status + end + + it "repeated attempts followed by successful attempt" do + options = Gitlab.config.rack_attack.git_basic_auth + maxretry = options[:maxretry] - 1 + ip = '1.2.3.4' + + allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) + Rack::Attack::Allow2Ban.reset(ip, options) + + maxretry.times.each do + expect(attempt_login(false)).to eq(401) + end + + expect(attempt_login(true)).to eq(200) + expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey + + maxretry.times.each do + expect(attempt_login(false)).to eq(401) + end + + Rack::Attack::Allow2Ban.reset(ip, options) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + download(path, user: user.username, password: user.password) do |response| + expect(response.status).to eq(404) + end + end + end + end + end + + context "when a gitlab ci token is provided" do + it "responds with status 200" do + token = "123" + project = FactoryGirl.create :empty_project + project.update_attributes(runners_token: token, builds_enabled: true) + + clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token + + expect(response.status).to eq(200) + end + end + end + end def clone_get(project, options={}) get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password)) end From 57145483fc41cc73b7b41005ebac90779f817b5e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 17:44:10 +0100 Subject: [PATCH 0012/1431] Spec Www-Authenticate --- spec/requests/git_http_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index a26b986aeb0..967e0ab6e74 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -4,6 +4,12 @@ describe 'Git HTTP requests', lib: true do let(:user) { create(:user) } let(:project) { create(:project) } + it "gives WWW-Authenticate hints" do + clone_get('doesnt/exist.git') + + expect(response.header['WWW-Authenticate']).to start_with('Basic ') + end + context "when the project doesn't exist" do context "when no authentication is provided" do it "responds with status 401" do From 5f3708418ab71c47c6fffe63b1fac03c0e7c889f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 17:44:13 +0100 Subject: [PATCH 0013/1431] Whitespace! --- spec/requests/git_http_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 967e0ab6e74..c1aad48ad04 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -60,13 +60,13 @@ describe 'Git HTTP requests', lib: true do before do project.update_attribute(:visibility_level, Project::PUBLIC) end - + it "responds with status 200" do download(path, env) do |response| expect(response.status).to eq(200) end end - + context 'but git-upload-pack is disabled' do it "responds with status 404" do allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) From 5fe06d7365f5552904add8027309d6216954793e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 24 Mar 2016 18:58:29 +0100 Subject: [PATCH 0014/1431] Add some upload specs --- .../projects/git_http_controller.rb | 40 +++++++++--- spec/requests/git_http_spec.rb | 63 ++++++++++++++++++- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 129e87dbf13..a26ab736115 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -5,10 +5,12 @@ class Projects::GitHttpController < Projects::ApplicationController def git_rpc if upload_pack? && upload_pack_allowed? - render_ok and return + render_ok + elsif receive_pack? && receive_pack_allowed? + render_ok + else + render_not_found end - - render_not_found end %i{info_refs git_receive_pack git_upload_pack}.each do |method| @@ -30,7 +32,7 @@ class Projects::GitHttpController < Projects::ApplicationController end def project_found? - render_not_found if project.nil? + render_not_found if project.blank? end def ci_request?(login, password) @@ -124,13 +126,21 @@ class Projects::GitHttpController < Projects::ApplicationController end def upload_pack? - if action_name == 'info_refs' - params[:service] == 'git-upload-pack' - else - action_name == 'git_upload_pack' - end + rpc == 'git-upload-pack' end + def receive_pack? + rpc == 'git-receive-pack' + end + + def rpc + if action_name == 'info_refs' + params[:service] + else + action_name.gsub('_', '-') + end + end + def render_ok render json: { 'GL_ID' => Gitlab::ShellEnv.gl_id(@user), @@ -164,4 +174,16 @@ class Projects::GitHttpController < Projects::ApplicationController false end end + + def receive_pack_allowed? + if !Gitlab.config.gitlab_shell.receive_pack + false + elsif user + # Skip user authorization on upload request. + # It will be done by the pre-receive hook in the repository. + true + else + false + end + end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c1aad48ad04..1fa14cadc0f 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -61,12 +61,38 @@ describe 'Git HTTP requests', lib: true do project.update_attribute(:visibility_level, Project::PUBLIC) end - it "responds with status 200" do + it "downloads get status 200" do download(path, env) do |response| expect(response.status).to eq(200) end end + it "uploads get status 401" do + upload(path, env) do |response| + expect(response.status).to eq(401) + end + end + + context "with correct credentials" do + let(:env) { { user: user.username, password: user.password } } + + it "uploads get status 200 (because Git hooks do the real check)" do + upload(path, env) do |response| + expect(response.status).to eq(200) + end + end + + context 'but git-receive-pack is disabled' do + it "responds with status 404" do + allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) + + upload(path, env) do |response| + expect(response.status).to eq(404) + end + end + end + end + context 'but git-upload-pack is disabled' do it "responds with status 404" do allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) @@ -133,13 +159,19 @@ describe 'Git HTTP requests', lib: true do end context "when the user isn't blocked" do - it "responds with status 200" do + it "downloads status 200" do expect(Rack::Attack::Allow2Ban).to receive(:reset) clone_get(path, env) expect(response.status).to eq(200) end + + it "uploads get status 200" do + upload(path, env) do |response| + expect(response.status).to eq(200) + end + end end context "when blank password attempts follow a valid login" do @@ -174,11 +206,17 @@ describe 'Git HTTP requests', lib: true do end context "when the user doesn't have access to the project" do - it "responds with status 404" do + it "downloads get status 404" do download(path, user: user.username, password: user.password) do |response| expect(response.status).to eq(404) end end + + it "uploads get status 200 (because Git hooks do the real check)" do + upload(path, user: user.username, password: user.password) do |response| + expect(response.status).to eq(200) + end + end end end end @@ -196,6 +234,7 @@ describe 'Git HTTP requests', lib: true do end end end + def clone_get(project, options={}) get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password)) end @@ -204,6 +243,14 @@ describe 'Git HTTP requests', lib: true do post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password)) end + def push_get(project, options={}) + get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password)) + end + + def push_post(project, options={}) + post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password)) + end + def download(project, user: nil, password: nil) args = [project, {user: user, password: password}] @@ -214,6 +261,16 @@ describe 'Git HTTP requests', lib: true do yield response end + def upload(project, user: nil, password: nil) + args = [project, {user: user, password: password}] + + push_get *args + yield response + + push_post *args + yield response + end + def auth_env(user, password) if user && password { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } From 1824fb0603c798ec467ea3529570031e7dbb2986 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 5 Apr 2016 15:20:58 +0000 Subject: [PATCH 0015/1431] Fix broken link in CI quickstart docs The space between the [label] and the (link) caused it to be interpreted literally. --- doc/ci/quick_start/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 9aba4326e11..aae9ccae1d3 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -212,8 +212,8 @@ If you want to receive e-mail notifications about the result status of the builds, you should explicitly enable the **Builds Emails** service under your project's settings. -For more information read the [Builds emails service documentation] -(../../project_services/builds_emails.md). +For more information read the +[Builds emails service documentation](../../project_services/builds_emails.md). ## Builds badge From ac4d3dc5ccba32e026250ab48fe7f29bcf4ddd97 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 17:23:16 +0200 Subject: [PATCH 0016/1431] Rubocop --- spec/requests/git_http_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 1fa14cadc0f..5d41d973083 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -252,7 +252,7 @@ describe 'Git HTTP requests', lib: true do end def download(project, user: nil, password: nil) - args = [project, {user: user, password: password}] + args = [project, { user: user, password: password }] clone_get *args yield response @@ -262,7 +262,7 @@ describe 'Git HTTP requests', lib: true do end def upload(project, user: nil, password: nil) - args = [project, {user: user, password: password}] + args = [project, { user: user, password: password }] push_get *args yield response From 6cc6d9730a234c2cc27869f9b9388ab61de9c460 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 17:27:52 +0200 Subject: [PATCH 0017/1431] Delete dead code --- lib/gitlab/backend/grack_auth.rb | 53 +------------------------------- 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index cdcaae8094c..e2363b91265 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -36,10 +36,7 @@ module Grack lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call return lfs_response unless lfs_response.nil? - if project && authorized_request? - # Tell gitlab-workhorse the request is OK, and what the GL_ID is - render_grack_auth_ok - elsif @user.nil? && !@ci + if @user.nil? && !@ci unauthorized else render_not_found @@ -141,36 +138,6 @@ module Grack user end - def authorized_request? - return true if @ci - - case git_cmd - when *Gitlab::GitAccess::DOWNLOAD_COMMANDS - if !Gitlab.config.gitlab_shell.upload_pack - false - elsif user - Gitlab::GitAccess.new(user, project).download_access_check.allowed? - elsif project.public? - # Allow clone/fetch for public projects - true - else - false - end - when *Gitlab::GitAccess::PUSH_COMMANDS - if !Gitlab.config.gitlab_shell.receive_pack - false - elsif user - # Skip user authorization on upload request. - # It will be done by the pre-receive hook in the repository. - true - else - false - end - else - false - end - end - def git_cmd if @request.get? @request.params['service'] @@ -197,24 +164,6 @@ module Grack end end - def render_grack_auth_ok - repo_path = - if @request.path_info =~ /^([\w\.\/-]+)\.wiki\.git/ - ProjectWiki.new(project).repository.path_to_repo - else - project.repository.path_to_repo - end - - [ - 200, - { "Content-Type" => "application/json" }, - [JSON.dump({ - 'GL_ID' => Gitlab::ShellEnv.gl_id(@user), - 'RepoPath' => repo_path, - })] - ] - end - def render_not_found [404, { "Content-Type" => "text/plain" }, ["Not Found"]] end From 91226c200151461b21e85cc8c85a103c93d6a17f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 17:52:12 +0200 Subject: [PATCH 0018/1431] Move workhorse protocol code into lib --- app/controllers/projects/git_http_controller.rb | 13 +++++-------- lib/gitlab/workhorse.rb | 7 +++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index a26ab736115..6dd7a683b0e 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -115,12 +115,12 @@ class Projects::GitHttpController < Projects::ApplicationController end end - def repo_path - @repo_path ||= begin + def repository + @repository ||= begin if params[:project_id].end_with?('.wiki.git') - project.wiki.wiki.path + project.wiki.repository else - repository.path_to_repo + project.repository end end end @@ -142,10 +142,7 @@ class Projects::GitHttpController < Projects::ApplicationController end def render_ok - render json: { - 'GL_ID' => Gitlab::ShellEnv.gl_id(@user), - 'RepoPath' => repo_path, - } + render json: Gitlab::Workhorse.git_http_ok(repository, user) end def render_not_found diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index c3ddd4c2680..5b2982e4994 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -6,6 +6,13 @@ module Gitlab SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data' class << self + def git_http_ok(repository, user) + { + 'GL_ID' => Gitlab::ShellEnv.gl_id(user), + 'RepoPath' => repository.path_to_repo, + } + end + def send_git_blob(repository, blob) params = { 'RepoPath' => repository.path_to_repo, From ccb29955c9d7de69d99fe91425d6246cc723def4 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 18:58:19 +0200 Subject: [PATCH 0019/1431] More tests, better descriptions --- spec/requests/git_http_spec.rb | 41 +++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5d41d973083..8b217684911 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -12,7 +12,7 @@ describe 'Git HTTP requests', lib: true do context "when the project doesn't exist" do context "when no authentication is provided" do - it "responds with status 401" do + it "responds with status 401 (no project existence information leak)" do download('doesnt/exist.git') do |response| expect(response.status).to eq(401) end @@ -72,7 +72,7 @@ describe 'Git HTTP requests', lib: true do expect(response.status).to eq(401) end end - + context "with correct credentials" do let(:env) { { user: user.username, password: user.password } } @@ -81,11 +81,11 @@ describe 'Git HTTP requests', lib: true do expect(response.status).to eq(200) end end - + context 'but git-receive-pack is disabled' do it "responds with status 404" do allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) - + upload(path, env) do |response| expect(response.status).to eq(404) end @@ -110,11 +110,17 @@ describe 'Git HTTP requests', lib: true do end context "when no authentication is provided" do - it "responds with status 401" do + it "responds with status 401 to downloads" do download(path, env) do |response| expect(response.status).to eq(401) end end + + it "responds with status 401 to uploads" do + upload(path, env) do |response| + expect(response.status).to eq(401) + end + end end context "when username and password are provided" do @@ -159,18 +165,18 @@ describe 'Git HTTP requests', lib: true do end context "when the user isn't blocked" do - it "downloads status 200" do + it "downloads get status 200" do expect(Rack::Attack::Allow2Ban).to receive(:reset) clone_get(path, env) expect(response.status).to eq(200) end - + it "uploads get status 200" do upload(path, env) do |response| expect(response.status).to eq(200) - end + end end end @@ -211,7 +217,7 @@ describe 'Git HTTP requests', lib: true do expect(response.status).to eq(404) end end - + it "uploads get status 200 (because Git hooks do the real check)" do upload(path, user: user.username, password: user.password) do |response| expect(response.status).to eq(200) @@ -222,15 +228,24 @@ describe 'Git HTTP requests', lib: true do end context "when a gitlab ci token is provided" do - it "responds with status 200" do - token = "123" - project = FactoryGirl.create :empty_project - project.update_attributes(runners_token: token, builds_enabled: true) + let(:token) { 123 } + let(:project) { FactoryGirl.create :empty_project } + before do + project.update_attributes(runners_token: token, builds_enabled: true) + end + + it "downloads get status 200" do clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token expect(response.status).to eq(200) end + + it "uploads get status 401 (no project existence information leak)" do + push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token + + expect(response.status).to eq(401) + end end end end From ab9dfa8fd681ac558cf988aa2cdb5bd69feea757 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 19:25:47 +0200 Subject: [PATCH 0020/1431] Clarify intentions --- app/controllers/projects/git_http_controller.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 6dd7a683b0e..11e17510cb9 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -108,11 +108,14 @@ class Projects::GitHttpController < Projects::ApplicationController id = params[:project_id] return if id.nil? - if id.end_with?('.wiki.git') - id.slice(0, id.length - 9) - elsif id.end_with?('.git') - id.slice(0, id.length - 4) + %w{.wiki.git .git}.each do |suffix| + # Be careful to only remove the suffix from the end of 'id'. + # Accidentally removing it from the middle is how security + # vulnerabilities happen! + return id.slice(0, id.length - suffix.length) if id.end_with?(suffix) end + + nil end def repository From 35266de2f0e91ac73995ab8ced1bbcb12e35f773 Mon Sep 17 00:00:00 2001 From: Chris McKnight Date: Wed, 6 Jan 2016 11:20:52 -0600 Subject: [PATCH 0021/1431] Updates git lfs initialize command --- doc/workflow/lfs/manage_large_binaries_with_git_lfs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index ba91685a20b..83db44c10b1 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -44,7 +44,7 @@ check it into your Git repository: ```bash git clone git@gitlab.example.com:group/project.git -git lfs init # initialize the Git LFS project project +git lfs install # initialize the Git LFS project project git lfs track "*.iso" # select the file extensions that you want to treat as large files ``` @@ -152,4 +152,4 @@ If you are using OS X you can use `osxkeychain` to store and encrypt your creden For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases). More details about various methods of storing the user credentials can be found -on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file +on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). From e4d9d4e55b67ab04e46ee3f72c5496f91687a205 Mon Sep 17 00:00:00 2001 From: "P.S.V.R" Date: Mon, 11 Apr 2016 16:45:00 +0800 Subject: [PATCH 0022/1431] fix #15127 ActiveJob::DeserializationError thrown send_devise_notification pre-maturely enqueued the task when the user instance has not yet been committed into the database, causing a record-not-found in the other sidekiq process. devise-async has already been taking care of asynchronous mail sending, we just need to run it inside queue `mailers` instead of `mailer` to enable it. --- app/models/user.rb | 5 ----- config/initializers/devise_async.rb | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 2b0bee2099f..531dc9a5aed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -845,11 +845,6 @@ class User < ActiveRecord::Base other.select(:id)]) end - # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration - def send_devise_notification(notification, *args) - devise_mailer.send(notification, self, *args).deliver_later - end - def ensure_external_user_rights return unless self.external? diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb index 05a1852cdbd..fa602cbe554 100644 --- a/config/initializers/devise_async.rb +++ b/config/initializers/devise_async.rb @@ -1 +1,2 @@ Devise::Async.backend = :sidekiq +Devise::Async.queue = :mailers From e84c155f092600b90be291f0f7bb649811fa53fb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 16:16:39 +0200 Subject: [PATCH 0023/1431] WIP --- .../projects/pipelines_controller.rb | 102 ++++++++++++++++++ app/models/ability.rb | 7 +- app/views/layouts/nav/_project.html.haml | 7 ++ .../projects/ci/commits/_commit.html.haml | 73 +++++++++++++ .../ci_commits/_header_title.html.haml | 1 + app/views/projects/ci_commits/index.html.haml | 65 +++++++++++ app/views/projects/ci_commits/new.html.haml | 25 +++++ config/routes.rb | 7 ++ 8 files changed, 286 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index e69de29bb2d..764c8cc9cca 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -0,0 +1,102 @@ +class Projects::PipelineController < Projects::ApplicationController + before_action :ci_commit, except: [:index, :new, :create] + before_action :authorize_read_pipeline! + before_action :authorize_create_pipeline!, only: [:new, :create] + before_action :authorize_update_pipeline!, only: [:retry, :cancel] + layout 'project' + + def index + @scope = params[:scope] + @all_commits = project.ci_commits + @commits = @all_commits.order(id: :desc) + @commits = + case @scope + when 'latest' + @commits + when 'running' + @commits.running_or_pending + when 'branches' + refs = project.repository.branches.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + when 'tags' + refs = project.repository.tags.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + else + @commits + end + @commits = @commits.page(params[:page]).per(30) + end + + def new + end + + def create + ref_names = project.repository.ref_names + unless ref_names.include?(params[:ref]) + @error = 'Reference not found' + render action: 'new' + return + end + + commit = project.commit(params[:ref]) + unless commit + @error = 'Commit not found' + render action: 'new' + return + end + + ci_commit = project.ci_commit(commit.id, params[:ref]) + if ci_commit + @error = 'Pipeline already created' + render action: 'new' + return + end + + # Skip creating ci_commit when no gitlab-ci.yml is found + commit = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) + unless commit.config_processor + @error = commit.yaml_errors || 'Missing .gitlab-ci.yml file' + render action: 'new' + return + end + + Ci::Commit.transaction do + commit.save! + commit.create_builds(params[:ref], false, current_user) + end + + redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id) + end + + def show + @commit = @ci_commit.commit + @builds = @ci_commit.builds + @statuses = @ci_commit.statuses + + respond_to do |format| + format.html + end + end + + def retry + ci_commit.builds.latest.failed.select(&:retryable?).each(&:retry) + + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + def cancel + ci_commit.builds.running_or_pending.each(&:cancel) + + redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) + end + + def retry_builds + end + private + + def ci_commit + @ci_commit ||= project.ci_commits.find_by!(id: params[:id]) + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index c0bf6def7c5..ec5ac54c277 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -195,6 +195,7 @@ class Ability :admin_label, :read_commit_status, :read_build, + :read_pipeline, ] end @@ -206,6 +207,8 @@ class Ability :update_commit_status, :create_build, :update_build, + :create_pipeline, + :update_pipeline, :create_merge_request, :create_wiki, :push_code @@ -234,7 +237,8 @@ class Ability :admin_wiki, :admin_project, :admin_commit_status, - :admin_build + :admin_build, + :admin_pipeline ] end @@ -277,6 +281,7 @@ class Ability unless project.builds_enabled rules += named_abilities('build') + rules += named_abilities('pipeline') end rules diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 86b46e8c75e..fcce1b1dc98 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -39,6 +39,13 @@ Commits - if project_nav_tab? :builds + = nav_link(controller: %w(ci_commits)) do + = link_to project_ci_commits_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do + = icon('ship fw') + %span + Pipelines + %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all)) + = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = icon('cubes fw') diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index e69de29bb2d..29efcc9cfdd 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -0,0 +1,73 @@ +- status = commit.status +%tr.commit + %td.commit-link + = link_to namespace_project_commit_url(@project.namespace, @project, commit), class: "ci-status ci-#{status}" do + = ci_icon_for_status(status) + %strong ##{commit.id} + + %td + %div + - if commit.ref + = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref) +   + - if commit.tag? + %span.label.label-primary tag + - if commit.branch? + %span.label.label-primary branch + - if commit.trigger_requests.any? + %span.label.label-primary triggered + - if commit.yaml_errors.present? + %span.label.label-danger.has-tooltip(title="#{commit.yaml_errors}") yaml invalid + - if commit.builds.any?(&:stuck?) + %span.label.label-warning stuck + + - if commit_data = commit.commit_data + = render 'projects/branches/commit', commit: commit_data, project: @project + - else + %p + Cant find HEAD commit for this branch + + - stages.each do |stage| + %td + - status = commit.statuses.latest.where(stage: stage).status + %span.has-tooltip(title="#{status || "missing"}"){class: "ci-status-icon-#{status || "skipped"}"} + = ci_icon_for_status(status || "missing") + -#- if status + -# = ci_status_with_icon(status) + -#- else + -# = ci_status_with_icon('missing') + + %td + - if commit.started_at && commit.finished_at + %p + #{duration_in_words(commit.finished_at, commit.started_at)} + - if commit.finished_at + %p + #{time_ago_with_tooltip(commit.finished_at)} + + %td.content + .controls.hidden-xs.pull-right + - artifacts = commit.builds.latest.select { |status| status.artifacts? } + - if artifacts.present? + .dropdown.inline + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + = icon('download') + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right + - artifacts.each do |build| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do + %i.fa.fa-download + %span #{build.name} +   + + - if can?(current_user, :update_pipeline, @project) + - if commit.retryable? + = link_to retry_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do + = icon("repeat") + +   + + - if commit.active? + = link_to cancel_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do + = icon("remove cred") diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml index e69de29bb2d..27c125ca40f 100644 --- a/app/views/projects/ci_commits/_header_title.html.haml +++ b/app/views/projects/ci_commits/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Pipelines", project_ci_commits_path(@project)) diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml index e69de29bb2d..0347c220382 100644 --- a/app/views/projects/ci_commits/index.html.haml +++ b/app/views/projects/ci_commits/index.html.haml @@ -0,0 +1,65 @@ +- page_title "Pipelines" += render "header_title" + +.top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to project_ci_commits_path(@project) do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_commits.count(:id)) + + %li{class: ('active' if @scope == 'branches')} + = link_to project_ci_commits_path(@project, scope: :branches) do + Branches + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'tags')} + = link_to project_ci_commits_path(@project, scope: :tags) do + Tags + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'running')} + = link_to project_ci_commits_path(@project, scope: :running) do + Failed + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + .nav-controls + - if can? current_user, :create_pipeline, @project + = link_to new_namespace_project_ci_commit_path(@project.namespace, @project), class: 'btn btn-create' do + = icon('plus') + New + + - if can?(current_user, :update_build, @project) + - unless @repository.gitlab_ci_yml + = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + + = link_to ci_lint_path, class: 'btn btn-default' do + = icon('wrench') + %span CI Lint + +.gray-content-block + Pipelines for #{(@scope || 'changes')} on this project + +%ul.content-list + - stages = @commits.stages + - if @commits.blank? + %li + .nothing-here-block No pipelines to show + - else + .table-holder + %table.table.builds + %tbody + %th Pipeline ID + %th Commit + - @commits.stages.each do |stage| + %th + = stage.titleize + %th + %th + = render @commits.includes(:statuses).includes(:builds), commit_sha: true, stage: true, allow_retry: true, stages: stages + + = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml index e69de29bb2d..e9a22bbb157 100644 --- a/app/views/projects/ci_commits/new.html.haml +++ b/app/views/projects/ci_commits/new.html.haml @@ -0,0 +1,25 @@ +- page_title "New Pipeline" += render "header_title" + +- if @error + .alert.alert-danger + %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + = @error +%h3.page-title + New Pipeline +%hr + += form_tag namespace_project_ci_commits_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do + .form-group + = label_tag :ref, 'Create for', class: 'control-label' + .col-sm-10 + = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control' + .help-block Existing branch name, tag + .form-actions + = button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3 + = link_to 'Cancel', namespace_project_ci_commits_path(@project.namespace, @project), class: 'btn btn-cancel' + +:javascript + var availableRefs = #{@project.repository.ref_names.to_json}; + + new NewBranchForm($('.js-create-branch-form'), availableRefs) diff --git a/config/routes.rb b/config/routes.rb index 842fbb99843..841b3f26272 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -654,6 +654,13 @@ Rails.application.routes.draw do resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] + resources :pipelines, only: [:index, :new, :create] do + member do + post :cancel + post :retry + end + end + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all From 406a796f76824e18f4dca2d29c41dcc3d2e4d457 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 19:57:22 +0200 Subject: [PATCH 0024/1431] Make Pipeline view work --- app/assets/stylesheets/framework/tables.scss | 19 +++++++++++++++++ .../projects/pipelines_controller.rb | 2 +- app/helpers/ci_status_helper.rb | 2 +- app/helpers/gitlab_routing_helper.rb | 2 +- app/models/ci/commit.rb | 4 ++++ app/views/layouts/nav/_project.html.haml | 2 +- .../projects/ci/commits/_commit.html.haml | 21 +++++++++---------- .../ci_commits/_header_title.html.haml | 1 - .../pipelines/_header_title.html.haml | 1 + .../{ci_commits => pipelines}/index.html.haml | 14 ++++++------- .../{ci_commits => pipelines}/new.html.haml | 4 ++-- spec/models/project_spec.rb | 2 +- 12 files changed, 48 insertions(+), 26 deletions(-) delete mode 100644 app/views/projects/ci_commits/_header_title.html.haml create mode 100644 app/views/projects/pipelines/_header_title.html.haml rename app/views/projects/{ci_commits => pipelines}/index.html.haml (76%) rename app/views/projects/{ci_commits => pipelines}/new.html.haml (72%) diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 75b770ae5a2..3a7f5bb932e 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -34,6 +34,25 @@ table { font-weight: normal; font-size: 15px; border-bottom: 1px solid $border-color; + + .rotate { + height: 140px; + white-space: nowrap; + } + + .rotate > div { + transform: + /* Magic Numbers */ + translate(25px, 51px) + /* 45 is really 360 - 45 */ + rotate(315deg); + width: 30px; + } + + .rotate > div > span { + border-bottom: 1px solid #ccc; + padding: 5px 10px; + } } td { diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 764c8cc9cca..a3e72fbdef1 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,4 +1,4 @@ -class Projects::PipelineController < Projects::ApplicationController +class Projects::PipelinesController < Projects::ApplicationController before_action :ci_commit, except: [:index, :new, :create] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index effa7ce77e1..3f7282d0c6c 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -37,7 +37,7 @@ module CiStatusHelper return unless ci_commit.is_a?(Commit) || ci_commit.is_a?(Ci::Commit) link_to ci_icon_for_status(ci_commit.status), - project_ci_commit_path(ci_commit.project, ci_commit), + project_pipeline_path(ci_commit.project, ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", title: "Build #{ci_label_for_status(ci_commit.status)}", data: { toggle: 'tooltip', placement: tooltip_placement } diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f1af8e163cd..ed0db04e069 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -37,7 +37,7 @@ module GitlabRoutingHelper builds_namespace_project_commit_path(project.namespace, project, commit.id) end - def project_ci_commit_path(project, ci_commit) + def project_pipeline_path(project, ci_commit) builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 8865bd76bd2..687654d3c89 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -94,6 +94,10 @@ module Ci end end + def triggered? + trigger_requests.any? + end + def invalidate write_attribute(:status, nil) write_attribute(:started_at, nil) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index fcce1b1dc98..b58d8270230 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -40,7 +40,7 @@ - if project_nav_tab? :builds = nav_link(controller: %w(ci_commits)) do - = link_to project_ci_commits_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do = icon('ship fw') %span Pipelines diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 29efcc9cfdd..7c6ba216386 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -1,7 +1,7 @@ - status = commit.status %tr.commit %td.commit-link - = link_to namespace_project_commit_url(@project.namespace, @project, commit), class: "ci-status ci-#{status}" do + = link_to namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "ci-status ci-#{status}" do = ci_icon_for_status(status) %strong ##{commit.id} @@ -14,7 +14,7 @@ %span.label.label-primary tag - if commit.branch? %span.label.label-primary branch - - if commit.trigger_requests.any? + - if commit.triggered? %span.label.label-primary triggered - if commit.yaml_errors.present? %span.label.label-danger.has-tooltip(title="#{commit.yaml_errors}") yaml invalid @@ -27,22 +27,21 @@ %p Cant find HEAD commit for this branch + - stages_status = commit.statuses.stages_status - stages.each do |stage| %td - - status = commit.statuses.latest.where(stage: stage).status - %span.has-tooltip(title="#{status || "missing"}"){class: "ci-status-icon-#{status || "skipped"}"} - = ci_icon_for_status(status || "missing") - -#- if status - -# = ci_status_with_icon(status) - -#- else - -# = ci_status_with_icon('missing') + - if status = stages_status[stage] + %span.has-tooltip(title="#{status}"){class: "ci-status-icon-#{status}"} + = ci_icon_for_status(status) %td - if commit.started_at && commit.finished_at %p + %i.fa.fa-late-o #{duration_in_words(commit.finished_at, commit.started_at)} - if commit.finished_at %p + %i.fa.fa-date-o #{time_ago_with_tooltip(commit.finished_at)} %td.content @@ -63,11 +62,11 @@ - if can?(current_user, :update_pipeline, @project) - if commit.retryable? - = link_to retry_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do + = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do = icon("repeat")   - if commit.active? - = link_to cancel_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do + = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do = icon("remove cred") diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml deleted file mode 100644 index 27c125ca40f..00000000000 --- a/app/views/projects/ci_commits/_header_title.html.haml +++ /dev/null @@ -1 +0,0 @@ -- header_title project_title(@project, "Pipelines", project_ci_commits_path(@project)) diff --git a/app/views/projects/pipelines/_header_title.html.haml b/app/views/projects/pipelines/_header_title.html.haml new file mode 100644 index 00000000000..faf63d64a79 --- /dev/null +++ b/app/views/projects/pipelines/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Pipelines", project_pipelines_path(@project)) diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/pipelines/index.html.haml similarity index 76% rename from app/views/projects/ci_commits/index.html.haml rename to app/views/projects/pipelines/index.html.haml index 0347c220382..b9877cd37be 100644 --- a/app/views/projects/ci_commits/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -4,32 +4,32 @@ .top-area %ul.nav-links %li{class: ('active' if @scope.nil?)} - = link_to project_ci_commits_path(@project) do + = link_to project_pipelines_path(@project) do All %span.badge.js-totalbuilds-count = number_with_delimiter(@all_commits.count(:id)) %li{class: ('active' if @scope == 'branches')} - = link_to project_ci_commits_path(@project, scope: :branches) do + = link_to project_pipelines_path(@project, scope: :branches) do Branches %span.badge.js-running-count = number_with_delimiter(@all_commits.running_or_pending.count(:id)) %li{class: ('active' if @scope == 'tags')} - = link_to project_ci_commits_path(@project, scope: :tags) do + = link_to project_pipelines_path(@project, scope: :tags) do Tags %span.badge.js-running-count = number_with_delimiter(@all_commits.running_or_pending.count(:id)) %li{class: ('active' if @scope == 'running')} - = link_to project_ci_commits_path(@project, scope: :running) do + = link_to project_pipelines_path(@project, scope: :running) do Failed %span.badge.js-running-count = number_with_delimiter(@all_commits.running_or_pending.count(:id)) .nav-controls - if can? current_user, :create_pipeline, @project - = link_to new_namespace_project_ci_commit_path(@project.namespace, @project), class: 'btn btn-create' do + = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do = icon('plus') New @@ -56,10 +56,10 @@ %th Pipeline ID %th Commit - @commits.stages.each do |stage| - %th + %th.rotate = stage.titleize %th %th - = render @commits.includes(:statuses).includes(:builds), commit_sha: true, stage: true, allow_retry: true, stages: stages + = render @commits, commit_sha: true, stage: true, allow_retry: true, stages: stages = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/pipelines/new.html.haml similarity index 72% rename from app/views/projects/ci_commits/new.html.haml rename to app/views/projects/pipelines/new.html.haml index e9a22bbb157..39b1571b9cf 100644 --- a/app/views/projects/ci_commits/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -9,7 +9,7 @@ New Pipeline %hr -= form_tag namespace_project_ci_commits_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do += form_tag namespace_project_pipelines_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do .form-group = label_tag :ref, 'Create for', class: 'control-label' .col-sm-10 @@ -17,7 +17,7 @@ .help-block Existing branch name, tag .form-actions = button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', namespace_project_ci_commits_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel' :javascript var availableRefs = #{@project.repository.ref_names.to_json}; diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1688e91ca62..59df2c5cb87 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -62,7 +62,7 @@ describe Project, models: true do it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_many(:commit_statuses) } - it { is_expected.to have_many(:ci_commits) } + it { is_expected.to have_many(:pipelines) } it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:runner_projects) } it { is_expected.to have_many(:runners) } From f5d24e60f842096f670593fb4dd0d29c3f5d4fcc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 13:01:08 +0200 Subject: [PATCH 0025/1431] Pipeline view --- app/assets/stylesheets/framework/tables.scss | 3 - .../projects/pipelines_controller.rb | 56 +++++++------------ app/models/ci/commit.rb | 7 +++ app/views/projects/ci/builds/_build.html.haml | 4 +- .../projects/ci/commits/_commit.html.haml | 41 +++++++------- .../projects/commit/_ci_commit.html.haml | 30 +++------- app/views/projects/pipelines/index.html.haml | 33 +++++------ app/views/projects/pipelines/show.html.haml | 3 + config/routes.rb | 2 +- 9 files changed, 79 insertions(+), 100 deletions(-) create mode 100644 app/views/projects/pipelines/show.html.haml diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 3a7f5bb932e..9d6a6c5b237 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -38,9 +38,6 @@ table { .rotate { height: 140px; white-space: nowrap; - } - - .rotate > div { transform: /* Magic Numbers */ translate(25px, 51px) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index a3e72fbdef1..b2ee5573bfc 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,5 +1,5 @@ class Projects::PipelinesController < Projects::ApplicationController - before_action :ci_commit, except: [:index, :new, :create] + before_action :pipeline, except: [:index, :new, :create] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] @@ -7,26 +7,24 @@ class Projects::PipelinesController < Projects::ApplicationController def index @scope = params[:scope] - @all_commits = project.ci_commits - @commits = @all_commits.order(id: :desc) - @commits = + @all_pipelines = project.ci_commits + @pipelines = @all_pipelines.order(id: :desc) + @pipelines = case @scope - when 'latest' - @commits when 'running' - @commits.running_or_pending + @pipelines.running_or_pending when 'branches' - refs = project.repository.branches.map(&:name) - ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') - @commits.where(id: ids) + @branches = project.repository.branches.map(&:name) + @branches_ids = @all_pipelines.where(ref: @branches).group(:ref).select('max(id)') + @pipelines.where(id: @branches_ids) when 'tags' - refs = project.repository.tags.map(&:name) - ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') - @commits.where(id: ids) + @tags = project.repository.tags.map(&:name) + @tags_ids = @all_pipelines.where(ref: @tags).group(:ref).select('max(id)') + @pipelines.where(id: @tags_ids) else - @commits + @pipelines end - @commits = @commits.page(params[:page]).per(30) + @pipelines = @pipelines.page(params[:page]).per(30) end def new @@ -47,56 +45,44 @@ class Projects::PipelinesController < Projects::ApplicationController return end - ci_commit = project.ci_commit(commit.id, params[:ref]) - if ci_commit - @error = 'Pipeline already created' - render action: 'new' - return - end + pipeline = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) # Skip creating ci_commit when no gitlab-ci.yml is found - commit = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA) - unless commit.config_processor - @error = commit.yaml_errors || 'Missing .gitlab-ci.yml file' + unless pipeline.config_processor + @error = pipeline.yaml_errors || 'Missing .gitlab-ci.yml file' render action: 'new' return end Ci::Commit.transaction do commit.save! - commit.create_builds(params[:ref], false, current_user) + commit.create_builds(current_user) end redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id) end def show - @commit = @ci_commit.commit - @builds = @ci_commit.builds - @statuses = @ci_commit.statuses - respond_to do |format| format.html end end def retry - ci_commit.builds.latest.failed.select(&:retryable?).each(&:retry) + pipeline.builds.latest.failed.select(&:retryable?).each(&:retry) redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end def cancel - ci_commit.builds.running_or_pending.each(&:cancel) + pipeline.builds.running_or_pending.each(&:cancel) redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end - def retry_builds - end private - def ci_commit - @ci_commit ||= project.ci_commits.find_by!(id: params[:id]) + def pipeline + @pipeline ||= project.ci_commits.find_by!(id: params[:id]) end end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 687654d3c89..7991b987e35 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -94,6 +94,13 @@ module Ci end end + def latest? + return false unless ref + commit = project.commit(ref) + return false unless commit + commit.sha == sha + end + def triggered? trigger_requests.any? end diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 218d396b898..7ded4828b2f 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -13,7 +13,9 @@ %strong ##{build.id} - if build.stuck? - %i.fa.fa-warning.text-warning + %i.fa.fa-warning.text-warning.has-tooltip(title="Build is stuck. Check runners.") + - if defined?(retried) && retried + %i.fa.fa-warning.has-tooltip(title="Build was retried") - if defined?(commit_sha) && commit_sha %td diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml index 7c6ba216386..32f85cb8f8c 100644 --- a/app/views/projects/ci/commits/_commit.html.haml +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -1,19 +1,21 @@ - status = commit.status %tr.commit %td.commit-link - = link_to namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "ci-status ci-#{status}" do + = link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{status}" do = ci_icon_for_status(status) %strong ##{commit.id} %td - %div + %div.branch-commit - if commit.ref - = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref) + = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace" + · + = link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace"   + - if commit.latest? + %span.label.label-success latest - if commit.tag? %span.label.label-primary tag - - if commit.branch? - %span.label.label-primary branch - if commit.triggered? %span.label.label-primary triggered - if commit.yaml_errors.present? @@ -21,32 +23,36 @@ - if commit.builds.any?(&:stuck?) %span.label.label-warning stuck - - if commit_data = commit.commit_data - = render 'projects/branches/commit', commit: commit_data, project: @project - - else - %p - Cant find HEAD commit for this branch + %p + %span + - if commit_data = commit.commit_data + = link_to_gfm commit_data.title, namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message" + - else + Cant find HEAD commit for this branch + - stages_status = commit.statuses.stages_status - stages.each do |stage| %td - if status = stages_status[stage] - %span.has-tooltip(title="#{status}"){class: "ci-status-icon-#{status}"} + %span.has-tooltip(title="#{stage.titleize}: #{status}"){class: "ci-status-icon-#{status}"} = ci_icon_for_status(status) %td - if commit.started_at && commit.finished_at %p - %i.fa.fa-late-o + %i.fa.fa-clock-o +   #{duration_in_words(commit.finished_at, commit.started_at)} - if commit.finished_at %p - %i.fa.fa-date-o + %i.fa.fa-calendar +   #{time_ago_with_tooltip(commit.finished_at)} - %td.content + %td .controls.hidden-xs.pull-right - - artifacts = commit.builds.latest.select { |status| status.artifacts? } + - artifacts = commit.builds.latest - if artifacts.present? .dropdown.inline %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} @@ -58,15 +64,12 @@ = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do %i.fa.fa-download %span #{build.name} -   - if can?(current_user, :update_pipeline, @project) - - if commit.retryable? + - if commit.retryable? && commit.builds.failed.any? = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do = icon("repeat") -   - - if commit.active? = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do = icon("remove cred") diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 06520e40bd9..582ce61a64a 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -2,12 +2,15 @@ .pull-right - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: 'btn btn-grouped btn-primary', method: :post + = link_to "Retry failed", retry_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: 'btn btn-grouped btn-primary', method: :post - if ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + = link_to "Cancel running", cancel_namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post .oneline + Pipeline + = link_to "##{ci_commit.id}", namespace_project_pipeline_path(@project.namespace, @project, ci_commit.id), class: "monospace" + with = pluralize ci_commit.statuses.count(:id), "build" - if ci_commit.ref for @@ -17,7 +20,7 @@ for commit = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" - if ci_commit.duration > 0 - in + took = time_interval_in_words ci_commit.duration - if ci_commit.yaml_errors.present? @@ -47,23 +50,4 @@ %th - builds = ci_commit.statuses.latest.ordered = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true - -- if ci_commit.retried.any? - .gray-content-block.second-block - Retried builds - - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @project.build_coverage_enabled? - %th Coverage - %th - = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false + = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false, retried: true diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index b9877cd37be..838b2986d4f 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -7,25 +7,21 @@ = link_to project_pipelines_path(@project) do All %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_commits.count(:id)) + = number_with_delimiter(@all_pipelines.count) + + %li{class: ('active' if @scope == 'running')} + = link_to project_pipelines_path(@project, scope: :running) do + Running + %span.badge.js-running-count + = number_with_delimiter(@all_pipelines.running_or_pending.count) %li{class: ('active' if @scope == 'branches')} = link_to project_pipelines_path(@project, scope: :branches) do Branches - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) %li{class: ('active' if @scope == 'tags')} = link_to project_pipelines_path(@project, scope: :tags) do Tags - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to project_pipelines_path(@project, scope: :running) do - Failed - %span.badge.js-running-count - = number_with_delimiter(@all_commits.running_or_pending.count(:id)) .nav-controls - if can? current_user, :create_pipeline, @project @@ -45,8 +41,8 @@ Pipelines for #{(@scope || 'changes')} on this project %ul.content-list - - stages = @commits.stages - - if @commits.blank? + - stages = @pipelines.stages + - if @pipelines.blank? %li .nothing-here-block No pipelines to show - else @@ -55,11 +51,12 @@ %tbody %th Pipeline ID %th Commit - - @commits.stages.each do |stage| - %th.rotate - = stage.titleize + - @pipelines.stages.each do |stage| + %th + %span.has-tooltip(title="#{stage.titleize}") + = truncate(stage.titleize.pluralize, length: 8) %th %th - = render @commits, commit_sha: true, stage: true, allow_retry: true, stages: stages + = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages - = paginate @commits, theme: 'gitlab' + = paginate @pipelines, theme: 'gitlab' diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml new file mode 100644 index 00000000000..9f33e2ad624 --- /dev/null +++ b/app/views/projects/pipelines/show.html.haml @@ -0,0 +1,3 @@ +- page_title "Pipeline" += render "header_title" += render "projects/commit/ci_commit", ci_commit: @pipeline diff --git a/config/routes.rb b/config/routes.rb index 841b3f26272..6384757835a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -654,7 +654,7 @@ Rails.application.routes.draw do resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] - resources :pipelines, only: [:index, :new, :create] do + resources :pipelines, only: [:index, :new, :create, :show] do member do post :cancel post :retry From 410f2b40f2579b2e6a77591157900ce07512ee36 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:39:23 +0200 Subject: [PATCH 0026/1431] Remove unneeded changes --- app/assets/stylesheets/framework/tables.scss | 16 ---------------- app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/commit/_ci_commit.html.haml | 2 +- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 9d6a6c5b237..75b770ae5a2 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -34,22 +34,6 @@ table { font-weight: normal; font-size: 15px; border-bottom: 1px solid $border-color; - - .rotate { - height: 140px; - white-space: nowrap; - transform: - /* Magic Numbers */ - translate(25px, 51px) - /* 45 is really 360 - 45 */ - rotate(315deg); - width: 30px; - } - - .rotate > div > span { - border-bottom: 1px solid #ccc; - padding: 5px 10px; - } } td { diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index b58d8270230..f4797a85bb7 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -39,7 +39,7 @@ Commits - if project_nav_tab? :builds - = nav_link(controller: %w(ci_commits)) do + = nav_link(controller: %w(pipelines)) do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do = icon('ship fw') %span diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 2ec3c809e1c..cf101acbb53 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -20,7 +20,7 @@ for commit = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - if ci_commit.duration > 0 - took + in = time_interval_in_words ci_commit.duration - if ci_commit.yaml_errors.present? From c351b9f599aa1af693435738d2c897cc2a954fe7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:51:52 +0200 Subject: [PATCH 0027/1431] Improve rendered CI statuses --- app/helpers/ci_status_helper.rb | 27 ++++++++++++------- app/views/projects/commits/_commit.html.haml | 2 +- .../projects/issues/_merge_requests.html.haml | 2 +- .../issues/_related_branches.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 2 +- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 417050b4132..acc01b008bf 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -38,15 +38,24 @@ module CiStatusHelper icon(icon_name + ' fw') end - def render_ci_status(ci_commit, tooltip_placement: 'auto left') - # TODO: split this method into - # - render_commit_status - # - render_pipeline_status - link_to ci_icon_for_status(ci_commit.status), - ci_status_path(ci_commit), - class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", - title: "Build #{ci_label_for_status(ci_commit.status)}", - data: { toggle: 'tooltip', placement: tooltip_placement } + def render_commit_status(commit, tooltip_placement: 'auto left') + project = commit.project + path = builds_namespace_project_commit_path(project.namespace, project, commit) + render_status_with_link('commit', commit.status, path, tooltip_placement) + end + + def render_pipeline_status(pipeline, tooltip_placement: 'auto left') + project = pipeline.project + path = namespace_project_pipeline_path(project.namespace, project, pipeline) + render_status_with_link('pipeline', pipeline.status, path, tooltip_placement) + end + + def render_status_with_link(type, status, path, tooltip_placement) + link_to ci_icon_for_status(status), + path, + class: "ci-status-link ci-status-icon-#{status.dasherize}", + title: "#{type.titleize}: #{ci_label_for_status(status)}", + data: { toggle: 'tooltip', placement: tooltip_placement } end def no_runners_for_project?(project) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index f7c8647ac0e..b231b584eab 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -17,7 +17,7 @@ .pull-right - if commit.status - = render_ci_status(commit) + = render_commit_status(commit) = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index d6b38b327ff..e953353567e 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -7,7 +7,7 @@ %li %span.merge-request-ci-status - if merge_request.ci_commit - = render_ci_status(merge_request.ci_commit) + = render_pipeline_status(merge_request.ci_commit) - elsif has_any_ci = icon('blank fw') %span.merge-request-id diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index bdfa0c7009e..5f9d2919982 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -8,7 +8,7 @@ - ci_commit = @project.ci_commit(sha, branch) if sha - if ci_commit %span.related-branch-ci-status - = render_ci_status(ci_commit) + = render_pipeline_status(ci_commit) %span.related-branch-info %strong = link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "label-branch" do diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 391193eed6c..7bfde8b1c57 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -13,7 +13,7 @@ - if merge_request.ci_commit %li - = render_ci_status(merge_request.ci_commit) + = render_pipeline_status(merge_request.ci_commit) - if merge_request.open? && merge_request.broken? %li diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index ab8b022411d..9ef021747a5 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -17,7 +17,7 @@ = project.main_language - if project.commit.try(:status) %span - = render_ci_status(project.commit) + = render_commit_status(project.commit) - if forks %span = icon('code-fork') From cb6f035141d2e7792d9594e5d664d1a305b728cf Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 17:05:17 +0200 Subject: [PATCH 0028/1431] Improve pipeline view --- app/controllers/projects/pipelines_controller.rb | 5 +++++ app/views/projects/commit/_ci_commit.html.haml | 3 +-- app/views/projects/commit/_commit_box.html.haml | 11 +++++++++-- app/views/projects/pipelines/show.html.haml | 6 ++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index b2ee5573bfc..aba64e4a730 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,5 +1,6 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :pipeline, except: [:index, :new, :create] + before_action :commit, only: [:show] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] @@ -85,4 +86,8 @@ class Projects::PipelinesController < Projects::ApplicationController def pipeline @pipeline ||= project.ci_commits.find_by!(id: params[:id]) end + + def commit + @commit ||= @pipeline.commit_data + end end diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index cf101acbb53..782ea341daf 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -14,8 +14,7 @@ = pluralize ci_commit.statuses.count(:id), "build" - if ci_commit.ref for - %span.label.label-info - = ci_commit.ref + = link_to ci_commit.ref, namespace_project_commits_path(@project.namespace, @project, ci_commit.ref), class: "monospace" - if defined?(link_to_commit) && link_to_commit for commit = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 0908e830f83..9cb14b6a90f 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,6 +1,6 @@ .pull-right %div - - if @notes_count > 0 + - if defined?(@notes_count) && @notes_count > 0 %span.btn.disabled.btn-grouped %i.fa.fa-comment = @notes_count @@ -42,7 +42,14 @@ - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @commit.status +- if defined?(pipeline) && pipeline + .pull-right + = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline), class: "ci-status ci-#{pipeline.status}" do + = ci_icon_for_status(pipeline.status) + pipeline: + = ci_label_for_status(pipeline.status) + +- elsif @commit.status .pull-right = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do = ci_icon_for_status(@commit.status) diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 9f33e2ad624..8a2e14d8d87 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -1,3 +1,9 @@ - page_title "Pipeline" + = render "header_title" +.prepend-top-default + - if @commit + = render "projects/commit/commit_box", pipeline: @pipeline + %div.block-connector + = render "projects/commit/ci_commit", ci_commit: @pipeline From 21136baa77369d5990ef5db4af26d688aedc8320 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 20:51:03 +0200 Subject: [PATCH 0029/1431] Update handling of skipped status --- app/models/ci/build.rb | 2 +- app/models/ci/commit.rb | 23 ++++++++--------------- app/models/concerns/ci_status.rb | 10 +++++++++- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 085ecc6951c..c0b334d3600 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -116,7 +116,7 @@ module Ci end def retried? - !self.commit.latest.include?(self) + !self.commit.statuses.latest.include?(self) end def retry diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index e2bf4d62541..00a95dd05be 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -125,16 +125,12 @@ module Ci end end - def latest - statuses.latest - end - def retried @retried ||= (statuses.order(id: :desc) - statuses.latest) end def coverage - coverage_array = latest.map(&:coverage).compact + coverage_array = statuses.latest.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -169,18 +165,15 @@ module Ci private def update_state - reload - self.status = if yaml_errors.present? - 'failed' + statuses.reload + self.status = if yaml_errors.blank? + statuses.latest.status || 'skipped' else - latest.status + 'failed' end - self.started_at = statuses.minimum(:started_at) - self.finished_at = statuses.maximum(:finished_at) - self.duration = begin - duration_array = latest.map(&:duration).compact - duration_array.reduce(:+).to_i - end + self.started_at = statuses.started_at + self.finished_at = statuses.finished_at + self.duration = statuses.latest.duration save end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index fd86d2f7553..8190b2a20c6 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -15,7 +15,7 @@ module CiStatus skipped = all.skipped.select('count(*)').to_sql deduce_status = "(CASE - WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' WHEN (#{builds})=(#{pending}) THEN 'pending' WHEN (#{builds})=(#{canceled}) THEN 'canceled' @@ -35,6 +35,14 @@ module CiStatus duration_array = all.map(&:duration).compact duration_array.reduce(:+).to_i end + + def started_at + all.minimum(:started_at) + end + + def finished_at + all.minimum(:finished_at) + end end included do From e7cea8cd75aa23ad4eb9705ddb0871775d65309b Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 15 Apr 2016 11:22:08 +0200 Subject: [PATCH 0030/1431] Avoid path helper name clash --- app/controllers/projects/application_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 275e94d39ed..817727d7868 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -23,8 +23,8 @@ class Projects::ApplicationController < ApplicationController @project = find_project if @project && can?(current_user, :read_project, @project) - if @project.path_with_namespace != project_path - redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) + if @project.path_with_namespace != path_with_namespace + redirect_to request.original_url.gsub(path_with_namespace, @project.path_with_namespace) end else @project = nil @@ -48,12 +48,12 @@ class Projects::ApplicationController < ApplicationController params[:namespace_id] end - def project_path + def path_with_namespace "#{namespace}/#{id}" end def find_project - Project.find_with_namespace(project_path) + Project.find_with_namespace(path_with_namespace) end def repository From d3541da4ceaa0f5e2051edd2aa59d4275f93f0f8 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 15 Apr 2016 12:40:43 +0200 Subject: [PATCH 0031/1431] Comment and whitespace --- .../projects/git_http_controller.rb | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 11e17510cb9..13af17083bd 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -2,7 +2,18 @@ class Projects::GitHttpController < Projects::ApplicationController skip_before_action :repository before_action :authenticate_user before_action :project_found? - + + # We support two actions (git push and git pull) which use four + # different HTTP requests: + # + # - GET /foo/bar.git/info/refs?service=git-upload-pack (pull) + # - GET /foo/bar.git/info/refs?service=git-receive-pack (push) + # - POST /foo/bar.git/git-upload-pack (pull) + # - POST /foo/bar.git/git-receive-pack" (push) + # + # The Rails routes divide these four requests over three methods: + # info_refs, git_upload_pack, and git_receive_pack. + def git_rpc if upload_pack? && upload_pack_allowed? render_ok @@ -12,7 +23,7 @@ class Projects::GitHttpController < Projects::ApplicationController render_not_found end end - + %i{info_refs git_receive_pack git_upload_pack}.each do |method| alias_method method, :git_rpc end @@ -60,7 +71,7 @@ class Projects::GitHttpController < Projects::ApplicationController token && token.accessible? && User.find_by(id: token.resource_owner_id) end end - + def rate_limit_ip!(login, user) # If the user authenticated successfully, we reset the auth failure count # from Rack::Attack for that IP. A client may attempt to authenticate @@ -95,7 +106,7 @@ class Projects::GitHttpController < Projects::ApplicationController "as #{login} but has been temporarily banned from Git auth" end end - + user end @@ -107,7 +118,7 @@ class Projects::GitHttpController < Projects::ApplicationController def id id = params[:project_id] return if id.nil? - + %w{.wiki.git .git}.each do |suffix| # Be careful to only remove the suffix from the end of 'id'. # Accidentally removing it from the middle is how security @@ -143,11 +154,11 @@ class Projects::GitHttpController < Projects::ApplicationController action_name.gsub('_', '-') end end - + def render_ok render json: Gitlab::Workhorse.git_http_ok(repository, user) end - + def render_not_found render text: 'Not Found', status: :not_found end @@ -155,11 +166,11 @@ class Projects::GitHttpController < Projects::ApplicationController def ci? !!@ci end - + def user @user end - + def upload_pack_allowed? if !Gitlab.config.gitlab_shell.upload_pack false From 03e5873ae52f3c8c0efb7baa7d1a358a7c3e7974 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 19 Apr 2016 19:04:58 +0300 Subject: [PATCH 0032/1431] Fix broken links [ci skip] --- doc/ci/{ => examples}/deployment/README.md | 0 doc/ci/triggers/README.md | 2 +- doc/markdown/markdown.md | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) rename doc/ci/{ => examples}/deployment/README.md (100%) diff --git a/doc/ci/deployment/README.md b/doc/ci/examples/deployment/README.md similarity index 100% rename from doc/ci/deployment/README.md rename to doc/ci/examples/deployment/README.md diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index 9f7c1bfe6a0..1848f6319d8 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -33,7 +33,7 @@ POST /projects/:id/trigger/builds The required parameters are the trigger's `token` and the Git `ref` on which the trigger will be performed. Valid refs are the branch, the tag or the commit -SHA. The `:id` of a project can be found by [querying the API](../api/projects.md) +SHA. The `:id` of a project can be found by [querying the API](../../api/projects.md) or by visiting the **Triggers** page which provides self-explanatory examples. When a rebuild is triggered, the information is exposed in GitLab's UI under diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 4f199b6af6f..3f44a1b4c6c 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -402,7 +402,7 @@ There are two ways to create links, inline-style and reference-style. [I'm a reference-style link][Arbitrary case-insensitive reference text] -[I'm a relative reference to a repository file](LICENSE) +[I'm a relative reference to a repository file](LICENSE)[^1] [You can use numbers for reference-style link definitions][1] @@ -594,3 +594,4 @@ By including colons in the header row, you can align the text within that column [rouge]: http://rouge.jneen.net/ "Rouge website" [redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" +[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com From a6ba8647f919cca5f37f663502186d8b6b7642ec Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 19 Apr 2016 16:00:45 -0400 Subject: [PATCH 0033/1431] Improve uniqueness of field names on the signup form Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/15075 --- app/controllers/registrations_controller.rb | 7 ++++++ app/views/devise/shared/_signup_box.html.haml | 4 ++-- spec/features/signup_spec.rb | 24 +++++++++---------- spec/features/users_spec.rb | 16 ++++++------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index c48175a4c5a..b441b34d0be 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -8,6 +8,13 @@ class RegistrationsController < Devise::RegistrationsController def create if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha + # To avoid duplicate form fields on the login page, the registration form + # names fields using `new_user`, but Devise still wants the params in + # `user`. + if params["new_#{resource_name}"].present? && params[resource_name].blank? + params[resource_name] = params.delete(:"new_#{resource_name}") + end + super else flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code." diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index e5607dacd0d..510215bb8cd 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -6,7 +6,7 @@ .login-heading %h3 Create an account .login-body - = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| .devise-errors = devise_error_messages! %div @@ -16,7 +16,7 @@ %div = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true + = f.password_field :password, class: "form-control bottom", placeholder: "Password", required: true %div - if current_application_settings.recaptcha_enabled = recaptcha_tags diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index 01472743b2a..a9b81733897 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -7,10 +7,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_session_path @@ -25,10 +25,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: existing_user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_registration_path @@ -42,10 +42,10 @@ feature 'Signup', feature: true do visit root_path - fill_in 'user_name', with: user.name - fill_in 'user_username', with: user.username - fill_in 'user_email', with: existing_user.email - fill_in 'user_password_sign_up', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: existing_user.email + fill_in 'new_user_password', with: user.password click_button "Sign up" expect(current_path).to eq user_registration_path diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index c1248162031..cf116040394 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -5,10 +5,10 @@ feature 'Users', feature: true do scenario 'GET /users/sign_in creates a new user account' do visit new_user_session_path - fill_in 'user_name', with: 'Name Surname' - fill_in 'user_username', with: 'Great' - fill_in 'user_email', with: 'name@mail.com' - fill_in 'user_password_sign_up', with: 'password1234' + fill_in 'new_user_name', with: 'Name Surname' + fill_in 'new_user_username', with: 'Great' + fill_in 'new_user_email', with: 'name@mail.com' + fill_in 'new_user_password', with: 'password1234' expect { click_button 'Sign up' }.to change { User.count }.by(1) end @@ -31,10 +31,10 @@ feature 'Users', feature: true do scenario 'Should show one error if email is already taken' do visit new_user_session_path - fill_in 'user_name', with: 'Another user name' - fill_in 'user_username', with: 'anotheruser' - fill_in 'user_email', with: user.email - fill_in 'user_password_sign_up', with: '12341234' + fill_in 'new_user_name', with: 'Another user name' + fill_in 'new_user_username', with: 'anotheruser' + fill_in 'new_user_email', with: user.email + fill_in 'new_user_password', with: '12341234' expect { click_button 'Sign up' }.to change { User.count }.by(0) expect(page).to have_text('Email has already been taken') expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' From 077f9a4eeef3c64c5f3e9cc5df5442c8817ee1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 30 Mar 2016 23:12:34 -0300 Subject: [PATCH 0034/1431] Implementing special GitLab markdown reference for milestones Using the syntax proposed in #13829 [project_reference]%(milestone_id | milestone_name) to get a link to the referred milestone. --- app/models/milestone.rb | 42 +++++++++++++++---- .../filter/milestone_reference_filter.rb | 26 +++++++++++- spec/fixtures/markdown.md.erb | 9 ++-- spec/support/markdown_feature.rb | 6 ++- spec/support/matchers/markdown_matchers.rb | 2 +- 5 files changed, 71 insertions(+), 14 deletions(-) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 986184dd301..39dc8d89614 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -74,8 +74,22 @@ class Milestone < ActiveRecord::Base end end + def self.reference_prefix + '%' + end + def self.reference_pattern - nil + %r{ + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)} + (?: + (?\d+) | # Integer-based milestone ID, or + (? + [A-Za-z0-9_-]+ | # String-based single-word milestone title, or + "[^"]+" # String-based multi-word milestone surrounded in quotes + ) + ) + }x end def self.link_reference_pattern @@ -86,13 +100,15 @@ class Milestone < ActiveRecord::Base self.where('due_date > ?', Time.now).reorder(due_date: :asc).first end - def to_reference(from_project = nil) - escaped_title = self.title.gsub("]", "\\]") + def to_reference(from_project = nil, format: :id) + format_reference = milestone_format_reference(format) + reference = "#{self.class.reference_prefix}#{format_reference}" - h = Gitlab::Routing.url_helpers - url = h.namespace_project_milestone_url(self.project.namespace, self.project, self) - - "[#{escaped_title}](#{url})" + if cross_project_reference?(from_project) + project.to_reference + reference + else + reference + end end def reference_link_text(from_project = nil) @@ -160,4 +176,16 @@ class Milestone < ActiveRecord::Base issues.where(id: ids). update_all(["position = CASE #{conditions} ELSE position END", *pairs]) end + + private + + def milestone_format_reference(format = :id) + raise StandardError, 'Unknown format' unless [:id, :name].include?(format) + + if format == :name && !name.include?('"') + %("#{name}") + else + id + end + end end diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 4cb82178024..2c90fd4d385 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -7,14 +7,36 @@ module Banzai end def find_object(project, id) - project.milestones.find_by(iid: id) + project.milestones.find(id) end - def url_for_object(issue, project) + def references_in(text, pattern = Milestone.reference_pattern) + text.gsub(pattern) do |match| + project = project_from_ref($~[:project]) + params = milestone_params($~[:milestone_id].to_i, $~[:milestone_name]) + milestone = project.milestones.find_by(params) + + if milestone + yield match, milestone.id, $~[:project], $~ + else + match + end + end + end + + def url_for_object(milestone, project) h = Gitlab::Routing.url_helpers h.namespace_project_milestone_url(project.namespace, project, milestone, only_path: context[:only_path]) end + + def milestone_params(id, name) + if name + { name: name.tr('"', '') } + else + { id: id } + end + end end end end diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 1772cc3f6a4..6d3bf810c2c 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -216,10 +216,13 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e #### MilestoneReferenceFilter -- Milestone: <%= milestone.to_reference %> +- Milestone by ID: <%= simple_milestone.to_reference %> +- Milestone by name: <%= Milestone.reference_prefix %><%= simple_milestone.name %> +- Milestone by name in quotes: <%= milestone.to_reference(format: :name) %> - Milestone in another project: <%= xmilestone.to_reference(project) %> -- Ignored in code: `<%= milestone.to_reference %>` -- Link to milestone by URL: [Milestone](<%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>) +- Ignored in code: `<%= simple_milestone.to_reference %>` +- Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link) +- Link to milestone by URL: [Milestone](<%= milestone.to_reference %>) ### Task Lists diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index b87cd6bbca2..7fc6d6fcc5e 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -63,8 +63,12 @@ class MarkdownFeature @label ||= create(:label, name: 'awaiting feedback', project: project) end + def simple_milestone + @simple_milestone ||= create(:milestone, name: 'gfm-milestone', project: project) + end + def milestone - @milestone ||= create(:milestone, project: project) + @milestone ||= create(:milestone, name: 'next goal', project: project) end # Cross-references ----------------------------------------------------------- diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 43cb6ef43f2..492138716af 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -154,7 +154,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-milestone', count: 3) + expect(actual).to have_selector('a.gfm.gfm-milestone', count: 5) end end From 375e83bb57dc0143691cf6ef7277bec494f060f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Thu, 31 Mar 2016 21:54:00 -0300 Subject: [PATCH 0035/1431] Consistently using iid when treating milestones as referrables Also, addint a suffix to the reference text when the milestone is in another project --- app/models/milestone.rb | 25 ++- .../filter/milestone_reference_filter.rb | 19 ++- .../filter/milestone_reference_filter_spec.rb | 159 +++++++++++++++--- 3 files changed, 164 insertions(+), 39 deletions(-) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 39dc8d89614..50fa95d4d4b 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -83,10 +83,10 @@ class Milestone < ActiveRecord::Base (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)} (?: - (?\d+) | # Integer-based milestone ID, or + (?\d+) | # Integer-based milestone iid, or (? - [A-Za-z0-9_-]+ | # String-based single-word milestone title, or - "[^"]+" # String-based multi-word milestone surrounded in quotes + [A-Za-z0-9_-]+ | # String-based single-word milestone title, or + "[^"]+" # String-based multi-word milestone surrounded in quotes ) ) }x @@ -100,7 +100,18 @@ class Milestone < ActiveRecord::Base self.where('due_date > ?', Time.now).reorder(due_date: :asc).first end - def to_reference(from_project = nil, format: :id) + ## + # Returns the String necessary to reference this Milestone in Markdown + # + # format - Symbol format to use (default: :iid, optional: :name) + # + # Examples: + # + # Milestone.first.to_reference # => "%1" + # Milestone.first.to_reference(format: :name) # => "%\"goal\"" + # Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1" + # + def to_reference(from_project = nil, format: :iid) format_reference = milestone_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" @@ -179,13 +190,13 @@ class Milestone < ActiveRecord::Base private - def milestone_format_reference(format = :id) - raise StandardError, 'Unknown format' unless [:id, :name].include?(format) + def milestone_format_reference(format = :iid) + raise StandardError, 'Unknown format' unless [:iid, :name].include?(format) if format == :name && !name.include?('"') %("#{name}") else - id + iid end end end diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 2c90fd4d385..419532717f2 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -7,17 +7,17 @@ module Banzai end def find_object(project, id) - project.milestones.find(id) + project.milestones.find_by(iid: id) end def references_in(text, pattern = Milestone.reference_pattern) text.gsub(pattern) do |match| project = project_from_ref($~[:project]) - params = milestone_params($~[:milestone_id].to_i, $~[:milestone_name]) + params = milestone_params($~[:milestone_iid].to_i, $~[:milestone_name]) milestone = project.milestones.find_by(params) if milestone - yield match, milestone.id, $~[:project], $~ + yield match, milestone.iid, $~[:project], $~ else match end @@ -30,11 +30,20 @@ module Banzai only_path: context[:only_path]) end - def milestone_params(id, name) + def object_link_text(object, matches) + if context[:project] == object.project + super + else + "#{super} in #{escape_once(object.project.name_with_namespace)}". + html_safe + end + end + + def milestone_params(iid, name) if name { name: name.tr('"', '') } else - { id: id } + { iid: iid } end end end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index ebf3d7489b5..26f87286b2c 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -3,8 +3,9 @@ require 'spec_helper' describe Banzai::Filter::MilestoneReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } - let(:milestone) { create(:milestone, project: project) } + let(:project) { create(:project, :public) } + let(:milestone) { create(:milestone, project: project) } + let(:reference) { milestone.to_reference } it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -17,10 +18,111 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do end end - context 'internal reference' do - # Convert the Markdown link to only the URL, since these tests aren't run through the regular Markdown pipeline. - # Milestone reference behavior in the full Markdown pipeline is tested elsewhere. - let(:reference) { milestone.to_reference.gsub(/\[([^\]]+)\]\(([^)]+)\)/, '\2') } + it 'includes default classes' do + doc = reference_filter("Milestone #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone' + end + + it 'includes a data-project attribute' do + doc = reference_filter("Milestone #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-milestone attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-milestone') + expect(link.attr('data-milestone')).to eq milestone.id.to_s + end + + it 'supports an :only_path context' do + doc = reference_filter("Milestone #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls. + namespace_project_milestone_path(project.namespace, project, milestone) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Milestone #{reference}") + expect(result[:references][:milestone]).to eq [milestone] + end + + context 'Integer-based references' do + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_milestone_url(project.namespace, project, milestone) + end + + it 'links with adjacent text' do + doc = reference_filter("Milestone (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{milestone.name}\.\))) + end + + it 'ignores invalid milestone IIDs' do + exp = act = "Milestone #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'String-based single-word references' do + let(:milestone) { create(:milestone, name: 'gfm', project: project) } + let(:reference) { "#{Milestone.reference_prefix}#{milestone.name}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_milestone_url(project.namespace, project, milestone) + expect(doc.text).to eq 'See gfm' + end + + it 'links with adjacent text' do + doc = reference_filter("Milestone (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{milestone.name}\.\))) + end + + it 'ignores invalid milestone names' do + exp = act = "Milestone #{Milestone.reference_prefix}#{milestone.name.reverse}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'String-based multi-word references in quotes' do + let(:milestone) { create(:milestone, name: 'gfm references', project: project) } + let(:reference) { milestone.to_reference(format: :name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_milestone_url(project.namespace, project, milestone) + expect(doc.text).to eq 'See gfm references' + end + + it 'links with adjacent text' do + doc = reference_filter("Milestone (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{milestone.name}\.\))) + end + + it 'ignores invalid milestone names' do + exp = act = %(Milestone #{Milestone.reference_prefix}"#{milestone.name.reverse}") + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'referencing a milestone in a link href' do + let(:reference) { %Q{Milestone} } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -30,29 +132,12 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do end it 'links with adjacent text' do - doc = reference_filter("milestone (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(milestone.title)}<\/a>\.\)/) - end - - it 'includes a title attribute' do - doc = reference_filter("milestone #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Milestone: #{milestone.title}" - end - - it 'escapes the title attribute' do - milestone.update_attribute(:title, %{">whateverMilestone\.\))) end it 'includes a data-project attribute' do - doc = reference_filter("milestone #{reference}") + doc = reference_filter("Milestone #{reference}") link = doc.css('a').first expect(link).to have_attribute('data-project') @@ -68,8 +153,28 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do end it 'adds to the results hash' do - result = reference_pipeline_result("milestone #{reference}") + result = reference_pipeline_result("Milestone #{reference}") expect(result[:references][:milestone]).to eq [milestone] end end + + describe 'cross project milestone references' do + let(:another_project) { create(:empty_project, :public) } + let(:project_name) { another_project.name_with_namespace } + let(:milestone) { create(:milestone, project: another_project) } + let(:reference) { milestone.to_reference(project) } + + let!(:result) { reference_filter("See #{reference}") } + + it 'points to referenced project milestone page' do + expect(result.css('a').first.attr('href')).to eq urls. + namespace_project_milestone_url(another_project.namespace, + another_project, + milestone) + end + + it 'contains cross project content' do + expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_name}" + end + end end From 6d9794d42a7bea1150374c76fd3ce5521a44e58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Mon, 4 Apr 2016 22:20:10 -0300 Subject: [PATCH 0036/1431] Transforming milestones link references to the short reference form --- lib/banzai/filter/milestone_reference_filter.rb | 5 +++++ spec/fixtures/markdown.md.erb | 1 + spec/support/matchers/markdown_matchers.rb | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 419532717f2..556087c4880 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -11,6 +11,11 @@ module Banzai end def references_in(text, pattern = Milestone.reference_pattern) + # We'll handle here the references that follow the `reference_pattern`. + # Other patterns (for example, the link pattern) are handled by the + # default implementation. + return super(text, pattern) if pattern != Milestone.reference_pattern + text.gsub(pattern) do |match| project = project_from_ref($~[:project]) params = milestone_params($~[:milestone_iid].to_i, $~[:milestone_name]) diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 6d3bf810c2c..3e777a5e92b 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -222,6 +222,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Milestone in another project: <%= xmilestone.to_reference(project) %> - Ignored in code: `<%= simple_milestone.to_reference %>` - Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link) +- Milestone by URL: <%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %> - Link to milestone by URL: [Milestone](<%= milestone.to_reference %>) ### Task Lists diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 492138716af..d921f9bb2bc 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -154,7 +154,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-milestone', count: 5) + expect(actual).to have_selector('a.gfm.gfm-milestone', count: 6) end end From 1ff896f2bf5d06d0d772fd0df98bf43edf107373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Mon, 4 Apr 2016 23:09:44 -0300 Subject: [PATCH 0037/1431] Escaping the `object_link_text` on cross project milestone references --- lib/banzai/filter/milestone_reference_filter.rb | 2 +- spec/lib/banzai/filter/milestone_reference_filter_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 556087c4880..aea1abf3b8e 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -39,7 +39,7 @@ module Banzai if context[:project] == object.project super else - "#{super} in #{escape_once(object.project.name_with_namespace)}". + "#{escape_once(super)} in #{escape_once(object.project.name_with_namespace)}". html_safe end end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 26f87286b2c..ac3e6e4e536 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -176,5 +176,11 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do it 'contains cross project content' do expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_name}" end + + it 'escapes the name attribute' do + allow_any_instance_of(Milestone).to receive(:title).and_return(%{">whatever Date: Mon, 4 Apr 2016 23:37:50 -0300 Subject: [PATCH 0038/1431] Implementing autocomplete for GFM milestone references --- .../javascripts/gfm_auto_complete.js.coffee | 19 +++++++++++++++++++ app/controllers/projects_controller.rb | 1 + app/services/projects/autocomplete_service.rb | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 61e3f811e73..54d89ef69a1 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -18,6 +18,10 @@ GitLab.GfmAutoComplete = Issues: template: '
  • ${id} ${title}
  • ' + # Milestones + Milestones: + template: '
  • ${title}
  • ' + # Add GFM auto-completion to all input fields, that accept GFM input. setup: (wrap) -> @input = $('.js-gfm-input') @@ -81,6 +85,19 @@ GitLab.GfmAutoComplete = title: sanitize(i.title) search: "#{i.iid} #{i.title}" + @input.atwho + at: '%' + alias: 'milestones' + searchKey: 'search' + displayTpl: @Milestones.template + insertTpl: '${atwho-at}${id}' + callbacks: + beforeSave: (milestones) -> + $.map milestones, (m) -> + id: m.iid + title: sanitize(m.title) + search: "#{m.title}" + @input.atwho at: '!' alias: 'mergerequests' @@ -105,6 +122,8 @@ GitLab.GfmAutoComplete = @input.atwho 'load', '@', data.members # load issues @input.atwho 'load', 'issues', data.issues + # load milestones + @input.atwho 'load', 'milestones', data.milestones # load merge requests @input.atwho 'load', 'mergerequests', data.mergerequests # load emojis diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3768efe142a..8662de712a1 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -147,6 +147,7 @@ class ProjectsController < Projects::ApplicationController @suggestions = { emojis: AwardEmoji.urls, issues: autocomplete.issues, + milestones: autocomplete.milestones, mergerequests: autocomplete.merge_requests, members: participants } diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index ba50305dbd5..eec38c5c3d8 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -4,6 +4,10 @@ module Projects @project.issues.visible_to_user(current_user).opened.select([:iid, :title]) end + def milestones + @project.milestones.active.select([:iid, :title]) + end + def merge_requests @project.merge_requests.opened.select([:iid, :title]) end From 0ba116a58ed57d760b264c39f241798528f54b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Tue, 5 Apr 2016 21:35:43 -0300 Subject: [PATCH 0039/1431] Matching version-like expressions as `milestone_name`s instead of `milestone_iid`s The changes also account for %2.1. being matched as milestone_name = "2.1" without the word-separating dot. --- app/models/milestone.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 50fa95d4d4b..92c07fd20da 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -79,14 +79,19 @@ class Milestone < ActiveRecord::Base end def self.reference_pattern + # NOTE: The iid pattern only matches when all characters on the expression + # are digits, so it will match %2 but not %2.1 because that's probably a + # milestone name and we want it to be matched as such. %r{ (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)} (?: - (?\d+) | # Integer-based milestone iid, or + (? + \d+(?!\S\w)\b # Integer-based milestone iid, or + ) | (? - [A-Za-z0-9_-]+ | # String-based single-word milestone title, or - "[^"]+" # String-based multi-word milestone surrounded in quotes + [^"\s]+\b | # String-based single-word milestone title, or + "[^"]+" # String-based multi-word milestone surrounded in quotes ) ) }x From 4596190a2d1eb6d575b52ff889a20c49fbd8ca2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Tue, 5 Apr 2016 21:40:40 -0300 Subject: [PATCH 0040/1431] Inserting Milestone titles insted of IIDs with GFM auto complete --- app/assets/javascripts/gfm_auto_complete.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 54d89ef69a1..0f2c5a6241c 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -90,7 +90,7 @@ GitLab.GfmAutoComplete = alias: 'milestones' searchKey: 'search' displayTpl: @Milestones.template - insertTpl: '${atwho-at}${id}' + insertTpl: '${atwho-at}${title}' callbacks: beforeSave: (milestones) -> $.map milestones, (m) -> From ec71edfeddc403df5dcff1300e3f4868554c5f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Tue, 5 Apr 2016 21:43:26 -0300 Subject: [PATCH 0041/1431] Sorting Milestones on the auto complete list by due date and title --- app/services/projects/autocomplete_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index eec38c5c3d8..eb73948006e 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -5,7 +5,7 @@ module Projects end def milestones - @project.milestones.active.select([:iid, :title]) + @project.milestones.active.reorder(due_date: :asc, title: :asc).select([:iid, :title]) end def merge_requests From 0f925714d04a4d2e86db3a752fc8c1fc45da2214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 6 Apr 2016 20:35:02 -0300 Subject: [PATCH 0042/1431] Inserting the Milestone title between quotes on GFM auto complete This is due to the fact that for multiple word titles it might be an invalid reference without the quotes --- app/assets/javascripts/gfm_auto_complete.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 0f2c5a6241c..41dba342107 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -90,7 +90,7 @@ GitLab.GfmAutoComplete = alias: 'milestones' searchKey: 'search' displayTpl: @Milestones.template - insertTpl: '${atwho-at}${title}' + insertTpl: '${atwho-at}"${title}"' callbacks: beforeSave: (milestones) -> $.map milestones, (m) -> From 30d1d47d1da729319a3e71bd5599c473fc926565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 6 Apr 2016 21:37:56 -0300 Subject: [PATCH 0043/1431] Using project `path_with_namespace` in milestone's cross project references link text --- lib/banzai/filter/milestone_reference_filter.rb | 2 +- spec/lib/banzai/filter/milestone_reference_filter_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index aea1abf3b8e..746e768061c 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -39,7 +39,7 @@ module Banzai if context[:project] == object.project super else - "#{escape_once(super)} in #{escape_once(object.project.name_with_namespace)}". + "#{escape_once(super)} in #{escape_once(object.project.path_with_namespace)}". html_safe end end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index ac3e6e4e536..bdf48eabb0e 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -160,7 +160,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do describe 'cross project milestone references' do let(:another_project) { create(:empty_project, :public) } - let(:project_name) { another_project.name_with_namespace } + let(:project_path) { another_project.path_with_namespace } let(:milestone) { create(:milestone, project: another_project) } let(:reference) { milestone.to_reference(project) } @@ -174,13 +174,13 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do end it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_name}" + expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_path}" end it 'escapes the name attribute' do allow_any_instance_of(Milestone).to receive(:title).and_return(%{">
    whatever Date: Fri, 8 Apr 2016 23:03:23 -0300 Subject: [PATCH 0044/1431] Include Milestone reference syntax in Markdown documentation --- doc/markdown/markdown.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 4f199b6af6f..1afa1f14067 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -185,20 +185,23 @@ GFM will turn that reference into a link so you can navigate between them easily GFM will recognize the following: -| input | references | -|:-----------------------|:---------------------------| -| `@user_name` | specific user | -| `@group_name` | specific group | -| `@all` | entire team | -| `#123` | issue | -| `!123` | merge request | -| `$123` | snippet | -| `~123` | label by ID | -| `~bug` | one-word label by name | -| `~"feature request"` | multi-word label by name | -| `9ba12248` | specific commit | -| `9ba12248...b19a04f5` | commit range comparison | -| `[README](doc/README)` | repository file references | +| input | references | +|:-----------------------|:--------------------------- | +| `@user_name` | specific user | +| `@group_name` | specific group | +| `@all` | entire team | +| `#123` | issue | +| `!123` | merge request | +| `$123` | snippet | +| `~123` | label by ID | +| `~bug` | one-word label by name | +| `~"feature request"` | multi-word label by name | +| `%123` | milestone by ID | +| `%v1.23` | one-word milestone by name | +| `%"release candidate"` | multi-word milestone by name | +| `9ba12248` | specific commit | +| `9ba12248...b19a04f5` | commit range comparison | +| `[README](doc/README)` | repository file references | GFM also recognizes certain cross-project references: @@ -206,6 +209,7 @@ GFM also recognizes certain cross-project references: |:----------------------------------------|:------------------------| | `namespace/project#123` | issue | | `namespace/project!123` | merge request | +| `namespace/project%123` | milestone | | `namespace/project$123` | snippet | | `namespace/project@9ba12248` | specific commit | | `namespace/project@9ba12248...b19a04f5` | commit range comparison | From 7910853368970292eb243ee34072c7f527fa67f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Tue, 19 Apr 2016 22:20:43 -0300 Subject: [PATCH 0045/1431] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d4b8a509261..b35cd9585dc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -112,6 +112,7 @@ v 8.7.0 (unreleased) - Fix GitHub project's link in the import page when provider has a custom URL - Add RAW build trace output and button on build page - Add incremental build trace update into CI API + - Implement GFM references for milestones (Alejandro RodrĂ­guez) v 8.6.7 - Fix persistent XSS vulnerability in `commit_person_link` helper From ff1e7474ed0f210df004c714e1b83c1c2eb0d91c Mon Sep 17 00:00:00 2001 From: Andrew Collett Date: Thu, 21 Apr 2016 10:30:27 +0000 Subject: [PATCH 0046/1431] Update cas.md to reflect the current syntax, and added that gitlab-ctl reconfigure should be run. --- doc/integration/cas.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/integration/cas.md b/doc/integration/cas.md index e6b2071f193..e34e306f9ac 100644 --- a/doc/integration/cas.md +++ b/doc/integration/cas.md @@ -27,17 +27,18 @@ To enable the CAS OmniAuth provider you must register your application with your ```ruby gitlab_rails['omniauth_providers'] = [ { - name: "cas3", - label: "cas", - args: { - url: 'CAS_SERVER', - login_url: '/CAS_PATH/login', - service_validate_url: '/CAS_PATH/p3/serviceValidate', - logout_url: '/CAS_PATH/logout'} } - } + "name"=> "cas3", + "label"=> "cas", + "args"=> { + "url"=> 'CAS_SERVER', + "login_url"=> '/CAS_PATH/login', + "service_validate_url"=> '/CAS_PATH/p3/serviceValidate', + "logout_url"=> '/CAS_PATH/logout' + } } ] ``` + For installations from source: @@ -57,6 +58,8 @@ To enable the CAS OmniAuth provider you must register your application with your 1. Save the configuration file. +1. Run `gitlab-ctl reconfigure` for the omnibus package. + 1. Restart GitLab for the changes to take effect. On the sign in page there should now be a CAS tab in the sign in form. From 9add3fbb3346460934d5990ede1b3216c03e62ee Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 22 Apr 2016 13:24:53 +0200 Subject: [PATCH 0047/1431] =?UTF-8?q?Some=20changes=20after=20review=20fro?= =?UTF-8?q?m=20R=C3=A9my=20and=20Valery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../projects/git_http_controller.rb | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 13af17083bd..cd8dd610bcd 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,20 +1,11 @@ class Projects::GitHttpController < Projects::ApplicationController skip_before_action :repository before_action :authenticate_user - before_action :project_found? + before_action :ensure_project_found? - # We support two actions (git push and git pull) which use four - # different HTTP requests: - # - # - GET /foo/bar.git/info/refs?service=git-upload-pack (pull) - # - GET /foo/bar.git/info/refs?service=git-receive-pack (push) - # - POST /foo/bar.git/git-upload-pack (pull) - # - POST /foo/bar.git/git-receive-pack" (push) - # - # The Rails routes divide these four requests over three methods: - # info_refs, git_upload_pack, and git_receive_pack. - - def git_rpc + # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) + # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) + def info_refs if upload_pack? && upload_pack_allowed? render_ok elsif receive_pack? && receive_pack_allowed? @@ -24,8 +15,22 @@ class Projects::GitHttpController < Projects::ApplicationController end end - %i{info_refs git_receive_pack git_upload_pack}.each do |method| - alias_method method, :git_rpc + # POST /foo/bar.git/git-upload-pack (git pull) + def git_upload_pack + if upload_pack? && upload_pack_allowed? + render_ok + else + render_not_found + end + end + + # POST /foo/bar.git/git-receive-pack" (git push) + def git_receive_pack + if receive_pack? && receive_pack_allowed? + render_ok + else + render_not_found + end end private @@ -34,7 +39,7 @@ class Projects::GitHttpController < Projects::ApplicationController return if project && project.public? && upload_pack? authenticate_or_request_with_http_basic do |login, password| - return @ci = true if ci_request?(login, password) + return @ci = true if valid_ci_request?(login, password) @user = Gitlab::Auth.new.find(login, password) @user ||= oauth_access_token_check(login, password) @@ -42,19 +47,21 @@ class Projects::GitHttpController < Projects::ApplicationController end end - def project_found? + def ensure_project_found? render_not_found if project.blank? end - def ci_request?(login, password) - matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) + def valid_ci_request?(login, password) + matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) if project && matched_login.present? && upload_pack? - underscored_service = matched_login['s'].underscore + underscored_service = matched_login['service'].underscore if underscored_service == 'gitlab_ci' return project && project.valid_build_token?(password) elsif Service.available_services_names.include?(underscored_service) + # We treat underscored_service as a trusted input because it is included + # in the Service.available_services_names whitelist. service_method = "#{underscored_service}_service" service = project.send(service_method) @@ -126,6 +133,7 @@ class Projects::GitHttpController < Projects::ApplicationController return id.slice(0, id.length - suffix.length) if id.end_with?(suffix) end + # No valid id was found. nil end @@ -140,14 +148,14 @@ class Projects::GitHttpController < Projects::ApplicationController end def upload_pack? - rpc == 'git-upload-pack' + git_command == 'git-upload-pack' end def receive_pack? - rpc == 'git-receive-pack' + git_command == 'git-receive-pack' end - def rpc + def git_command if action_name == 'info_refs' params[:service] else @@ -178,11 +186,8 @@ class Projects::GitHttpController < Projects::ApplicationController true elsif user Gitlab::GitAccess.new(user, project).download_access_check.allowed? - elsif project.public? - # Allow clone/fetch for public projects - true else - false + project.public? end end From c161065e781a2c6d7a3b22954259809ffd7c5b26 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 22 Apr 2016 13:58:40 +0200 Subject: [PATCH 0048/1431] Don't mess up our parent controller --- .../projects/application_controller.rb | 26 ++++----------- .../projects/git_http_controller.rb | 32 ++++++++++++------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 817727d7868..74150ad606b 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -10,6 +10,9 @@ class Projects::ApplicationController < ApplicationController def project unless @project + namespace = params[:namespace_id] + id = params[:project_id] || params[:id] + # Redirect from # localhost/group/project.git # to @@ -20,11 +23,12 @@ class Projects::ApplicationController < ApplicationController return end - @project = find_project + project_path = "#{namespace}/#{id}" + @project = Project.find_with_namespace(project_path) if @project && can?(current_user, :read_project, @project) - if @project.path_with_namespace != path_with_namespace - redirect_to request.original_url.gsub(path_with_namespace, @project.path_with_namespace) + if @project.path_with_namespace != project_path + redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) end else @project = nil @@ -40,22 +44,6 @@ class Projects::ApplicationController < ApplicationController @project end - def id - params[:project_id] || params[:id] - end - - def namespace - params[:namespace_id] - end - - def path_with_namespace - "#{namespace}/#{id}" - end - - def find_project - Project.find_with_namespace(path_with_namespace) - end - def repository @repository ||= project.repository end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index cd8dd610bcd..e38552218ec 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -119,27 +119,37 @@ class Projects::GitHttpController < Projects::ApplicationController def project return @project if defined?(@project) - @project = find_project + + project_id, _ = project_id_with_suffix + if project_id.blank? + @project = nil + else + @project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}") + end end - def id - id = params[:project_id] - return if id.nil? + # This method returns two values so that we can parse + # params[:project_id] (untrusted input!) in exactly one place. + def project_id_with_suffix + id = params[:project_id] || '' %w{.wiki.git .git}.each do |suffix| - # Be careful to only remove the suffix from the end of 'id'. - # Accidentally removing it from the middle is how security - # vulnerabilities happen! - return id.slice(0, id.length - suffix.length) if id.end_with?(suffix) + if id.end_with?(suffix) + # Be careful to only remove the suffix from the end of 'id'. + # Accidentally removing it from the middle is how security + # vulnerabilities happen! + return [id.slice(0, id.length - suffix.length), suffix] + end end - # No valid id was found. - nil + # Something is wrong with params[:project_id]; do not pass it on. + [nil, nil] end def repository @repository ||= begin - if params[:project_id].end_with?('.wiki.git') + _, suffix = project_id_with_suffix + if suffix == '.wiki.git' project.wiki.repository else project.repository From b64cbaccbe297c82b5af0dac94b491f86b17ddd3 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 22 Apr 2016 14:04:36 +0200 Subject: [PATCH 0049/1431] Remove trivial 'let' --- spec/requests/git_http_spec.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 8b217684911..20c7357cba5 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -54,7 +54,6 @@ describe 'Git HTTP requests', lib: true do context "when the project exists" do let(:path) { "#{project.path_with_namespace}.git" } - let(:env) { {} } context "when the project is public" do before do @@ -62,13 +61,13 @@ describe 'Git HTTP requests', lib: true do end it "downloads get status 200" do - download(path, env) do |response| + download(path, {}) do |response| expect(response.status).to eq(200) end end it "uploads get status 401" do - upload(path, env) do |response| + upload(path, {}) do |response| expect(response.status).to eq(401) end end @@ -97,7 +96,7 @@ describe 'Git HTTP requests', lib: true do it "responds with status 404" do allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) - download(path, env) do |response| + download(path, {}) do |response| expect(response.status).to eq(404) end end @@ -111,13 +110,13 @@ describe 'Git HTTP requests', lib: true do context "when no authentication is provided" do it "responds with status 401 to downloads" do - download(path, env) do |response| + download(path, {}) do |response| expect(response.status).to eq(401) end end it "responds with status 401 to uploads" do - upload(path, env) do |response| + upload(path, {}) do |response| expect(response.status).to eq(401) end end From ab1734f9e1e3f07482185c8a4cb168be463fcff5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 23 Apr 2016 12:27:29 +0200 Subject: [PATCH 0050/1431] Move changelog item --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f81435805d3..b1df9145d93 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.8.0 (unreleased) + - Implement GFM references for milestones (Alejandro RodrĂ­guez) v 8.7.1 (unreleased) - Use the `can?` helper instead of `current_user.can?` @@ -121,7 +122,6 @@ v 8.7.0 - Fix GitHub project's link in the import page when provider has a custom URL - Add RAW build trace output and button on build page - Add incremental build trace update into CI API - - Implement GFM references for milestones (Alejandro RodrĂ­guez) v 8.6.7 - Fix persistent XSS vulnerability in `commit_person_link` helper From 715959e58190eca661ea377b949af3515d8da913 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 23 Apr 2016 12:34:09 +0200 Subject: [PATCH 0051/1431] Fix cross-project milestone ref with invalid project --- .../filter/milestone_reference_filter.rb | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 746e768061c..dad0768f51b 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -17,9 +17,7 @@ module Banzai return super(text, pattern) if pattern != Milestone.reference_pattern text.gsub(pattern) do |match| - project = project_from_ref($~[:project]) - params = milestone_params($~[:milestone_iid].to_i, $~[:milestone_name]) - milestone = project.milestones.find_by(params) + milestone = find_milestone($~[:project], $~[:milestone_iid], $~[:milestone_name]) if milestone yield match, milestone.iid, $~[:project], $~ @@ -29,6 +27,22 @@ module Banzai end end + def find_milestone(project_ref, milestone_id, milestone_name) + project = project_from_ref(project_ref) + return unless project + + milestone_params = milestone_params(milestone_id, milestone_name) + project.milestones.find_by(milestone_params) + end + + def milestone_params(iid, name) + if name + { name: name.tr('"', '') } + else + { iid: iid.to_i } + end + end + def url_for_object(milestone, project) h = Gitlab::Routing.url_helpers h.namespace_project_milestone_url(project.namespace, project, milestone, @@ -43,14 +57,6 @@ module Banzai html_safe end end - - def milestone_params(iid, name) - if name - { name: name.tr('"', '') } - else - { iid: iid } - end - end end end end From d698d3e846c83f49cd363291dd811220c338c8e9 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 25 Apr 2016 18:05:05 +0200 Subject: [PATCH 0052/1431] =?UTF-8?q?More=20changes=20suggested=20by=20R?= =?UTF-8?q?=C3=A9my?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../projects/git_http_controller.rb | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index e38552218ec..fafd9e445b5 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,7 +1,9 @@ class Projects::GitHttpController < Projects::ApplicationController + attr_reader :user + skip_before_action :repository before_action :authenticate_user - before_action :ensure_project_found? + before_action :ensure_project_found! # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) @@ -47,29 +49,29 @@ class Projects::GitHttpController < Projects::ApplicationController end end - def ensure_project_found? + def ensure_project_found! render_not_found if project.blank? end def valid_ci_request?(login, password) matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) - if project && matched_login.present? && upload_pack? - underscored_service = matched_login['service'].underscore - - if underscored_service == 'gitlab_ci' - return project && project.valid_build_token?(password) - elsif Service.available_services_names.include?(underscored_service) - # We treat underscored_service as a trusted input because it is included - # in the Service.available_services_names whitelist. - service_method = "#{underscored_service}_service" - service = project.send(service_method) - - return service && service.activated? && service.valid_token?(password) - end + unless project && matched_login.present? && upload_pack? + return false end - false + underscored_service = matched_login['service'].underscore + + if underscored_service == 'gitlab_ci' + project && project.valid_build_token?(password) + elsif Service.available_services_names.include?(underscored_service) + # We treat underscored_service as a trusted input because it is included + # in the Service.available_services_names whitelist. + service_method = "#{underscored_service}_service" + service = project.send(service_method) + + service && service.activated? && service.valid_token?(password) + end end def oauth_access_token_check(login, password) @@ -185,10 +187,6 @@ class Projects::GitHttpController < Projects::ApplicationController !!@ci end - def user - @user - end - def upload_pack_allowed? if !Gitlab.config.gitlab_shell.upload_pack false From c6f19aed51736e5945283a611eae09f32a9b5aeb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 26 Apr 2016 16:01:00 +0200 Subject: [PATCH 0053/1431] Fix builds rendering bug --- app/views/projects/commit/_ci_commit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 782ea341daf..21a30080868 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -18,7 +18,7 @@ - if defined?(link_to_commit) && link_to_commit for commit = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - - if ci_commit.duration > 0 + - if ci_commit.duration in = time_interval_in_words ci_commit.duration From 9f85b7bc58485831a61da0f9acc530511ea7474a Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 26 Apr 2016 16:56:14 +0200 Subject: [PATCH 0054/1431] Initialize wikis on legacy projects during check --- .../single_repository_worker.rb | 34 +++++++++++++------ .../single_repository_worker_spec.rb | 33 +++++++++++++++--- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb index a76729e3c74..f2d12ba5a7d 100644 --- a/app/workers/repository_check/single_repository_worker.rb +++ b/app/workers/repository_check/single_repository_worker.rb @@ -1,9 +1,9 @@ module RepositoryCheck class SingleRepositoryWorker include Sidekiq::Worker - + sidekiq_options retry: false - + def perform(project_id) project = Project.find(project_id) project.update_columns( @@ -11,20 +11,32 @@ module RepositoryCheck last_repository_check_at: Time.now, ) end - + private - + def check(project) - repositories = [project.repository] - repositories << project.wiki.repository if project.wiki_enabled? - # Use 'map do', not 'all? do', to prevent short-circuiting - repositories.map { |repository| git_fsck(repository.path_to_repo) }.all? + if !git_fsck(project.repository) + false + elsif project.wiki_enabled? + # Historically some projects never had their wiki repos initialized; + # this happens on project creation now. Let's initialize an empty repo + # if it is not already there. + begin + project.create_wiki + rescue Rugged::RepositoryError + end + + git_fsck(project.wiki.repository) + else + true + end end - - def git_fsck(path) + + def git_fsck(repository) + path = repository.path_to_repo cmd = %W(nice git --git-dir=#{path} fsck) output, status = Gitlab::Popen.popen(cmd) - + if status.zero? true else diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index 087e4c667d8..5a03bb77ebd 100644 --- a/spec/workers/repository_check/single_repository_worker_spec.rb +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -12,7 +12,7 @@ describe RepositoryCheck::SingleRepositoryWorker do subject.perform(project.id) expect(project.reload.last_repository_check_failed).to eq(false) - destroy_wiki(project) + break_wiki(project) subject.perform(project.id) expect(project.reload.last_repository_check_failed).to eq(true) @@ -20,15 +20,38 @@ describe RepositoryCheck::SingleRepositoryWorker do it 'skips wikis when disabled' do project = create(:project_empty_repo, wiki_enabled: false) - # Make sure the test would fail if it checked the wiki repo - destroy_wiki(project) + # Make sure the test would fail if the wiki repo was checked + break_wiki(project) subject.perform(project.id) expect(project.reload.last_repository_check_failed).to eq(false) end - def destroy_wiki(project) - FileUtils.rm_rf(project.wiki.repository.path_to_repo) + it 'creates missing wikis' do + project = create(:project_empty_repo, wiki_enabled: true) + FileUtils.rm_rf(wiki_path(project)) + + subject.perform(project.id) + + expect(project.reload.last_repository_check_failed).to eq(false) + end + + it 'does not create a wiki if the main repo does not exist at all' do + project = create(:project_empty_repo) + FileUtils.rm_rf(project.repository.path_to_repo) + FileUtils.rm_rf(wiki_path(project)) + + subject.perform(project.id) + + expect(File.exist?(wiki_path(project))).to eq(false) + end + + def break_wiki(project) + FileUtils.rm_rf(wiki_path(project) + '/objects') + end + + def wiki_path(project) + project.wiki.repository.path_to_repo end end From 247ae960552acc8cd3be299dbb10ed61d8dafe75 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Tue, 26 Apr 2016 23:20:19 +0700 Subject: [PATCH 0055/1431] Allow to assign labels and milestone to target project when moving issue --- app/services/issues/move_service.rb | 11 ++++++++++- spec/services/issues/move_service_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index 82e7090f1ea..8d41ea5df55 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -41,7 +41,8 @@ module Issues private def create_new_issue - new_params = { id: nil, iid: nil, label_ids: [], milestone: nil, + new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids, + milestone: cloneable_milestone_id, project: @new_project, author: @old_issue.author, description: rewrite_content(@old_issue.description) } @@ -49,6 +50,14 @@ module Issues CreateService.new(@new_project, @current_user, new_params).execute end + def cloneable_label_ids + @new_project.labels.where(title: @old_issue.labels.pluck(:title)).pluck(:id) + end + + def cloneable_milestone_id + @new_project.milestones.find_by(title: @old_issue.milestone.try(:title)) + end + def rewrite_notes @old_issue.notes.find_each do |note| new_note = note.dup diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 2a5e4ac3ec4..c98e60d76b3 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -21,6 +21,15 @@ describe Issues::MoveService, services: true do before do old_project.team << [user, :reporter] new_project.team << [user, :reporter] + + create(:milestone, project_id: old_project.id, title: 'v1.0') + create(:milestone, project_id: new_project.id, title: 'v1.0') + + old_issue.labels << create(:label, project_id: old_project.id, title: 'label1') + old_issue.labels << create(:label, project_id: old_project.id, title: 'label2') + + new_project.labels << create(:label, title: 'label1') + new_project.labels << create(:label, title: 'label2') end end @@ -39,6 +48,17 @@ describe Issues::MoveService, services: true do expect(new_issue.project).to eq new_project end + it 'assigns milestone to new issue' do + expect(new_issue.reload.milestone.title).to eq 'v1.0' + end + + it 'assign labels to new issue' do + expected_label_titles = new_issue.reload.labels.map(&:title) + expect(expected_label_titles).to include 'label1' + expect(expected_label_titles).to include 'label2' + expect(expected_label_titles.size).to eq 2 + end + it 'rewrites issue title' do expect(new_issue.title).to eq title end From d6d1daf430bbadef953b40975a503e0bb64044fc Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Wed, 27 Apr 2016 00:03:09 +0700 Subject: [PATCH 0056/1431] Update specs --- spec/services/issues/move_service_spec.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index c98e60d76b3..7654b255697 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -7,10 +7,11 @@ describe Issues::MoveService, services: true do let(:description) { 'Some issue description' } let(:old_project) { create(:project) } let(:new_project) { create(:project) } + let!(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') } let(:old_issue) do create(:issue, title: title, description: description, - project: old_project, author: author) + project: old_project, author: author, milestone: milestone1) end let(:move_service) do @@ -22,8 +23,7 @@ describe Issues::MoveService, services: true do old_project.team << [user, :reporter] new_project.team << [user, :reporter] - create(:milestone, project_id: old_project.id, title: 'v1.0') - create(:milestone, project_id: new_project.id, title: 'v1.0') + create(:milestone, project_id: new_project.id, title: 'v9.0') old_issue.labels << create(:label, project_id: old_project.id, title: 'label1') old_issue.labels << create(:label, project_id: old_project.id, title: 'label2') @@ -49,7 +49,7 @@ describe Issues::MoveService, services: true do end it 'assigns milestone to new issue' do - expect(new_issue.reload.milestone.title).to eq 'v1.0' + expect(new_issue.reload.milestone.title).to eq 'v9.0' end it 'assign labels to new issue' do @@ -92,11 +92,6 @@ describe Issues::MoveService, services: true do expect(new_issue.author).to eq author end - it 'removes data that is invalid in new context' do - expect(new_issue.milestone).to be_nil - expect(new_issue.labels).to be_empty - end - it 'creates a new internal id for issue' do expect(new_issue.iid).to be 1 end From 0ace42cdb90161ee7daf69b7a6a7af6ce4195208 Mon Sep 17 00:00:00 2001 From: Jason Roehm Date: Tue, 26 Apr 2016 21:41:52 -0400 Subject: [PATCH 0057/1431] fix: in recent versions of Docker, the /.dockerinit file doesn't exist; use /.dockerenv instead [ci skip] Signed-off-by: Jason Roehm --- doc/ci/ssh_keys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 7f825e6a065..7c0fb225dac 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -57,7 +57,7 @@ before_script: # WARNING: Use this only with the Docker executor, if you use it with shell # you will overwrite your user's SSH config. - mkdir -p ~/.ssh - - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' ``` As a final step, add the _public_ key from the one you created earlier to the From ce9310d63f6ddf55187d16b882d59f26f7b384a9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 27 Apr 2016 09:28:08 +0200 Subject: [PATCH 0058/1431] fix var error --- lib/gitlab/bitbucket_import/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index 9bb507b5edd..9b83292ef33 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -12,7 +12,7 @@ module Gitlab token_secret = import_data_credentials[:bb_session][:bitbucket_access_token_secret] new(token, token_secret) else - raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{project.id}" end end From 6b2489f2007e3ffe890f707d824a915f97ed7e61 Mon Sep 17 00:00:00 2001 From: Karlo Soriano Date: Wed, 27 Apr 2016 17:45:50 +0800 Subject: [PATCH 0059/1431] Fix typo in user steps feature [ci skip] --- features/steps/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/user.rb b/features/steps/user.rb index 3230234cb6d..39bbb59343b 100644 --- a/features/steps/user.rb +++ b/features/steps/user.rb @@ -12,7 +12,7 @@ class Spinach::Features::User < Spinach::FeatureSteps user = User.find_by(name: 'John Doe') project = contributed_project - # Issue controbution + # Issue contribution issue_params = { title: 'Bug in old browser' } Issues::CreateService.new(project, user, issue_params).execute From c8187738a217166cbf03c26adc4a09fcf43a3b94 Mon Sep 17 00:00:00 2001 From: Karlo Soriano Date: Wed, 27 Apr 2016 19:36:04 +0800 Subject: [PATCH 0060/1431] Remove unused .contributed-projects class While working on #13401 and trying to add a new tab to the user profile page, I came across this. I noticed that the `contributed-projects` class was only being used in order to select the div in the tests. For consistency with the other tabs, I decided to remove this class and use the div's id for the selector. --- app/views/users/show.html.haml | 2 +- features/steps/user.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 3028491e5b6..5554f736e98 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -98,7 +98,7 @@ #groups.tab-pane - # This tab is always loaded via AJAX - #contributed.contributed-projects.tab-pane + #contributed.tab-pane - # This tab is always loaded via AJAX #projects.tab-pane diff --git a/features/steps/user.rb b/features/steps/user.rb index 3230234cb6d..18fd8e5c618 100644 --- a/features/steps/user.rb +++ b/features/steps/user.rb @@ -28,7 +28,7 @@ class Spinach::Features::User < Spinach::FeatureSteps end step 'I should see contributed projects' do - page.within '.contributed-projects' do + page.within '#contributed' do expect(page).to have_content(@contributed_project.name) end end From b677b0e33a04ead7edaad5ee528c915980b1c283 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 27 Apr 2016 16:03:20 -0600 Subject: [PATCH 0061/1431] Add explicit --with statement for postgres and mysql gem groups as necessary. When I followed these instructions, the first command did not install the postgres group and it had to be made explicit. --- doc/update/patch_versions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index f446ed0a35b..bca570b28b9 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -57,10 +57,10 @@ sudo -u git -H make cd /home/git/gitlab # PostgreSQL -sudo -u git -H bundle install --without development test mysql --deployment +sudo -u git -H bundle install --without development test mysql --with postgres --deployment # MySQL -sudo -u git -H bundle install --without development test postgres --deployment +sudo -u git -H bundle install --without development test postgres --with mysql --deployment # Optional: clean up old gems sudo -u git -H bundle clean From 267dd23311fe6ad6e9c9af15d683dc1a1bf608fe Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 27 Apr 2016 18:05:57 -0600 Subject: [PATCH 0062/1431] Enable LstripRstrip cop This requires no code changes since it doesn't actually change anything in the codebase, just preventative. --- .rubocop.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 83ed6c38678..9f179efa3ce 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -953,10 +953,9 @@ Performance/DoubleStartEndWith: Performance/EndWith: Enabled: false -# TODO: Enable LstripRstrip Cop. # Use `strip` instead of `lstrip.rstrip`. Performance/LstripRstrip: - Enabled: false + Enabled: true # TODO: Enable RangeInclude Cop. # Use `Range#cover?` instead of `Range#include?`. From 8793025dd04b7a6c8dd6dd04a52eabbfc612745d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 28 Apr 2016 10:26:44 +0200 Subject: [PATCH 0063/1431] added spec testing exception raised --- .../gitlab/bitbucket_import/client_spec.rb | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb index aa0699f2ebf..cd31894eda6 100644 --- a/spec/lib/gitlab/bitbucket_import/client_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -34,18 +34,33 @@ describe Gitlab::BitbucketImport::Client, lib: true do it 'retrieves issues over a number of pages' do stub_request(:get, "https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=0"). - to_return(status: 200, - body: first_sample_data.to_json, - headers: {}) + to_return(status: 200, + body: first_sample_data.to_json, + headers: {}) stub_request(:get, "https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=50"). - to_return(status: 200, - body: second_sample_data.to_json, - headers: {}) + to_return(status: 200, + body: second_sample_data.to_json, + headers: {}) issues = client.issues(project_id) expect(issues.count).to eq(95) end end + + context 'project import' do + it 'calls .from_project with no errors' do + project = create(:empty_project) + project.create_or_update_import_data(credentials: + { :user => "git", + :password => nil, + :bb_session => + { :bitbucket_access_token => "test", + :bitbucket_access_token_secret => "test" } }) + project.import_url = "ssh://git@bitbucket.org/test/test.git" + + expect { described_class.from_project(project) }.to_not raise_error + end + end end From f7844a11be6a5f6aa7011bd96f59bf218c4788ea Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 28 Apr 2016 15:52:23 +0700 Subject: [PATCH 0064/1431] Code refactor and fix broken spec --- app/services/issues/move_service.rb | 8 +++++--- spec/services/issues/move_service_spec.rb | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index 8d41ea5df55..fe5df8f18cb 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -42,7 +42,7 @@ module Issues def create_new_issue new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids, - milestone: cloneable_milestone_id, + milestone_id: cloneable_milestone_id, project: @new_project, author: @old_issue.author, description: rewrite_content(@old_issue.description) } @@ -51,11 +51,13 @@ module Issues end def cloneable_label_ids - @new_project.labels.where(title: @old_issue.labels.pluck(:title)).pluck(:id) + @new_project.labels + .where(title: @old_issue.labels.pluck(:title)).pluck(:id) end def cloneable_milestone_id - @new_project.milestones.find_by(title: @old_issue.milestone.try(:title)) + @new_project.milestones + .find_by(title: @old_issue.milestone.try(:title)).try(:id) end def rewrite_notes diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 7654b255697..62a5e93037e 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -7,7 +7,9 @@ describe Issues::MoveService, services: true do let(:description) { 'Some issue description' } let(:old_project) { create(:project) } let(:new_project) { create(:project) } - let!(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') } + let!(:milestone1) do + create(:milestone, project_id: old_project.id, title: 'v9.0') + end let(:old_issue) do create(:issue, title: title, description: description, @@ -23,10 +25,12 @@ describe Issues::MoveService, services: true do old_project.team << [user, :reporter] new_project.team << [user, :reporter] - create(:milestone, project_id: new_project.id, title: 'v9.0') - - old_issue.labels << create(:label, project_id: old_project.id, title: 'label1') - old_issue.labels << create(:label, project_id: old_project.id, title: 'label2') + ['label1', 'label2'].each do |label| + old_issue.labels << create(:label, + project_id: old_project.id, + title: label + ) + end new_project.labels << create(:label, title: 'label1') new_project.labels << create(:label, title: 'label2') @@ -35,6 +39,10 @@ describe Issues::MoveService, services: true do describe '#execute' do shared_context 'issue move executed' do + let!(:milestone2) do + create(:milestone, project_id: new_project.id, title: 'v9.0') + end + let!(:new_issue) { move_service.execute(old_issue, new_project) } end From 81467d2e04562d63249063113bdd61d4ad16da7d Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 28 Apr 2016 16:21:41 +0700 Subject: [PATCH 0065/1431] Fix rubocop --- spec/services/issues/move_service_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 62a5e93037e..7399003bd75 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -28,8 +28,7 @@ describe Issues::MoveService, services: true do ['label1', 'label2'].each do |label| old_issue.labels << create(:label, project_id: old_project.id, - title: label - ) + title: label) end new_project.labels << create(:label, title: 'label1') From 9a9681772c3c8a6523bb94e758655e5c555cea91 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 28 Apr 2016 11:40:23 +0200 Subject: [PATCH 0066/1431] fix failing spec --- spec/lib/gitlab/bitbucket_import/client_spec.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb index cd31894eda6..af839f42421 100644 --- a/spec/lib/gitlab/bitbucket_import/client_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -53,11 +53,10 @@ describe Gitlab::BitbucketImport::Client, lib: true do it 'calls .from_project with no errors' do project = create(:empty_project) project.create_or_update_import_data(credentials: - { :user => "git", - :password => nil, - :bb_session => - { :bitbucket_access_token => "test", - :bitbucket_access_token_secret => "test" } }) + { user: "git", + password: nil, + bb_session: { bitbucket_access_token: "test", + bitbucket_access_token_secret: "test" } }) project.import_url = "ssh://git@bitbucket.org/test/test.git" expect { described_class.from_project(project) }.to_not raise_error From ced6563121851d95cd156ee486476469acdd6469 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 28 Apr 2016 16:41:35 +0700 Subject: [PATCH 0067/1431] Add changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 558897ad892..c6c96c096c8 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.8.0 (unreleased) + - Assign labels and milestone to target project when moving issue - Remove future dates from contribution calendar graph. - Fix error when visiting commit builds page before build was updated - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project From f43044cc8abb23f391f635e145b52cf5a54a0058 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 28 Apr 2016 16:43:21 +0700 Subject: [PATCH 0068/1431] Update changelog --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c6c96c096c8..2d3a9e051e5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.8.0 (unreleased) - - Assign labels and milestone to target project when moving issue + - Assign labels and milestone to target project when moving issue. !3934 - Remove future dates from contribution calendar graph. - Fix error when visiting commit builds page before build was updated - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project From 8a6776caa9f45c4c16347ad662bd83519032687d Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 28 Apr 2016 12:11:08 +0200 Subject: [PATCH 0069/1431] Use 'exec' in Unicorn and Sidekiq launch scripts When running Unicorn or Sidekiq in the foreground this change removes an intermediate /bin/sh process. This makes process supervision in the GitLab Development Kit more reliable. This change does not affect Omnibus-GitLab (because there we do not use these launch scripts). Installations from source do use the launch scripts but for the standard GitLab init script this change will not make a difference. Custom installations using Upstart or Systemd may be affected however, because under certain configurations these systems count exactly how many forks happen during process startup, and we are reducing that number by one here. --- bin/background_jobs | 2 +- bin/web | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/background_jobs b/bin/background_jobs index 1f67d732949..25a578a1c49 100755 --- a/bin/background_jobs +++ b/bin/background_jobs @@ -37,7 +37,7 @@ start_no_deamonize() start_sidekiq() { - bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@" + exec bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@" } load_ok() diff --git a/bin/web b/bin/web index 03fe7a6354b..ecd0bbd10b0 100755 --- a/bin/web +++ b/bin/web @@ -19,12 +19,12 @@ get_unicorn_pid() start() { - $unicorn_cmd -D + exec $unicorn_cmd -D } start_foreground() { - $unicorn_cmd + exec $unicorn_cmd } stop() From 52a6b976a77d9ede9f1b50d55e33824ed05acb5f Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 28 Apr 2016 14:13:39 +0200 Subject: [PATCH 0070/1431] feature proposal issue template in contributing guide --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24cd5864530..a1ded9b9848 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,6 +141,16 @@ members to add the label `feature proposal` to the issue. Please keep feature proposals as small and simple as possible, complex ones might be edited to make them small and simple. +You are encouraged to use the template below for feature proposals. + +``` +## Description including problem, use cases, benefits, and/or goals + +## Proposal + +## Links / references +``` + For changes in the interface, it can be helpful to create a mockup first. If you want to create something yourself, consider opening an issue first to discuss whether it is interesting to include this in GitLab. From e9eeeaa0d0c8820f66a9581d8b4d03f02ac705d0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Apr 2016 15:35:40 +0200 Subject: [PATCH 0071/1431] Redesign navigation for group pages Signed-off-by: Dmitriy Zaporozhets --- app/views/groups/activity.html.haml | 1 - app/views/groups/edit.html.haml | 2 - .../groups/group_members/index.html.haml | 1 - app/views/groups/issues.html.haml | 1 - app/views/groups/merge_requests.html.haml | 1 - app/views/groups/milestones/index.html.haml | 1 - app/views/groups/projects.html.haml | 1 - app/views/groups/show.html.haml | 6 --- app/views/layouts/group.html.haml | 3 +- app/views/layouts/group_settings.html.haml | 4 +- app/views/layouts/nav/_dashboard.html.haml | 4 +- app/views/layouts/nav/_group.html.haml | 40 ++++++++++++------- 12 files changed, 31 insertions(+), 34 deletions(-) diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml index f73e1d9e865..aaad265b3ee 100644 --- a/app/views/groups/activity.html.haml +++ b/app/views/groups/activity.html.haml @@ -3,7 +3,6 @@ = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") - page_title "Activity" -- header_title group_title(@group, "Activity", activity_group_path(@group)) %section.activities = render 'activities' diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index a698cbbe9db..92cd4c553d0 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,5 +1,3 @@ -- header_title group_title(@group, "Settings", edit_group_path(@group)) - .panel.panel-default.prepend-top-default .panel-heading Group settings diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 6b7fd5746d6..0eb6bbd4420 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,5 +1,4 @@ - page_title "Members" -- header_title group_title(@group, "Members", group_group_members_path(@group)) .group-members-page.prepend-top-default - if current_user && current_user.can?(:admin_group_member, @group) diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index aea35c50862..76160232e06 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,5 +1,4 @@ - page_title "Issues" -- header_title group_title(@group, "Issues", issues_group_path(@group)) = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index e1c9dd931ee..87090352e26 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,5 +1,4 @@ - page_title "Merge Requests" -- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group)) .top-area = render 'shared/issuable/nav', type: :merge_requests diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index ab307708b75..8bca00eb710 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,5 +1,4 @@ - page_title "Milestones" -- header_title group_title(@group, "Milestones", group_milestones_path(@group)) .top-area = render 'shared/milestones_filter' diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index dd75766121e..c2f2d9912f7 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,5 +1,4 @@ - page_title "Projects" -- header_title group_title(@group, "Projects", projects_group_path(@group)) .panel.panel-default.prepend-top-default .panel-heading diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 3d16ecb097a..c71070e6c9c 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -5,12 +5,6 @@ = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") .cover-block - .cover-controls - - if @group && can?(current_user, :admin_group, @group) - = link_to icon('pencil'), edit_group_path(@group), class: 'btn' - - if current_user - = link_to icon('rss'), group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' - .avatar-holder = link_to group_icon(@group), target: '_blank' do = image_tag group_icon(@group), class: "avatar group-avatar s90" diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 2e483b7148d..7bee7e172e6 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,6 +1,7 @@ - page_title @group.name - page_description @group.description unless page_description - header_title group_title(@group) unless header_title -- sidebar "group" unless sidebar +- sidebar "dashboard" unless sidebar +- nav "group" = render template: "layouts/application" diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml index a1a1fc2f858..1ff53ce2a9d 100644 --- a/app/views/layouts/group_settings.html.haml +++ b/app/views/layouts/group_settings.html.haml @@ -1,5 +1,5 @@ - page_title "Settings" -- header_title group_title(@group, "Settings", edit_group_path(@group)) -- sidebar "group_settings" +- sidebar "dashboard" unless sidebar +- nav "group" = render template: "layouts/group" diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index ca49c313ff7..fad4224e945 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -15,12 +15,12 @@ = icon('dashboard fw') %span Activity - = nav_link(controller: :groups) do + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to dashboard_groups_path, title: 'Groups' do = icon('group fw') %span Groups - = nav_link(controller: :milestones) do + = nav_link(controller: 'dashboard/milestones') do = link_to dashboard_milestones_path, title: 'Milestones' do = icon('clock-o fw') %span diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 55940741dc0..bc5ce7052e4 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,12 +1,28 @@ -%ul.nav.nav-sidebar - = nav_link do - = link_to root_path, title: 'Go to dashboard', class: 'back-link' do - = icon('caret-square-o-left fw') - %span - Go to dashboard +- if current_user + .controls + - if current_path?('groups#show') + = link_to icon('rss'), group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn btn-gray rss-btn' - %li.separate-item + %span.dropdown.group-settings-dropdown + %a.dropdown-new.btn.btn-gray#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('angle-down') + %ul.dropdown-menu.dropdown-menu-align-right + = nav_link(path: 'groups#projects') do + = link_to projects_group_path(@group), title: 'Projects' do + Projects + %li.divider + - if @group && can?(current_user, :admin_group, @group) + %li + = link_to edit_group_path(@group) do + Edit Group + - if access = @group.users.find(current_user) + %li + = link_to leave_group_group_members_path(@group), + data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do + Leave Group +%ul.nav-links = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do = icon('group fw') @@ -28,22 +44,16 @@ %span Issues - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.count= number_with_delimiter(issues.count) + %span.badge.count= number_with_delimiter(issues.count) = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do = icon('tasks fw') %span Merge Requests - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.count= number_with_delimiter(merge_requests.count) + %span.badge.count= number_with_delimiter(merge_requests.count) = nav_link(controller: [:group_members]) do = link_to group_group_members_path(@group), title: 'Members' do = icon('users fw') %span Members - - if can?(current_user, :admin_group, @group) - = nav_link(html_options: { class: "separate-item" }) do - = link_to edit_group_path(@group), title: 'Settings' do - = icon ('cogs fw') - %span - Settings From 9ab70184e3299766165017267a74f53e80b03836 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Thu, 28 Apr 2016 12:05:45 -0700 Subject: [PATCH 0072/1431] Pass trusted_proxies to action_dispatch as IPAddrs instead of strings Without this setting your own trusted_proxies does not work. --- config/initializers/trusted_proxies.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb index b8cc025bae2..d256a16d42b 100644 --- a/config/initializers/trusted_proxies.rb +++ b/config/initializers/trusted_proxies.rb @@ -1,2 +1,3 @@ -Rails.application.config.action_dispatch.trusted_proxies = +Rails.application.config.action_dispatch.trusted_proxies = ( [ '127.0.0.1', '::1' ] + Array(Gitlab.config.gitlab.trusted_proxies) +).map { |proxy| IPAddr.new(proxy) } From c4b9bd041321df25764ad1de90f89b1f0dda9f33 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Fri, 22 Apr 2016 14:07:25 +0200 Subject: [PATCH 0073/1431] API support for the 'since' and 'until' operators on commit requests - Parameter validation as ISO8601 format --- CHANGELOG | 1 + Gemfile.lock | 2 +- .../projects/commits_controller.rb | 2 +- app/controllers/projects/graphs_controller.rb | 4 +-- app/models/repository.rb | 6 ++-- doc/api/commits.md | 2 ++ lib/api/commits.rb | 8 ++++- lib/api/helpers.rb | 16 +++++++++ lib/gitlab/push_data_builder.rb | 2 +- spec/models/repository_spec.rb | 2 +- spec/requests/api/commits_spec.rb | 35 +++++++++++++++++++ 11 files changed, 71 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e52e52691c2..c9215954378 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.8.0 (unreleased) - Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea) - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Added button to toggle whitespaces changes on diff view + - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) v 8.7.1 (unreleased) - Throttle the update of `project.last_activity_at` to 1 minute. !3848 diff --git a/Gemfile.lock b/Gemfile.lock index 91d89b4875a..02ccbdd8fc3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -351,7 +351,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (10.0.0) + gitlab_git (10.0.2) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 1420b96840c..a52c614b259 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -15,7 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController if search.present? @repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact else - @repository.commits(@ref, @path, @limit, @offset) + @repository.commits(@ref, path: @path, limit: @limit, offset: @offset) end @note_counts = project.notes.where(commit_id: @commits.map(&:id)). diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index d13ea9f34b6..092ef32e6e3 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -17,7 +17,7 @@ class Projects::GraphsController < Projects::ApplicationController end def commits - @commits = @project.repository.commits(@ref, nil, 2000, 0, true) + @commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true) @commits_graph = Gitlab::Graphs::Commits.new(@commits) @commits_per_week_days = @commits_graph.commits_per_week_days @commits_per_time = @commits_graph.commits_per_time @@ -55,7 +55,7 @@ class Projects::GraphsController < Projects::ApplicationController private def fetch_graph - @commits = @project.repository.commits(@ref, nil, 6000, 0, true) + @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true) @log = [] @commits.each do |commit| diff --git a/app/models/repository.rb b/app/models/repository.rb index d495c8d18f5..292acf9044f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -87,13 +87,15 @@ class Repository nil end - def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) + def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil) options = { repo: raw_repository, ref: ref, path: path, limit: limit, offset: offset, + after: after, + before: before, # --follow doesn't play well with --skip. See: # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 follow: false, @@ -575,7 +577,7 @@ class Repository end def contributors - commits = self.commits(nil, nil, 2000, 0, true) + commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true) commits.group_by(&:author_email).map do |email, commits| contributor = Gitlab::Contributor.new diff --git a/doc/api/commits.md b/doc/api/commits.md index 6341440c58b..57c2e1d9b87 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -12,6 +12,8 @@ GET /projects/:id/repository/commits | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | +| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | +| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | ```bash curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits" diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 4544a41b1e3..93a3a5ce089 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -12,14 +12,20 @@ module API # Parameters: # id (required) - The ID of a project # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used + # since (optional) - Only commits after or in this date will be returned + # until (optional) - Only commits before or in this date will be returned # Example Request: # GET /projects/:id/repository/commits get ":id/repository/commits" do + datetime_attributes! :since, :until + page = (params[:page] || 0).to_i per_page = (params[:per_page] || 20).to_i ref = params[:ref_name] || user_project.try(:default_branch) || 'master' + after = params[:since] + before = params[:until] - commits = user_project.repository.commits(ref, nil, per_page, page * per_page) + commits = user_project.repository.commits(ref, limit: per_page, offset: page * per_page, after: after, before: before) present commits, with: Entities::RepoCommit end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 5bbf721321d..40c967453fb 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -183,6 +183,22 @@ module API Gitlab::Access.options_with_owner.values.include? level.to_i end + # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601 + # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked. + # + # Parameters: + # keys (required) - An array consisting of elements that must be parseable as dates from the params hash + def datetime_attributes!(*keys) + keys.each do |key| + begin + params[key] = Time.xmlschema(params[key]) if params[key].present? + rescue ArgumentError + message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ" + render_api_error!(message, 400) + end + end + end + def issuable_order_by if params["order_by"] == 'updated_at' 'updated_at' diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index 67622f321a6..c8f12577112 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -66,7 +66,7 @@ module Gitlab # This method provide a sample data generated with # existing project and commits to test webhooks def build_sample(project, user) - commits = project.repository.commits(project.default_branch, nil, 3) + commits = project.repository.commits(project.default_branch, limit: 3) ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" build(project, user, commits.last.id, commits.first.id, ref, commits) end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index a306cc4aef8..802b4e579e1 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -561,7 +561,7 @@ describe Repository, models: true do end describe :skip_merged_commit do - subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } } + subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } } it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') } end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index e28998d51b5..cb82ca7802d 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -32,6 +32,41 @@ describe API::API, api: true do expect(response.status).to eq(401) end end + + context "since optional parameter" do + it "should return project commits since provided parameter" do + commits = project.repository.commits("master") + since = commits.second.created_at + + get api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user) + + expect(json_response.size).to eq 2 + expect(json_response.first["id"]).to eq(commits.first.id) + expect(json_response.second["id"]).to eq(commits.second.id) + end + end + + context "until optional parameter" do + it "should return project commits until provided parameter" do + commits = project.repository.commits("master") + before = commits.second.created_at + + get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) + + expect(json_response.size).to eq(commits.size - 1) + expect(json_response.first["id"]).to eq(commits.second.id) + expect(json_response.second["id"]).to eq(commits.third.id) + end + end + + context "invalid xmlschema date parameters" do + it "should return an invalid parameter error message" do + get api("/projects/#{project.id}/repository/commits?since=invalid-date", user) + + expect(response.status).to eq(400) + expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format" + end + end end describe "GET /projects:id/repository/commits/:sha" do From f41a3e24d20b26b53c5321571ef89f441c32aa4d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 08:13:16 -0400 Subject: [PATCH 0074/1431] Added authentication service for docker registry --- Gemfile | 1 + Gemfile.lock | 1 + app/models/ability.rb | 8 +- app/models/ci/build.rb | 1 + app/models/project.rb | 5 + config/initializers/1_settings.rb | 39 ++++ ...07120251_add_images_enabled_for_project.rb | 5 + db/schema.rb | 1 + lib/api/api.rb | 1 + lib/api/auth.rb | 166 ++++++++++++++++++ 10 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20160407120251_add_images_enabled_for_project.rb create mode 100644 lib/api/auth.rb diff --git a/Gemfile b/Gemfile index 7882e467f8d..512c6babd7e 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' gem 'rack-oauth2', '~> 1.2.1' +gem 'jwt' # Spam and anti-bot protection gem 'recaptcha', require: 'recaptcha/rails' diff --git a/Gemfile.lock b/Gemfile.lock index 1dcda0daff6..2b578429b3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -957,6 +957,7 @@ DEPENDENCIES jquery-scrollto-rails (~> 1.4.3) jquery-turbolinks (~> 2.1.0) jquery-ui-rails (~> 5.0.0) + jwt kaminari (~> 0.16.3) letter_opener_web (~> 1.3.0) licensee (~> 8.0.0) diff --git a/app/models/ability.rb b/app/models/ability.rb index 6103a2947e2..ba27b9a9b14 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -203,6 +203,7 @@ class Ability :admin_label, :read_commit_status, :read_build, + :read_image, ] end @@ -216,7 +217,9 @@ class Ability :update_build, :create_merge_request, :create_wiki, - :push_code + :push_code, + :create_image, + :update_image, ] end @@ -242,7 +245,8 @@ class Ability :admin_wiki, :admin_project, :admin_commit_status, - :admin_build + :admin_build, + :admin_image ] end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 553cd447971..c2ddee527e5 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -426,6 +426,7 @@ module Ci 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 << { key: :CI_DOCKER_REGISTRY, value: project.registry_repository_url, public: true } if project.registry_repository_url variables end end diff --git a/app/models/project.rb b/app/models/project.rb index 5c6c36e6b31..76265a59ea7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -64,6 +64,7 @@ class Project < ActiveRecord::Base default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wall_enabled, false default_value_for :snippets_enabled, gitlab_config_features.snippets + default_value_for :images_enabled, gitlab_config_features.images default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } # set last_activity_at to the same as created_at @@ -369,6 +370,10 @@ class Project < ActiveRecord::Base @repository ||= Repository.new(path_with_namespace, self) end + def registry_repository_url + "#{Gitlab.config.registry.host_with_port}/#{path_with_namespace}" if images_enabled? && Gitlab.config.registry.enabled + end + def commit(id = 'HEAD') repository.commit(id) end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8db2c05fe45..01ee8a0d525 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -27,6 +27,30 @@ class Settings < Settingslogic ].join('') end + def build_registry_api_url + if registry.port.to_i == (registry.https ? 443 : 80) + custom_port = nil + else + custom_port = ":#{registry.port}" + end + [ registry.protocol, + "://", + registry.internal_host, + custom_port + ].join('') + end + + def build_registry_host_with_port + if registry.port.to_i == (registry.https ? 443 : 80) + custom_port = nil + else + custom_port = ":#{registry.port}" + end + [ registry.host, + custom_port + ].join('') + end + def build_gitlab_shell_ssh_path_prefix user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}" @@ -211,6 +235,7 @@ Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.g Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil? +Settings.gitlab.default_projects_features['images'] = true if Settings.gitlab.default_projects_features['images'].nil? Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil? Settings.gitlab['restricted_signup_domains'] ||= [] @@ -242,6 +267,20 @@ Settings.artifacts['enabled'] = true if Settings.artifacts['enabled'].nil? Settings.artifacts['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"), Rails.root) Settings.artifacts['max_size'] ||= 100 # in megabytes +# +# Registry +# +Settings['registry'] ||= Settingslogic.new({}) +Settings.registry['registry'] = false if Settings.registry['enabled'].nil? +Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], "registry"), Rails.root) +Settings.registry['host'] ||= "example.com" +Settings.registry['internal_host']||= "localhost" +Settings.registry['https'] = false if Settings.registry['https'].nil? +Settings.registry['port'] ||= Settings.registry.https ? 443 : 80 +Settings.registry['protocol'] ||= Settings.registry.https ? "https" : "http" +Settings.registry['api_url'] ||= Settings.send(:build_registry_api_url) +Settings.registry['host_port'] ||= Settings.send(:build_registry_host_with_port) + # # Git LFS # diff --git a/db/migrate/20160407120251_add_images_enabled_for_project.rb b/db/migrate/20160407120251_add_images_enabled_for_project.rb new file mode 100644 index 00000000000..6a221a7fb03 --- /dev/null +++ b/db/migrate/20160407120251_add_images_enabled_for_project.rb @@ -0,0 +1,5 @@ +class AddImagesEnabledForProject < ActiveRecord::Migration + def change + add_column :projects, :images_enabled, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 42457d92353..bf46028d23f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -760,6 +760,7 @@ ActiveRecord::Schema.define(version: 20160421130527) do t.integer "pushes_since_gc", default: 0 t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" + t.boolean "images_enabled" end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree diff --git a/lib/api/api.rb b/lib/api/api.rb index cc1004f8005..6ddfe11d98e 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -58,5 +58,6 @@ module API mount Variables mount Runners mount Licenses + mount Auth end end diff --git a/lib/api/auth.rb b/lib/api/auth.rb new file mode 100644 index 00000000000..b992e497307 --- /dev/null +++ b/lib/api/auth.rb @@ -0,0 +1,166 @@ +module API + # Projects builds API + class Auth < Grape::API + namespace 'auth' do + get 'token' do + required_attributes! [:scope, :service] + keys = attributes_for_keys [:scope, :service] + + case keys[:service] + when 'docker' + docker_token_auth(keys[:scope]) + else + not_found! + end + end + end + + helpers do + def docker_token_auth(scope) + @type, @path, actions = scope.split(':', 3) + bad_request!("invalid type: #{type}") unless type == 'repository' + + @actions = actions.split(',') + bad_request!('missing actions') if @actions.empty? + + @project = Project.find_with_namespace(path) + not_found!('Project') unless @project + + auth! + + authorize_actions!(@actions) + + { token: encode(docker_payload) } + end + + def auth! + auth = BasicRequest.new(request.env) + return unless auth.provided? + + return bad_request unless auth.basic? + + # Authentication with username and password + login, password = auth.credentials + + if ci_request?(login, password) + @ci = true + return + end + + @user = authenticate_user(login, password) + + if @user + request.env['REMOTE_USER'] = @auth.username + end + end + + def ci_request?(login, password) + matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) + + if @project && matched_login.present? + underscored_service = matched_login['s'].underscore + + if underscored_service == 'gitlab_ci' + return @project.valid_build_token?(password) + end + end + + false + end + + def authenticate_user(login, password) + user = Gitlab::Auth.new.find(login, password) + + unless user + user = oauth_access_token_check(login, password) + end + + # If the user authenticated successfully, we reset the auth failure count + # from Rack::Attack for that IP. A client may attempt to authenticate + # with a username and blank password first, and only after it receives + # a 401 error does it present a password. Resetting the count prevents + # false positives from occurring. + # + # Otherwise, we let Rack::Attack know there was a failed authentication + # attempt from this IP. This information is stored in the Rails cache + # (Redis) and will be used by the Rack::Attack middleware to decide + # whether to block requests from this IP. + config = Gitlab.config.rack_attack.git_basic_auth + + if config.enabled + if user + # A successful login will reset the auth failure count from this IP + Rack::Attack::Allow2Ban.reset(@request.ip, config) + else + banned = Rack::Attack::Allow2Ban.filter(@request.ip, config) do + # Unless the IP is whitelisted, return true so that Allow2Ban + # increments the counter (stored in Rails.cache) for the IP + if config.ip_whitelist.include?(@request.ip) + false + else + true + end + end + + if banned + Rails.logger.info "IP #{@request.ip} failed to login " \ + "as #{login} but has been temporarily banned from Git auth" + end + end + end + + user + end + + def docker_payload + { + access: [ + type: @type, + name: @path, + actions: @actions + ], + exp: Time.now.to_i + 3600 + } + end + + def private_key + @private_key ||= OpenSSL::PKey::RSA.new File.read 'config/registry.key' + end + + def encode(payload) + JWT.encode(payload, private_key, 'RS256') + end + + def authorize_actions!(actions) + actions.each do |action| + forbidden! unless can_access?(action) + end + end + + def can_access?(action) + case action + when 'pull' + @ci || can?(@user, :download_code, @project) + when 'push' + @ci || can?(@user, :push_code, @project) + else + false + end + end + + class BasicRequest < Rack::Auth::AbstractRequest + def basic? + "basic" == scheme + end + + def credentials + @credentials ||= params.unpack("m*").first.split(/:/, 2) + end + + def username + credentials.first + end + end + end + end +end From 03b3fe13f6af67f8117cf4322b605630f55f3136 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 08:23:17 -0400 Subject: [PATCH 0075/1431] Make images_enabled configurable --- app/controllers/projects_controller.rb | 3 ++- app/views/projects/edit.html.haml | 10 ++++++++++ lib/api/entities.rb | 3 ++- lib/api/projects.rb | 5 +++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3768efe142a..52f7b993343 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -235,7 +235,8 @@ class ProjectsController < Projects::ApplicationController def project_params params.require(:project).permit( :name, :path, :description, :issues_tracker, :tag_list, :runners_token, - :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, + :issues_enabled, :merge_requests_enabled, :snippets_enabled, :images_enabled, + :issues_tracker_id, :default_branch, :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :public_builds, diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 76a4f41193c..5c7960031e0 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -84,6 +84,16 @@ %br %span.descr Share code pastes with others out of git repository + - if Gitlab.config.registry.enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :images_enabled do + = f.check_box :images_enabled + %strong Images + %br + %span.descr Use Docker Registry for this repository + = render 'builds_settings', f: f %fieldset.features diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 716ca6f7ed9..95c3597b03c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -66,7 +66,8 @@ module API expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at + expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :images_enabled + expose :created_at, :last_activity_at expose :shared_runners_enabled expose :creator_id expose :namespace diff --git a/lib/api/projects.rb b/lib/api/projects.rb index cc2c7a0c503..6f85bc4b1b9 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -94,6 +94,7 @@ module API # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) + # images_enabled (optional) # shared_runners_enabled (optional) # namespace_id (optional) - defaults to user namespace # public (optional) - if true same as setting visibility_level = 20 @@ -112,6 +113,7 @@ module API :builds_enabled, :wiki_enabled, :snippets_enabled, + :images_enabled, :shared_runners_enabled, :namespace_id, :public, @@ -143,6 +145,7 @@ module API # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) + # images_enabled (optional) # shared_runners_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) @@ -206,6 +209,7 @@ module API # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) + # images_enabled (optional) # shared_runners_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) - visibility level of a project @@ -222,6 +226,7 @@ module API :builds_enabled, :wiki_enabled, :snippets_enabled, + :images_enabled, :shared_runners_enabled, :public, :visibility_level, From 0ca8db25f008cd3bc4f2df0f58efd739718323d0 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 19 Apr 2016 10:55:10 -0400 Subject: [PATCH 0076/1431] Try to fix auth service --- lib/api/auth.rb | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/api/auth.rb b/lib/api/auth.rb index b992e497307..ec944b1dc8c 100644 --- a/lib/api/auth.rb +++ b/lib/api/auth.rb @@ -3,12 +3,12 @@ module API class Auth < Grape::API namespace 'auth' do get 'token' do - required_attributes! [:scope, :service] - keys = attributes_for_keys [:scope, :service] + required_attributes! [:service] + keys = attributes_for_keys [:offline_token, :scope, :service] case keys[:service] when 'docker' - docker_token_auth(keys[:scope]) + docker_token_auth(keys[:scope], keys[:offline_token]) else not_found! end @@ -16,19 +16,23 @@ module API end helpers do - def docker_token_auth(scope) - @type, @path, actions = scope.split(':', 3) - bad_request!("invalid type: #{type}") unless type == 'repository' - - @actions = actions.split(',') - bad_request!('missing actions') if @actions.empty? - - @project = Project.find_with_namespace(path) - not_found!('Project') unless @project - + def docker_token_auth(scope, offline_token) auth! - authorize_actions!(@actions) + if offline_token + forbidden! unless @user + elsif scope + @type, @path, actions = scope.split(':', 3) + bad_request!("invalid type: #{@type}") unless @type == 'repository' + + @actions = actions.split(',') + bad_request!('missing actions') if @actions.empty? + + @project = Project.find_with_namespace(@path) + not_found!('Project') unless @project + + authorize_actions!(@actions) + end { token: encode(docker_payload) } end @@ -50,7 +54,7 @@ module API @user = authenticate_user(login, password) if @user - request.env['REMOTE_USER'] = @auth.username + request.env['REMOTE_USER'] = @user.username end end @@ -71,10 +75,6 @@ module API def authenticate_user(login, password) user = Gitlab::Auth.new.find(login, password) - unless user - user = oauth_access_token_check(login, password) - end - # If the user authenticated successfully, we reset the auth failure count # from Rack::Attack for that IP. A client may attempt to authenticate # with a username and blank password first, and only after it receives From 72611f9cfa9014653c0894115af6223687c2eab4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 19 Apr 2016 13:57:35 -0400 Subject: [PATCH 0077/1431] Auth token --- config/gitlab.yml.example | 10 ++++++++++ config/initializers/1_settings.rb | 1 + lib/api/auth.rb | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 07ce4b6d715..e55ca6f9c6b 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -176,6 +176,16 @@ production: &base repository_archive_cache_worker: cron: "0 * * * *" + registry: + # enabled: true + # host: localhost + # port: 5000 + # https: false + # internal_host: localhost + # key: config/registry.key + # issuer: omnibus-certificate + # path: shared/registry + # # 2. GitLab CI settings # ========================== diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 01ee8a0d525..b94f3f2f901 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -275,6 +275,7 @@ Settings.registry['registry'] = false if Settings.registry['enabled'].nil? Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], "registry"), Rails.root) Settings.registry['host'] ||= "example.com" Settings.registry['internal_host']||= "localhost" +Settings.registry['key'] ||= nil Settings.registry['https'] = false if Settings.registry['https'].nil? Settings.registry['port'] ||= Settings.registry.https ? 443 : 80 Settings.registry['protocol'] ||= Settings.registry.https ? "https" : "http" diff --git a/lib/api/auth.rb b/lib/api/auth.rb index ec944b1dc8c..d769c692754 100644 --- a/lib/api/auth.rb +++ b/lib/api/auth.rb @@ -119,12 +119,13 @@ module API name: @path, actions: @actions ], + iss: Gitlab.config.registry.issuer, exp: Time.now.to_i + 3600 } end def private_key - @private_key ||= OpenSSL::PKey::RSA.new File.read 'config/registry.key' + @private_key ||= OpenSSL::PKey::RSA.new File.read Gitlab.config.registry.key end def encode(payload) From 8aac802eaf417a4f484f099089410934cdfdb0b7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 19 Apr 2016 14:16:17 -0400 Subject: [PATCH 0078/1431] Audience --- lib/api/auth.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/api/auth.rb b/lib/api/auth.rb index d769c692754..e4ce9bf122d 100644 --- a/lib/api/auth.rb +++ b/lib/api/auth.rb @@ -120,6 +120,7 @@ module API actions: @actions ], iss: Gitlab.config.registry.issuer, + aud: "docker", exp: Time.now.to_i + 3600 } end From 5fc310b440a7bb3ead91760ac2b7cbb1cee72f2a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 21 Apr 2016 10:02:24 +0200 Subject: [PATCH 0079/1431] Missing parameters of docker payload --- lib/api/auth.rb | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/api/auth.rb b/lib/api/auth.rb index e4ce9bf122d..dab04bca818 100644 --- a/lib/api/auth.rb +++ b/lib/api/auth.rb @@ -113,6 +113,7 @@ module API end def docker_payload + issued_at = Time.now { access: [ type: @type, @@ -121,8 +122,14 @@ module API ], iss: Gitlab.config.registry.issuer, aud: "docker", + sub: @user.try(:username), + aud: @service, + iat: issued_at, + nbf: issued_at - 5.seconds, + exp: issued_at + 60.minutes, + jti: SecureRandom.uuid, exp: Time.now.to_i + 3600 - } + }.compact end def private_key @@ -130,7 +137,10 @@ module API end def encode(payload) - JWT.encode(payload, private_key, 'RS256') + headers = { + kid: kid(private_key) + } + JWT.encode(payload, private_key, 'RS256', headers) end def authorize_actions!(actions) @@ -150,6 +160,15 @@ module API end end + def kid(private_key) + sha256 = Digest::SHA256.new + sha256.update(private_key.public_key.to_der) + payload = StringIO.new(sha256.digest).read(30) + Base32.encode(payload).split("").each_slice(4).each_with_object([]) do |slice, mem| + mem << slice.join + end.join(":") + end + class BasicRequest < Rack::Auth::AbstractRequest def basic? "basic" == scheme From 0a280158efeb7f681589ae7af24f0ed9052de809 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 15 Apr 2016 19:23:33 +0530 Subject: [PATCH 0080/1431] Eager load `lib/api` - So that the server doesn't have to be restarted for every change in dev. --- config/application.rb | 2 + config/routes.rb | 1 - lib/api/api.rb | 4 +- lib/api/api_guard.rb | 316 +++++++++++++++++++++--------------------- lib/ci/api/api.rb | 2 +- 5 files changed, 163 insertions(+), 162 deletions(-) diff --git a/config/application.rb b/config/application.rb index 2e2ed48db07..abe22691ad1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -79,6 +79,8 @@ module Gitlab # This is needed for gitlab-shell ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH'] + config.eager_load_paths += ["#{Rails.root}/lib"] + config.generators do |g| g.factory_girl false end diff --git a/config/routes.rb b/config/routes.rb index 5ce1f49ec6a..adf4bb18b3c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,5 @@ require 'sidekiq/web' require 'sidekiq/cron/web' -require 'api/api' Rails.application.routes.draw do if Gitlab::Sherlock.enabled? diff --git a/lib/api/api.rb b/lib/api/api.rb index 6ddfe11d98e..d41b4b71865 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -1,8 +1,6 @@ -Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file} - module API class API < Grape::API - include APIGuard + include ::API::APIGuard version 'v3', using: :path rescue_from ActiveRecord::RecordNotFound do diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index b9994fcefda..6dfd6e4396b 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -2,171 +2,173 @@ require 'rack/oauth2' -module APIGuard - extend ActiveSupport::Concern +module API + module APIGuard + extend ActiveSupport::Concern - included do |base| - # OAuth2 Resource Server Authentication - use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request| - # The authenticator only fetches the raw token string + included do |base| + # OAuth2 Resource Server Authentication + use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request| + # The authenticator only fetches the raw token string - # Must yield access token to store it in the env - request.access_token - end - - helpers HelperMethods - - install_error_responders(base) - end - - # Helper Methods for Grape Endpoint - module HelperMethods - # Invokes the doorkeeper guard. - # - # If token is presented and valid, then it sets @current_user. - # - # If the token does not have sufficient scopes to cover the requred scopes, - # then it raises InsufficientScopeError. - # - # If the token is expired, then it raises ExpiredError. - # - # If the token is revoked, then it raises RevokedError. - # - # If the token is not found (nil), then it raises TokenNotFoundError. - # - # Arguments: - # - # scopes: (optional) scopes required for this guard. - # Defaults to empty array. - # - def doorkeeper_guard!(scopes: []) - if (access_token = find_access_token).nil? - raise TokenNotFoundError - - else - case validate_access_token(access_token, scopes) - when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE - raise InsufficientScopeError.new(scopes) - when Oauth2::AccessTokenValidationService::EXPIRED - raise ExpiredError - when Oauth2::AccessTokenValidationService::REVOKED - raise RevokedError - when Oauth2::AccessTokenValidationService::VALID - @current_user = User.find(access_token.resource_owner_id) - end + # Must yield access token to store it in the env + request.access_token end + + helpers HelperMethods + + install_error_responders(base) end - def doorkeeper_guard(scopes: []) - if access_token = find_access_token - case validate_access_token(access_token, scopes) - when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE - raise InsufficientScopeError.new(scopes) + # Helper Methods for Grape Endpoint + module HelperMethods + # Invokes the doorkeeper guard. + # + # If token is presented and valid, then it sets @current_user. + # + # If the token does not have sufficient scopes to cover the requred scopes, + # then it raises InsufficientScopeError. + # + # If the token is expired, then it raises ExpiredError. + # + # If the token is revoked, then it raises RevokedError. + # + # If the token is not found (nil), then it raises TokenNotFoundError. + # + # Arguments: + # + # scopes: (optional) scopes required for this guard. + # Defaults to empty array. + # + def doorkeeper_guard!(scopes: []) + if (access_token = find_access_token).nil? + raise TokenNotFoundError - when Oauth2::AccessTokenValidationService::EXPIRED - raise ExpiredError - - when Oauth2::AccessTokenValidationService::REVOKED - raise RevokedError - - when Oauth2::AccessTokenValidationService::VALID - @current_user = User.find(access_token.resource_owner_id) - end - end - end - - def current_user - @current_user - end - - private - def find_access_token - @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods) - end - - def doorkeeper_request - @doorkeeper_request ||= ActionDispatch::Request.new(env) - end - - def validate_access_token(access_token, scopes) - Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes) - end - end - - module ClassMethods - # Installs the doorkeeper guard on the whole Grape API endpoint. - # - # Arguments: - # - # scopes: (optional) scopes required for this guard. - # Defaults to empty array. - # - def guard_all!(scopes: []) - before do - guard! scopes: scopes - end - end - - private - def install_error_responders(base) - error_classes = [ MissingTokenError, TokenNotFoundError, - ExpiredError, RevokedError, InsufficientScopeError] - - base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler - end - - def oauth2_bearer_token_error_handler - Proc.new do |e| - response = - case e - when MissingTokenError - Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new - - when TokenNotFoundError - Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( - :invalid_token, - "Bad Access Token.") - - when ExpiredError - Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( - :invalid_token, - "Token is expired. You can either do re-authorization or token refresh.") - - when RevokedError - Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( - :invalid_token, - "Token was revoked. You have to re-authorize from the user.") - - when InsufficientScopeError - # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2) - # does not include WWW-Authenticate header, which breaks the standard. - Rack::OAuth2::Server::Resource::Bearer::Forbidden.new( - :insufficient_scope, - Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope], - { scope: e.scopes }) + else + case validate_access_token(access_token, scopes) + when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE + raise InsufficientScopeError.new(scopes) + when Oauth2::AccessTokenValidationService::EXPIRED + raise ExpiredError + when Oauth2::AccessTokenValidationService::REVOKED + raise RevokedError + when Oauth2::AccessTokenValidationService::VALID + @current_user = User.find(access_token.resource_owner_id) end + end + end - response.finish + def doorkeeper_guard(scopes: []) + if access_token = find_access_token + case validate_access_token(access_token, scopes) + when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE + raise InsufficientScopeError.new(scopes) + + when Oauth2::AccessTokenValidationService::EXPIRED + raise ExpiredError + + when Oauth2::AccessTokenValidationService::REVOKED + raise RevokedError + + when Oauth2::AccessTokenValidationService::VALID + @current_user = User.find(access_token.resource_owner_id) + end + end + end + + def current_user + @current_user + end + + private + def find_access_token + @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods) + end + + def doorkeeper_request + @doorkeeper_request ||= ActionDispatch::Request.new(env) + end + + def validate_access_token(access_token, scopes) + Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes) + end + end + + module ClassMethods + # Installs the doorkeeper guard on the whole Grape API endpoint. + # + # Arguments: + # + # scopes: (optional) scopes required for this guard. + # Defaults to empty array. + # + def guard_all!(scopes: []) + before do + guard! scopes: scopes + end + end + + private + def install_error_responders(base) + error_classes = [ MissingTokenError, TokenNotFoundError, + ExpiredError, RevokedError, InsufficientScopeError] + + base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler + end + + def oauth2_bearer_token_error_handler + Proc.new do |e| + response = + case e + when MissingTokenError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new + + when TokenNotFoundError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( + :invalid_token, + "Bad Access Token.") + + when ExpiredError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( + :invalid_token, + "Token is expired. You can either do re-authorization or token refresh.") + + when RevokedError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( + :invalid_token, + "Token was revoked. You have to re-authorize from the user.") + + when InsufficientScopeError + # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2) + # does not include WWW-Authenticate header, which breaks the standard. + Rack::OAuth2::Server::Resource::Bearer::Forbidden.new( + :insufficient_scope, + Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope], + { scope: e.scopes }) + end + + response.finish + end + end + end + + # + # Exceptions + # + + class MissingTokenError < StandardError; end + + class TokenNotFoundError < StandardError; end + + class ExpiredError < StandardError; end + + class RevokedError < StandardError; end + + class InsufficientScopeError < StandardError + attr_reader :scopes + def initialize(scopes) + @scopes = scopes end end end - - # - # Exceptions - # - - class MissingTokenError < StandardError; end - - class TokenNotFoundError < StandardError; end - - class ExpiredError < StandardError; end - - class RevokedError < StandardError; end - - class InsufficientScopeError < StandardError - attr_reader :scopes - def initialize(scopes) - @scopes = scopes - end - end -end +end \ No newline at end of file diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb index 353c4ddebf8..7cd8b6fbae2 100644 --- a/lib/ci/api/api.rb +++ b/lib/ci/api/api.rb @@ -3,7 +3,7 @@ Dir["#{Rails.root}/lib/ci/api/*.rb"].each {|file| require file} module Ci module API class API < Grape::API - include APIGuard + include ::API::APIGuard version 'v1', using: :path rescue_from ActiveRecord::RecordNotFound do From 9ef50db6279d722caed1ab1e4576275428e6a94f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 29 Apr 2016 18:56:53 +0200 Subject: [PATCH 0081/1431] Specify that oauth cannot push code --- spec/requests/git_http_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 20c7357cba5..14d126480a3 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -179,6 +179,25 @@ describe 'Git HTTP requests', lib: true do end end + context "when an oauth token is provided" do + before do + application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) + @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id) + end + + it "downloads get status 200" do + clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token + + expect(response.status).to eq(200) + end + + it "uploads get status 401 (no project existence information leak)" do + push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token + + expect(response.status).to eq(401) + end + end + context "when blank password attempts follow a valid login" do def attempt_login(include_password) password = include_password ? user.password : "" From b1ffc9f0fee16251899e5a2efbc78c4781ef4902 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 29 Apr 2016 18:58:55 +0200 Subject: [PATCH 0082/1431] Make CI/Oauth/rate limiting reusable --- .../projects/git_http_controller.rb | 78 ++----------- config/initializers/doorkeeper.rb | 2 +- lib/api/session.rb | 8 +- lib/gitlab/auth.rb | 103 ++++++++++++++++-- lib/gitlab/backend/grack_auth.rb | 2 +- spec/lib/gitlab/auth_spec.rb | 56 ++++++++-- 6 files changed, 156 insertions(+), 93 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index fafd9e445b5..16a85d6f62b 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -41,11 +41,15 @@ class Projects::GitHttpController < Projects::ApplicationController return if project && project.public? && upload_pack? authenticate_or_request_with_http_basic do |login, password| - return @ci = true if valid_ci_request?(login, password) + user, type = Gitlab::Auth.find(login, password, project: project, ip: request.ip) - @user = Gitlab::Auth.new.find(login, password) - @user ||= oauth_access_token_check(login, password) - rate_limit_ip!(login, @user) + if (type == :ci) && upload_pack? + @ci = true + elsif (type == :oauth) && !upload_pack? + @user = nil + else + @user = user + end end end @@ -53,72 +57,6 @@ class Projects::GitHttpController < Projects::ApplicationController render_not_found if project.blank? end - def valid_ci_request?(login, password) - matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) - - unless project && matched_login.present? && upload_pack? - return false - end - - underscored_service = matched_login['service'].underscore - - if underscored_service == 'gitlab_ci' - project && project.valid_build_token?(password) - elsif Service.available_services_names.include?(underscored_service) - # We treat underscored_service as a trusted input because it is included - # in the Service.available_services_names whitelist. - service_method = "#{underscored_service}_service" - service = project.send(service_method) - - service && service.activated? && service.valid_token?(password) - end - end - - def oauth_access_token_check(login, password) - if login == "oauth2" && upload_pack? && password.present? - token = Doorkeeper::AccessToken.by_token(password) - token && token.accessible? && User.find_by(id: token.resource_owner_id) - end - end - - def rate_limit_ip!(login, user) - # If the user authenticated successfully, we reset the auth failure count - # from Rack::Attack for that IP. A client may attempt to authenticate - # with a username and blank password first, and only after it receives - # a 401 error does it present a password. Resetting the count prevents - # false positives from occurring. - # - # Otherwise, we let Rack::Attack know there was a failed authentication - # attempt from this IP. This information is stored in the Rails cache - # (Redis) and will be used by the Rack::Attack middleware to decide - # whether to block requests from this IP. - - config = Gitlab.config.rack_attack.git_basic_auth - return user unless config.enabled - - if user - # A successful login will reset the auth failure count from this IP - Rack::Attack::Allow2Ban.reset(request.ip, config) - else - banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do - # Unless the IP is whitelisted, return true so that Allow2Ban - # increments the counter (stored in Rails.cache) for the IP - if config.ip_whitelist.include?(request.ip) - false - else - true - end - end - - if banned - Rails.logger.info "IP #{request.ip} failed to login " \ - "as #{login} but has been temporarily banned from Git auth" - end - end - - user - end - def project return @project if defined?(@project) diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 66ac88e9f4a..0c694e0d37a 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -12,7 +12,7 @@ Doorkeeper.configure do end resource_owner_from_credentials do |routes| - Gitlab::Auth.new.find(params[:username], params[:password]) + Gitlab::Auth.find_by_master_or_ldap(params[:username], params[:password]) end # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. diff --git a/lib/api/session.rb b/lib/api/session.rb index cc646895914..e308ccc3004 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -11,8 +11,12 @@ module API # Example Request: # POST /session post "/session" do - auth = Gitlab::Auth.new - user = auth.find(params[:email] || params[:login], params[:password]) + user, _ = Gitlab::Auth.find( + params[:email] || params[:login], + params[:password], + project: nil, + ip: request.ip + ) return unauthorized! unless user present user, with: Entities::UserLogin diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 30509528b8b..32e903905ad 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,17 +1,100 @@ module Gitlab class Auth - def find(login, password) - user = User.by_login(login) + class << self + def find(login, password, project:, ip:) + raise "Must provide an IP for rate limiting" if ip.nil? - # If no user is found, or it's an LDAP server, try LDAP. - # LDAP users are only authenticated via LDAP - if user.nil? || user.ldap_user? - # Second chance - try LDAP authentication - return nil unless Gitlab::LDAP::Config.enabled? + user, type = nil, nil - Gitlab::LDAP::Authentication.login(login, password) - else - user if user.valid_password?(password) + if valid_ci_request?(login, password, project) + type = :ci + elsif user = find_by_master_or_ldap(login, password) + type = :master_or_ldap + elsif user = oauth_access_token_check(login, password) + type = :oauth + end + + rate_limit!(ip, success: !!user || (type == :ci), login: login) + [user, type] + end + + def find_by_master_or_ldap(login, password) + user = User.by_login(login) + + # If no user is found, or it's an LDAP server, try LDAP. + # LDAP users are only authenticated via LDAP + if user.nil? || user.ldap_user? + # Second chance - try LDAP authentication + return nil unless Gitlab::LDAP::Config.enabled? + + Gitlab::LDAP::Authentication.login(login, password) + else + user if user.valid_password?(password) + end + end + + private + + def valid_ci_request?(login, password, project) + matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) + + return false unless project && matched_login.present? + + underscored_service = matched_login['service'].underscore + + if underscored_service == 'gitlab_ci' + project && project.valid_build_token?(password) + elsif Service.available_services_names.include?(underscored_service) + # We treat underscored_service as a trusted input because it is included + # in the Service.available_services_names whitelist. + service_method = "#{underscored_service}_service" + service = project.send(service_method) + + service && service.activated? && service.valid_token?(password) + end + end + + def oauth_access_token_check(login, password) + if login == "oauth2" && password.present? + token = Doorkeeper::AccessToken.by_token(password) + token && token.accessible? && User.find_by(id: token.resource_owner_id) + end + end + + def rate_limit!(ip, success:, login:) + # If the user authenticated successfully, we reset the auth failure count + # from Rack::Attack for that IP. A client may attempt to authenticate + # with a username and blank password first, and only after it receives + # a 401 error does it present a password. Resetting the count prevents + # false positives from occurring. + # + # Otherwise, we let Rack::Attack know there was a failed authentication + # attempt from this IP. This information is stored in the Rails cache + # (Redis) and will be used by the Rack::Attack middleware to decide + # whether to block requests from this IP. + + config = Gitlab.config.rack_attack.git_basic_auth + return unless config.enabled + + if success + # A successful login will reset the auth failure count from this IP + Rack::Attack::Allow2Ban.reset(ip, config) + else + banned = Rack::Attack::Allow2Ban.filter(ip, config) do + # Unless the IP is whitelisted, return true so that Allow2Ban + # increments the counter (stored in Rails.cache) for the IP + if config.ip_whitelist.include?(ip) + false + else + true + end + end + + if banned + Rails.logger.info "IP #{ip} failed to login " \ + "as #{login} but has been temporarily banned from Git auth" + end + end end end end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index e2363b91265..b263a27d4d3 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -95,7 +95,7 @@ module Grack end def authenticate_user(login, password) - user = Gitlab::Auth.new.find(login, password) + user, _ = Gitlab::Auth.new.find_by_master_or_ldap(login, password) unless user user = oauth_access_token_check(login, password) diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index aad291c03cd..2c2f7ed0665 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -1,9 +1,47 @@ require 'spec_helper' describe Gitlab::Auth, lib: true do - let(:gl_auth) { Gitlab::Auth.new } + let(:gl_auth) { described_class } - describe :find do + describe 'find' do + it 'recognizes CI' do + token = '123' + project = create(:empty_project) + project.update_attributes(runners_token: token, builds_enabled: true) + ip = 'ip' + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token') + expect(gl_auth.find('gitlab-ci-token', token, project: project, ip: ip)).to eq([nil, :ci]) + end + + it 'recognizes master passwords' do + user = create(:user, password: 'password') + ip = 'ip' + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) + expect(gl_auth.find(user.username, 'password', project: nil, ip: ip)).to eq([user, :master_or_ldap]) + end + + it 'recognizes OAuth tokens' do + user = create(:user) + application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) + token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id) + ip = 'ip' + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2') + expect(gl_auth.find("oauth2", token.token, project: nil, ip: ip)).to eq([user, :oauth]) + end + + it 'returns double nil for invalid credentials' do + login = 'foo' + ip = 'ip' + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) + expect(gl_auth.find(login, 'bar', project: nil, ip: ip)).to eq ([nil, nil]) + end + end + + describe 'find_by_master_or_ldap' do let!(:user) do create(:user, username: username, @@ -14,25 +52,25 @@ describe Gitlab::Auth, lib: true do let(:password) { 'my-secret' } it "should find user by valid login/password" do - expect( gl_auth.find(username, password) ).to eql user + expect( gl_auth.find_by_master_or_ldap(username, password) ).to eql user end it 'should find user by valid email/password with case-insensitive email' do - expect(gl_auth.find(user.email.upcase, password)).to eql user + expect(gl_auth.find_by_master_or_ldap(user.email.upcase, password)).to eql user end it 'should find user by valid username/password with case-insensitive username' do - expect(gl_auth.find(username.upcase, password)).to eql user + expect(gl_auth.find_by_master_or_ldap(username.upcase, password)).to eql user end it "should not find user with invalid password" do password = 'wrong' - expect( gl_auth.find(username, password) ).not_to eql user + expect( gl_auth.find_by_master_or_ldap(username, password) ).not_to eql user end it "should not find user with invalid login" do user = 'wrong' - expect( gl_auth.find(username, password) ).not_to eql user + expect( gl_auth.find_by_master_or_ldap(username, password) ).not_to eql user end context "with ldap enabled" do @@ -43,13 +81,13 @@ describe Gitlab::Auth, lib: true do it "tries to autheticate with db before ldap" do expect(Gitlab::LDAP::Authentication).not_to receive(:login) - gl_auth.find(username, password) + gl_auth.find_by_master_or_ldap(username, password) end it "uses ldap as fallback to for authentication" do expect(Gitlab::LDAP::Authentication).to receive(:login) - gl_auth.find('ldap_user', 'password') + gl_auth.find_by_master_or_ldap('ldap_user', 'password') end end end From b47a84b4be5781c91bf1072dcfa765b6782f0a35 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 28 Apr 2016 21:44:21 +0200 Subject: [PATCH 0083/1431] Added performance guidelines Fixes gitlab-org/gitlab-ce#15254 gitlab-org/gitlab-ce#14277 [ci skip] --- doc/development/README.md | 1 + doc/development/performance.md | 258 +++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 doc/development/performance.md diff --git a/doc/development/README.md b/doc/development/README.md index 3f3ef068f96..aa7d54c01d0 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -8,6 +8,7 @@ - [How to dump production data to staging](db_dump.md) - [Instrumentation](instrumentation.md) - [Migration Style Guide](migration_style_guide.md) for creating safe migrations +- [Performance guidelines](performance.md) - [Rake tasks](rake_tasks.md) for development - [Shell commands](shell_commands.md) in the GitLab codebase - [Sidekiq debugging](sidekiq_debugging.md) diff --git a/doc/development/performance.md b/doc/development/performance.md new file mode 100644 index 00000000000..c74198650e5 --- /dev/null +++ b/doc/development/performance.md @@ -0,0 +1,258 @@ +# Performance Guidelines + +This document describes various guidelines to follow to ensure good and +consistent performance of GitLab. + +## Workflow + +The process of solving performance problems is roughly as follows: + +1. Make sure there's an issue open somewhere (e.g. on the GitLab CE issue + tracker), create one if there isn't. See [#15607][#15607] for an example. +2. Measure the performance of the code in a production environment such as + GitLab.com (see the [Tooling](#tooling) section below). Performance should be + measured over a period of _at least_ 24 hours. +3. Add your findings based on the measurement period (screenshots of graphs, + timings, etc) to the issue mentioned in step 1. +4. Solve the problem. +5. Create a merge request, assign the "performance" label and ping the right + people (e.g. [@yorickpeterse][@yorickpeterse] and [@joshfng][@joshfng]). +6. Once a change has been deployed make sure to _again_ measure for at least 24 + hours to see if your changes have any impact on the production environment. +7. Repeat until you're done. + +When providing timings make sure to provide: + +* The 95th percentile +* The 99th percentile +* The mean + +When providing screenshots of graphs make sure that both the X and Y axes and +the legend are clearly visible. If you happen to have access to GitLab.com's own +monitoring tools you should also provide a link to any relevant +graphs/dashboards. + +## Tooling + +GitLab provides two built-in tools to aid the process of improving performance: + +* [Sherlock](doc/development/profiling.md#sherlock) +* [GitLab Performance Monitoring](doc/monitoring/performance) + +GitLab employees can use GitLab.com's performance monitoring systems located at +, this requires you to log in using your +`@gitlab.com` Email address. Non-GitLab employees are advised to set up their +own InfluxDB + Grafana stack. + +## Benchmarks + +Benchmarks are almost always useless. Benchmarks usually only test small bits of +code in isolation and often only measure the best case scenario. On top of that, +benchmarks for libraries (e.g. a Gem) tend to be biased in favour of the +library. After all there's little benefit to an author publishing a benchmark +that shows they perform worse than their competitors. + +Benchmarks are only really useful when you need a rough (emphasis on "rough") +understanding of the impact of your changes. For example, if a certain method is +slow a benchmark can be used to see if the changes you're making have any impact +on the method's performance. However, even when a benchmark shows your changes +improve performance there's no guarantee the performance also improves in a +production environment. + +When writing benchmarks you should almost always use +[benchmark-ips](https://github.com/evanphx/benchmark-ips). Ruby's `Benchmark` +module that comes with the standard library is rarely useful as it runs either a +single iteration (when using `Benchmark.bm`) or two iterations (when using +`Benchmark.bmbm`). Running this few iterations means external factors (e.g. a +video streaming in the background) can very easily skew the benchmark +statistics. + +Another problem with the `Benchmark` module is that it displays timings, not +iterations. This means that if a piece of code completes in a very short period +of time it can be very difficult to compare the timings before and after a +certain change. This in turn leads to patterns such as the following: + +```ruby +Benchmark.bmbm(10) do |bench| + bench.report 'do something' do + 100.times do + ... work here ... + end + end +end +``` + +This however leads to the question: how many iterations should we run to get +meaningful statistics? + +The benchmark-ips Gem basically takes care of all this and much more, and as a +result of this should be used instead of the `Benchmark` module. + +In short: + +1. Don't trust benchmarks you find on the internet. +2. Never make claims based on just benchmarks, always measure in production to + confirm your findings. +3. X being N times faster than Y is meaningless if you don't know what impact it + will actually have on your production environment. +4. A production environment is the _only_ benchmark that always tells the truth + (unless your performance monitoring systems are not set up correctly). +5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's + `Benchmark` module. + +## Importance of Changes + +When working on performance improvements it's important to always ask yourself +the question "How important is it to improve the performance of this piece of +code?". Not every piece of code is equally important and it would be a waste to +spend a week trying to improve something that only impacts a tiny fraction of +our users. For example, spending a week trying to squeeze 10 milliseconds out of +a method is a waste of time when you could have spent a week squeezing out 10 +seconds elsewhere. + +There is no clear set of steps that you can follow to determine if a certain +piece of code is worth optimizing. The only two things you can do are: + +1. Think about what the code does, how it's used, how many times it's called and + how much time is spent in it relative to the total execution time (e.g. the + total time spent in a web request). +2. Ask others (preferably in the form of an issue). + +Some examples of changes that aren't really important/worth the effort: + +* Replacing double quotes with single quotes. +* Replacing usage of Array with Set when the list of values is very small. +* Replacing library A with library B when both only take up 0.1% of the total + execution time. +* Calling `freeze` on every string (see [String Freezing](#string-freezing)). + +## Slow Operations & Sidekiq + +Slow operations (e.g. merging branches) or operations that are prone to errors +(using external APIs) should be performed in a Sidekiq worker instead of +directly in a web request as much as possible. This has numerous benefits such +as: + +1. An error won't prevent the request from completing. +2. The process being slow won't affect the loading time of a page. +3. In case of a failure it's easy to re-try the process (Sidekiq takes care of + this automatically). +4. By isolating the code from a web request it will hopefully be easier to test + and maintain. + +It's especially important to use Sidekiq as much as possible when dealing with +Git operations as these operations can take quite some time to complete +depending on the performance of the underlying storage system. + +## Git Operations + +Care should be taken to not run unnecessary Git operations. For example, +retrieving the list of branch names using `Repository#branch_names` can be done +without an explicit check if a repository exists or not. In other words, instead +of this: + +```ruby +if repository.exists? + repository.branch_names.each do |name| + ... + end +end +``` + +You can instead just write: + +```ruby +repository.branch_names.each do |name| + ... +end +``` + +## Caching + +Operations that will often return the same result should be cached using Redis, +in particular Git operations. When caching data in Redis make sure the cache is +flushed whenever needed. For example, a cache for the list of tags should be +flushed whenever a new tag is pushed or a tag is removed. + +When adding cache expiration code for repositories this code should be placed in +one of the before/after hooks residing in the Repository class. For example, if +a cache should be flushed after importing a repository this code should be added +to `Repository#after_import`. This ensures the cache logic stays within the +Repository class instead of leaking into other classes. + +When caching data make sure to also memoize the result in an instance variable. +While retrieving data from Redis is much faster than raw Git operations it still +has overhead. By caching the result in an instance variable repeated calls to +the same method won't end up retrieving data from Redis upon every call. When +memoizing cached data in an instance variable make sure to also reset the +instance variable when flushing the cache. An example: + + +```ruby +def first_branch + @first_branch ||= cache.fetch(:first_branch) { branches.first } +end + +def expire_first_branch_cache + cache.expire(:first_branch) + @first_branch = nil +end +``` + +## Anti-Patterns + +This is a collection of [anti-patterns][anti-pattern] that should be avoided +unless these changes have a measurable, significant and positive impact on +production environments. + +### String Freezing + +In recent Ruby versions calling `freeze` on a String leads to it being allocated +only once and re-used. For example, on Ruby 2.3 this will only allocate the +"foo" String once: + +```ruby +10.times do + 'foo'.freeze +end +``` + +Blindly adding a `.freeze` call to every String is an anti-pattern that should +be avoided unless one can prove (using production data) the call actually has a +positive impact on performance. + +This feature of Ruby wasn't really meant to make things faster directly, instead +it was meant to reduce the number of allocations. Depending on the size of the +String and how frequently it would be allocated (before the `.freeze` call was +added) this _may_ make things faster, but there's no guarantee it will. + +Another common flavour of this is to not only freeze a String but also assign it +to a constant, for example: + +```ruby +SOME_CONSTANT = 'foo'.freeze + +9000.times do + SOME_CONSTANT +end +``` + +The only reason you should be doing this is to prevent somebody from mutating +the global String. However, since you can just re-assign constants in Ruby +there's nothing stopping somebody from doing this elsewhere in the code: + +```ruby +SOME_CONSTANT = 'bar' +``` + +### Moving Allocations to Constants + +Storing an object as a constant so you only allocate it once _may_ improve +performance but there's no guarantee this will. Looking up constants has an +impact on runtime performance and as such using a constant instead of +referencing an object directly may even slow code down. + +[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 +[@yorickpeterse]: https://gitlab.com/u/yorickpeterse +[@joshfng]: https://gitlab.com/u/joshfng +[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern From 6243a834115c9c815fe9a1224824f02f9fb3857f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 30 Apr 2016 22:36:26 -0700 Subject: [PATCH 0084/1431] Bump mail_room to 0.7.0 to fix stuck IDLE connections Closes #13357 --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 121b03be320..7b34095af9f 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.8.0 (unreleased) - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Make build status canceled if any of the jobs was canceled and none failed + - Bump mail_room to 0.7.0 to fix stuck IDLE connections - Remove future dates from contribution calendar graph. - Use ActionDispatch Remote IP for Akismet checking - Fix error when visiting commit builds page before build was updated diff --git a/Gemfile b/Gemfile index 25c13fda575..8210f4cb3d5 100644 --- a/Gemfile +++ b/Gemfile @@ -319,7 +319,7 @@ gem "newrelic_rpm", '~> 3.14' gem 'octokit', '~> 4.3.0' -gem "mail_room", "~> 0.6.1" +gem "mail_room", "~> 0.7" gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index b1e954e0884..5ea4347112c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -465,7 +465,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.6.1) + mail_room (0.7.0) method_source (0.8.2) mime-types (2.99.1) mimemagic (0.3.0) @@ -961,7 +961,7 @@ DEPENDENCIES letter_opener_web (~> 1.3.0) licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.6.1) + mail_room (~> 0.7) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) From 41df76b028aa663711b8126b2aabd7f36f254153 Mon Sep 17 00:00:00 2001 From: Karlo Soriano Date: Sun, 1 May 2016 14:43:52 +0800 Subject: [PATCH 0085/1431] Remove unnecessary method call in events view Since `User#to_param` already returns `User#username`, we don't need to pass in the user's username. Changing this also helps us obey LoD. --- app/views/events/_event.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 5d622582088..e4629bae0e6 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -5,7 +5,7 @@ = cache [event, current_application_settings, "v2.2"] do - if event.author - = link_to user_path(event.author.username) do + = link_to user_path(event.author) do = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:'' - else = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:'' From 66d7acfe4ddec5d5f16bc6a7d758343ff8573fd9 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sun, 1 May 2016 13:44:50 -0600 Subject: [PATCH 0086/1431] Update rspec-rails from 3.3.3 to 3.4.2. This allows the removal of the monkey patch from this commit: 47ff1c56089b3df9c36b77c02f0f3db54fea1d54 It'll also make it slightly easier to upgrade to 3.5.0 later. Changelog: https://github.com/rspec/rspec-rails/blob/master/Changelog.md#340--2015-11-11 --- Gemfile | 2 +- Gemfile.lock | 36 ++++++++++++++++++------------------ spec/spec_helper.rb | 6 ------ 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Gemfile b/Gemfile index 25c13fda575..c10fe0a1c7c 100644 --- a/Gemfile +++ b/Gemfile @@ -269,7 +269,7 @@ group :development, :test do gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails', '~> 4.6.0' - gem 'rspec-rails', '~> 3.3.0' + gem 'rspec-rails', '~> 3.4.0' gem 'rspec-retry' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' diff --git a/Gemfile.lock b/Gemfile.lock index b1e954e0884..b19dc5b6d20 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -662,29 +662,29 @@ GEM chunky_png rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) - rspec (3.3.0) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-core (3.3.2) - rspec-support (~> 3.3.0) - rspec-expectations (3.3.1) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.4) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-mocks (3.3.2) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-rails (3.3.3) + rspec-support (~> 3.4.0) + rspec-rails (3.4.2) actionpack (>= 3.0, < 4.3) activesupport (>= 3.0, < 4.3) railties (>= 3.0, < 4.3) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-support (~> 3.3.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-support (~> 3.4.0) rspec-retry (0.4.5) rspec-core - rspec-support (3.3.0) + rspec-support (3.4.1) rubocop (0.38.0) parser (>= 2.3.0.6, < 3.0) powerpack (~> 0.1) @@ -1011,7 +1011,7 @@ DEPENDENCIES responders (~> 2.0) rouge (~> 1.10.1) rqrcode-rails3 (~> 0.1.7) - rspec-rails (~> 3.3.0) + rspec-rails (~> 3.4.0) rspec-retry rubocop (~> 0.38.0) ruby-fogbugz (~> 0.2.1) @@ -1058,4 +1058,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.11.2 + 1.12.1 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 596d607f2a1..576d16e7ea3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -51,10 +51,4 @@ FactoryGirl::SyntaxRunner.class_eval do include RSpec::Mocks::ExampleMethods end -# Work around a Rails 4.2.5.1 issue -# See https://github.com/rspec/rspec-rails/issues/1532 -RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do - alias_method :find_all_anywhere, :find_all -end - ActiveRecord::Migration.maintain_test_schema! From c0f02aad4a1a178109a235d34bd70218c0aec86c Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Mon, 2 May 2016 16:37:12 +0700 Subject: [PATCH 0087/1431] Add snippet tab under user profile --- app/assets/javascripts/user_tabs.js.coffee | 9 ++++++++- app/controllers/users_controller.rb | 22 ++++++++++++++++++++++ app/views/users/show.html.haml | 6 ++++++ config/routes.rb | 5 +++-- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee index 09b7eec9104..aa798b96ede 100644 --- a/app/assets/javascripts/user_tabs.js.coffee +++ b/app/assets/javascripts/user_tabs.js.coffee @@ -26,6 +26,10 @@ # Personal projects # # +#
  • +# +# +#
  • # # #
    @@ -41,6 +45,9 @@ #
    # Projects content #
    +#
    +# Snippets content +#
    #
    # #
    @@ -100,7 +107,7 @@ class @UserTabs if action is 'activity' @loadActivities(source) - if action in ['groups', 'contributed', 'projects'] + if action in ['groups', 'contributed', 'projects', 'snippets'] @loadTab(source, action) loadTab: (source, action) -> diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2ae180c8a12..799421c185b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -58,6 +58,19 @@ class UsersController < ApplicationController end end + def snippets + load_snippets + + respond_to do |format| + format.html { render 'show' } + format.json do + render json: { + html: view_to_html_string("snippets/_snippets", collection: @snippets) + } + end + end + end + def calendar calendar = contributions_calendar @timestamps = calendar.timestamps @@ -116,6 +129,15 @@ class UsersController < ApplicationController @groups = JoinedGroupsFinder.new(user).execute(current_user) end + def load_snippets + @snippets = SnippetsFinder.new.execute( + current_user, + filter: :by_user, + user: user, + scope: params[:scope] + ).page(params[:page]) + end + def projects_for_current_user ProjectsFinder.new.execute(current_user) end diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 3028491e5b6..a453a7fedb7 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -81,6 +81,9 @@ %li.projects-tab = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do Personal projects + %li.snippets-tab + = link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do + Snippets %div{ class: container_class } .tab-content @@ -104,6 +107,9 @@ #projects.tab-pane - # This tab is always loaded via AJAX + #snippets.tab-pane + - # This tab is always loaded via AJAX + .loading-status = spinner diff --git a/config/routes.rb b/config/routes.rb index 2f820aafed1..f6a41331ecf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,8 +89,6 @@ Rails.application.routes.draw do end end - get '/s/:username' => 'snippets#index', as: :user_snippets, constraints: { username: /.*/ } - # # Invites # @@ -355,6 +353,9 @@ Rails.application.routes.draw do get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects, constraints: { username: /.*/ } + get 'u/:username/snippets' => 'users#snippets', as: :user_snippets, + constraints: { username: /.*/ } + get '/u/:username' => 'users#show', as: :user, constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Mon, 2 May 2016 13:19:39 +0200 Subject: [PATCH 0088/1431] Use correct auth finder --- lib/api/session.rb | 7 +------ lib/gitlab/backend/grack_auth.rb | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/api/session.rb b/lib/api/session.rb index e308ccc3004..1156aab8cc2 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -11,12 +11,7 @@ module API # Example Request: # POST /session post "/session" do - user, _ = Gitlab::Auth.find( - params[:email] || params[:login], - params[:password], - project: nil, - ip: request.ip - ) + user = Gitlab::Auth.find_by_master_or_ldap(params[:email] || params[:login], params[:password]) return unauthorized! unless user present user, with: Entities::UserLogin diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index b263a27d4d3..3462c2dcfbc 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -95,7 +95,7 @@ module Grack end def authenticate_user(login, password) - user, _ = Gitlab::Auth.new.find_by_master_or_ldap(login, password) + user = Gitlab::Auth.new.find_by_master_or_ldap(login, password) unless user user = oauth_access_token_check(login, password) From 9ce099429972726da22253407d98ae8aa1ef167b Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 2 May 2016 13:21:59 +0200 Subject: [PATCH 0089/1431] Rubocop and whitespace --- lib/gitlab/workhorse.rb | 4 ++-- spec/lib/gitlab/auth_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 5b2982e4994..f9ceee142d7 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -36,9 +36,9 @@ module Gitlab "git-archive:#{encode(params)}", ] end - + protected - + def encode(hash) Base64.urlsafe_encode64(JSON.dump(hash)) end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 2c2f7ed0665..16083f90bb4 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -37,7 +37,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) - expect(gl_auth.find(login, 'bar', project: nil, ip: ip)).to eq ([nil, nil]) + expect(gl_auth.find(login, 'bar', project: nil, ip: ip)).to eq([nil, nil]) end end From 105017c3084c60e45f4bac85a76da78f39e6433f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 2 May 2016 13:29:17 +0200 Subject: [PATCH 0090/1431] Added JWT controller --- Gemfile | 1 + Gemfile.lock | 2 + app/controllers/jwt_controller.rb | 173 ++++++++++++++++++++++++++++++ config/routes.rb | 3 + 4 files changed, 179 insertions(+) create mode 100644 app/controllers/jwt_controller.rb diff --git a/Gemfile b/Gemfile index 512c6babd7e..0301f6fe062 100644 --- a/Gemfile +++ b/Gemfile @@ -225,6 +225,7 @@ gem 'request_store', '~> 1.3.0' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' gem 'net-ssh', '~> 3.0.1' +gem 'base32', '~> 0.3.0' # Sentry integration gem 'sentry-raven', '~> 0.15' diff --git a/Gemfile.lock b/Gemfile.lock index 2b578429b3c..2b1cfdc9bb2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -74,6 +74,7 @@ GEM ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) babosa (1.0.2) + base32 (0.3.2) bcrypt (3.1.10) benchmark-ips (2.3.0) better_errors (1.0.1) @@ -897,6 +898,7 @@ DEPENDENCIES attr_encrypted (~> 1.3.4) awesome_print (~> 1.2.0) babosa (~> 1.0.2) + base32 (~> 0.3.0) benchmark-ips better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb new file mode 100644 index 00000000000..7e70c70c89c --- /dev/null +++ b/app/controllers/jwt_controller.rb @@ -0,0 +1,173 @@ +class JwtController < ApplicationController + skip_before_action :authenticate_user! + skip_before_action :verify_authenticity_token + + def auth + @authenticated = authenticate_with_http_basic do |login, password| + @ci_project = ci_project(login, password) + @user = authenticate_user(login, password) unless @ci_project + end + + unless @authenticated + return render_403 if has_basic_credentials? + end + + case params[:service] + when 'docker' + docker_token_auth(params[:scope], params[:offline_token]) + else + return render_404 + end + end + + private + + def render_400 + head :invalid_request + end + + def render_404 + head :not_found + end + + def render_403 + head :forbidden + end + + def docker_token_auth(scope, offline_token) + payload = { + aud: params[:service], + sub: @user.try(:username) + } + + if offline_token + return render_403 unless @user + elsif scope + access = process_access(scope) + return render_404 unless access + payload[:access] = [access] + end + + render json: { token: encode(payload) } + end + + def ci_project(login, password) + matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) + + if matched_login.present? + underscored_service = matched_login['s'].underscore + + if underscored_service == 'gitlab_ci' + Project.find_by(builds_enabled: true, runners_token: password) + end + end + end + + def authenticate_user(login, password) + user = Gitlab::Auth.new.find(login, password) + + # If the user authenticated successfully, we reset the auth failure count + # from Rack::Attack for that IP. A client may attempt to authenticate + # with a username and blank password first, and only after it receives + # a 401 error does it present a password. Resetting the count prevents + # false positives from occurring. + # + # Otherwise, we let Rack::Attack know there was a failed authentication + # attempt from this IP. This information is stored in the Rails cache + # (Redis) and will be used by the Rack::Attack middleware to decide + # whether to block requests from this IP. + config = Gitlab.config.rack_attack.git_basic_auth + + if config.enabled + if user + # A successful login will reset the auth failure count from this IP + Rack::Attack::Allow2Ban.reset(request.ip, config) + else + banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do + # Unless the IP is whitelisted, return true so that Allow2Ban + # increments the counter (stored in Rails.cache) for the IP + if config.ip_whitelist.include?(request.ip) + false + else + true + end + end + + if banned + Rails.logger.info "IP #{request.ip} failed to login " \ + "as #{login} but has been temporarily banned from Git auth" + end + end + end + + user + end + + def process_access(scope) + type, name, actions = scope.split(':', 3) + actions = actions.split(',') + + case type + when 'repository' + process_repository_access(type, name, actions) + end + end + + def process_repository_access(type, name, actions) + project = Project.find_with_namespace(name) + return unless project + + actions = actions.select do |action| + can_access?(project, action) + end + + { type: 'repository', name: name, actions: actions } if actions + end + + def default_payload + { + aud: 'docker', + sub: @user.try(:username), + aud: params[:service], + } + end + + def private_key + @private_key ||= OpenSSL::PKey::RSA.new File.read Gitlab.config.registry.key + end + + def encode(payload) + issued_at = Time.now + payload = payload.merge( + iss: Gitlab.config.registry.issuer, + iat: issued_at.to_i, + nbf: issued_at.to_i - 5.seconds.to_i, + exp: issued_at.to_i + 60.minutes.to_i, + jti: SecureRandom.uuid, + ) + headers = { + kid: kid(private_key) + } + JWT.encode(payload, private_key, 'RS256', headers) + end + + def can_access?(project, action) + case action + when 'pull' + project == @ci_project || can?(@user, :download_code, project) + when 'push' + project == @ci_project || can?(@user, :push_code, project) + else + false + end + end + + def kid(private_key) + sha256 = Digest::SHA256.new + sha256.update(private_key.public_key.to_der) + payload = StringIO.new(sha256.digest).read(30) + Base32.encode(payload).split('').each_slice(4).each_with_object([]) do |slice, mem| + mem << slice.join + end.join(':') + end +end diff --git a/config/routes.rb b/config/routes.rb index adf4bb18b3c..5b48819dd9d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -63,6 +63,9 @@ Rails.application.routes.draw do get 'search' => 'search#show' get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete + # JSON Web Token + get 'jwt/auth' => 'jwt#auth' + # API API::API.logger Rails.logger mount API::API => '/api' From 011a905a821e2ff0cd2d9885ef93764018eb8346 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 2 May 2016 14:32:16 +0200 Subject: [PATCH 0091/1431] Split docker authentication service --- app/controllers/jwt_controller.rb | 116 +++--------------- .../jwt/docker_authentication_service.rb | 65 ++++++++++ lib/jwt/rsa_token.rb | 36 ++++++ lib/jwt/token.rb | 48 ++++++++ 4 files changed, 163 insertions(+), 102 deletions(-) create mode 100644 app/services/jwt/docker_authentication_service.rb create mode 100644 lib/jwt/rsa_token.rb create mode 100644 lib/jwt/token.rb diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 7e70c70c89c..2a92627cb1b 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -2,6 +2,10 @@ class JwtController < ApplicationController skip_before_action :authenticate_user! skip_before_action :verify_authenticity_token + SERVICES = { + 'docker' => Jwt::DockerAuthenticationService, + } + def auth @authenticated = authenticate_with_http_basic do |login, password| @ci_project = ci_project(login, password) @@ -9,46 +13,22 @@ class JwtController < ApplicationController end unless @authenticated - return render_403 if has_basic_credentials? + head :forbidden if ActionController::HttpAuthentication::Basic.has_basic_credentials?(request) end - case params[:service] - when 'docker' - docker_token_auth(params[:scope], params[:offline_token]) - else - return render_404 - end + service = SERVICES[params[:service]] + head :not_found unless service + + result = service.new(@ci_project, @user, auth_params).execute + return head result[:http_status] if result[:http_status] + + render json: result end private - def render_400 - head :invalid_request - end - - def render_404 - head :not_found - end - - def render_403 - head :forbidden - end - - def docker_token_auth(scope, offline_token) - payload = { - aud: params[:service], - sub: @user.try(:username) - } - - if offline_token - return render_403 unless @user - elsif scope - access = process_access(scope) - return render_404 unless access - payload[:access] = [access] - end - - render json: { token: encode(payload) } + def auth_params + params.permit(:service, :scope, :offline_token, :account, :client_id) end def ci_project(login, password) @@ -102,72 +82,4 @@ class JwtController < ApplicationController user end - - def process_access(scope) - type, name, actions = scope.split(':', 3) - actions = actions.split(',') - - case type - when 'repository' - process_repository_access(type, name, actions) - end - end - - def process_repository_access(type, name, actions) - project = Project.find_with_namespace(name) - return unless project - - actions = actions.select do |action| - can_access?(project, action) - end - - { type: 'repository', name: name, actions: actions } if actions - end - - def default_payload - { - aud: 'docker', - sub: @user.try(:username), - aud: params[:service], - } - end - - def private_key - @private_key ||= OpenSSL::PKey::RSA.new File.read Gitlab.config.registry.key - end - - def encode(payload) - issued_at = Time.now - payload = payload.merge( - iss: Gitlab.config.registry.issuer, - iat: issued_at.to_i, - nbf: issued_at.to_i - 5.seconds.to_i, - exp: issued_at.to_i + 60.minutes.to_i, - jti: SecureRandom.uuid, - ) - headers = { - kid: kid(private_key) - } - JWT.encode(payload, private_key, 'RS256', headers) - end - - def can_access?(project, action) - case action - when 'pull' - project == @ci_project || can?(@user, :download_code, project) - when 'push' - project == @ci_project || can?(@user, :push_code, project) - else - false - end - end - - def kid(private_key) - sha256 = Digest::SHA256.new - sha256.update(private_key.public_key.to_der) - payload = StringIO.new(sha256.digest).read(30) - Base32.encode(payload).split('').each_slice(4).each_with_object([]) do |slice, mem| - mem << slice.join - end.join(':') - end end diff --git a/app/services/jwt/docker_authentication_service.rb b/app/services/jwt/docker_authentication_service.rb new file mode 100644 index 00000000000..ce28085e5d6 --- /dev/null +++ b/app/services/jwt/docker_authentication_service.rb @@ -0,0 +1,65 @@ +module Jwt + class DockerAuthenticationService < BaseService + def execute + if params[:offline_token] + return error('forbidden', 403) unless current_user + end + + { token: token.encoded } + end + + private + + def token + token = ::Jwt::RSAToken.new(registry.key) + token.issuer = registry.issuer + token.audience = params[:service] + token.subject = current_user.try(:username) + token[:access] = access + token + end + + def access + return unless params[:scope] + + scope = process_scope(params[:scope]) + [scope].compact + end + + def process_scope(scope) + type, name, actions = scope.split(':', 3) + actions = actions.split(',') + + case type + when 'repository' + process_repository_access(type, name, actions) + end + end + + def process_repository_access(type, name, actions) + current_project = Project.find_with_namespace(name) + return unless current_project + + actions = actions.select do |action| + can_access?(current_project, action) + end + + { type: type, name: name, actions: actions } if actions + end + + def can_access?(current_project, action) + case action + when 'pull' + current_project == project || can?(current_user, :download_code, current_project) + when 'push' + current_project == project || can?(current_user, :push_code, current_project) + else + false + end + end + + def registry + Gitlab.config.registry + end + end +end diff --git a/lib/jwt/rsa_token.rb b/lib/jwt/rsa_token.rb new file mode 100644 index 00000000000..cc265e3b31a --- /dev/null +++ b/lib/jwt/rsa_token.rb @@ -0,0 +1,36 @@ +module Jwt + class RSAToken < Token + attr_reader :key_file + + def initialize(key_file) + super() + @key_file = key_file + end + + def encoded + headers = { + kid: kid + } + JWT.encode(payload, key, 'RS256', headers) + end + + private + + def key_data + @key_data ||= File.read(key_file) + end + + def key + @key ||= OpenSSL::PKey::RSA.new(key_data) + end + + def kid + sha256 = Digest::SHA256.new + sha256.update(key.public_key.to_der) + payload = StringIO.new(sha256.digest).read(30) + Base32.encode(payload).split('').each_slice(4).each_with_object([]) do |slice, mem| + mem << slice.join + end.join(':') + end + end +end diff --git a/lib/jwt/token.rb b/lib/jwt/token.rb new file mode 100644 index 00000000000..38cbc8004e7 --- /dev/null +++ b/lib/jwt/token.rb @@ -0,0 +1,48 @@ +module Jwt + class Token + attr_accessor :issuer, :subject, :audience, :id + attr_accessor :issued_at, :not_before, :expire_time + + def initialize + @payload = {} + @id = SecureRandom.uuid + @issued_at = Time.now + @not_before = issued_at - 5.seconds + @expire_time = issued_at + 1.minute + end + + def [](key) + @payload[key] + end + + def []=(key, value) + @payload[key] = value + end + + def encoded + raise NotImplementedError + end + + def payload + @payload.merge(default_payload) + end + + def to_json + payload.to_json + end + + private + + def default_payload + { + jti: id, + aud: audience, + sub: subject, + iss: issuer, + iat: issued_at.to_i, + nbf: not_before.to_i, + exp: expire_time.to_i + }.compact + end + end +end \ No newline at end of file From e31b471efc7e9e21011b80a0bce9433a3d899eda Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 May 2016 19:22:48 +0200 Subject: [PATCH 0092/1431] Make files list more compact by reducing row height from 47px to 44px Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/tree.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 25b5e95583e..a84fc2e0318 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -16,7 +16,7 @@ tr { > td, > th { - line-height: 26px; + line-height: 23px; } &:hover { From b0cfd2a9c55e134711079a64a50c00d6fcdd766a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 May 2016 19:37:22 +0200 Subject: [PATCH 0093/1431] Use correct border color between table rows Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/tables.scss | 2 +- app/assets/stylesheets/framework/variables.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 75b770ae5a2..4765a3de515 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -37,8 +37,8 @@ table { } td { + border-bottom: 1px solid; border-color: $table-border-color; - border-bottom: 1px solid $border-color; } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b8ed7e8a74c..92ecfe50488 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,7 +12,7 @@ $gutter_inner_width: 258px; */ $border-color: #e5e5e5; $focus-border-color: #3aabf0; -$table-border-color: #eef0f2; +$table-border-color: #ececec; $background-color: #fafafa; /* From 606e09c141d39ccc3bcfe47ac200eef6df3861a0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 May 2016 19:52:46 +0200 Subject: [PATCH 0094/1431] Fix table borders Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/tables.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 4765a3de515..b42075c98d0 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -32,12 +32,10 @@ table { th { background-color: $background-color; font-weight: normal; - font-size: 15px; - border-bottom: 1px solid $border-color; + border-bottom: none; } td { - border-bottom: 1px solid; border-color: $table-border-color; } } From 3dc276b367fe88c3c1026371d275d6078611f625 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 3 May 2016 11:46:14 +0200 Subject: [PATCH 0095/1431] Remove parallel assignment --- lib/gitlab/auth.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 32e903905ad..0479006f993 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -4,7 +4,8 @@ module Gitlab def find(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? - user, type = nil, nil + user = nil + type = nil if valid_ci_request?(login, password, project) type = :ci From 01aed0c84863ee0271cfb27ab3120ffdf2be7739 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 3 May 2016 12:31:50 +0200 Subject: [PATCH 0096/1431] Dont specify sidebar for group and group settings layouts Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/group.html.haml | 1 - app/views/layouts/group_settings.html.haml | 1 - 2 files changed, 2 deletions(-) diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 7bee7e172e6..f06acc98ca1 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,7 +1,6 @@ - page_title @group.name - page_description @group.description unless page_description - header_title group_title(@group) unless header_title -- sidebar "dashboard" unless sidebar - nav "group" = render template: "layouts/application" diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml index 1ff53ce2a9d..66b115e36de 100644 --- a/app/views/layouts/group_settings.html.haml +++ b/app/views/layouts/group_settings.html.haml @@ -1,5 +1,4 @@ - page_title "Settings" -- sidebar "dashboard" unless sidebar - nav "group" = render template: "layouts/group" From d8440b3035667623c87fe84b5a58c68da4ff936f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 3 May 2016 12:37:07 +0200 Subject: [PATCH 0097/1431] Fix active state for group pages if user not logged in Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/nav/_explore.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml index f08c5edf99c..3b40006a0cc 100644 --- a/app/views/layouts/nav/_explore.html.haml +++ b/app/views/layouts/nav/_explore.html.haml @@ -4,7 +4,7 @@ = icon('bookmark fw') %span Projects - = nav_link(controller: :groups) do + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to explore_groups_path, title: 'Groups' do = icon('group fw') %span From f4e0c56279007fd6cec3d8e6bd684f0483b0e0ff Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 3 May 2016 13:03:10 +0200 Subject: [PATCH 0098/1431] Improve documentation and web test for web hooks I wanted to share what I learned trying to debug web hooks using netcat. --- app/controllers/projects/hooks_controller.rb | 2 +- app/models/hooks/web_hook.rb | 2 +- doc/web_hooks/web_hooks.md | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index dfa9bd259e8..366373b0f0a 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -28,7 +28,7 @@ class Projects::HooksController < Projects::ApplicationController status, message = TestHookService.new.execute(hook, current_user) if status - flash[:notice] = 'Hook successfully executed.' + flash[:notice] = "Hook successfully executed, HTTP #{status} #{message}" else flash[:alert] = "Hook execution failed: #{message}" end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 1e3b4815596..818abbf4cc4 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -59,7 +59,7 @@ class WebHook < ActiveRecord::Base basic_auth: auth) end - [(response.code >= 200 && response.code < 300), ActionView::Base.full_sanitizer.sanitize(response.to_s)] + [response.code, ActionView::Base.full_sanitizer.sanitize(response.to_s)] rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e logger.error("WebHook Error => #{e}") [false, e.to_s] diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index c1c51302e79..6ffdb18339e 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -13,6 +13,19 @@ You can configure webhooks to listen for specific events like pushes, issues or Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. +## Webhook endpoint tips + +If you are writing your own endpoint (web server) that will receive +GitLab web hooks keep in mind the following things: + +- Your endpoint should send its HTTP response as fast as possible. If + you wait too long, GitLab may decide the hook failed and retry it. +- Your endpoint should ALWAYS return a valid HTTP response. If you do + not do this then GitLab will think the hook failed and retry it. + Most HTTP libraries take care of this for you automatically but if + you are writing a low-level hook this is important to remember. +- GitLab ignores the HTTP status code returned by your endpoint. + ## SSL Verification By default, the SSL certificate of the webhook endpoint is verified based on From 774af93027a5e1758adac00eb61178ecd85567ed Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 3 May 2016 13:04:47 +0200 Subject: [PATCH 0099/1431] Remove go to dashboard test suite for group page and get rid of AR warning Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/nav/_group.html.haml | 2 +- features/groups.feature | 4 ---- features/steps/group/milestones.rb | 6 ++++-- features/steps/groups.rb | 4 ---- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index bc5ce7052e4..87ee765b8f2 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -16,7 +16,7 @@ %li = link_to edit_group_path(@group) do Edit Group - - if access = @group.users.find(current_user) + - if access = @group.users.find(current_user.id) %li = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do diff --git a/features/groups.feature b/features/groups.feature index 419a5d3963d..49e939807b5 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -7,10 +7,6 @@ Feature: Groups When I visit group "NonExistentGroup" page Then page status code should be 404 - Scenario: I should have back to group button - When I visit group "Owned" page - Then I should see back to dashboard button - @javascript Scenario: I should see group "Owned" dashboard list When I visit group "Owned" page diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index a167d259837..f5fddab357d 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps include SharedUser step 'I click on group milestones' do - click_link 'Milestones' + page.within('.layout-nav') do + click_link 'Milestones' + end end step 'I should see group milestones index page has no milestones' do @@ -84,7 +86,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps end step 'I click on the "Labels" tab' do - page.within('.nav-links') do + page.within('.content .nav-links') do page.find(:xpath, "//a[@href='#tab-labels']").click end end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index e5b7db4c5e3..483370f41c6 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps include SharedGroup include SharedUser - step 'I should see back to dashboard button' do - expect(page).to have_content 'Go to dashboard' - end - step 'I should see group "Owned"' do expect(page).to have_content '@owned' end From 268868bcf0e1d09448c2212fb5275d1264d21b1b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 3 May 2016 13:21:09 +0200 Subject: [PATCH 0100/1431] Improve milestone page UI Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/milestone.scss | 4 ++-- app/views/projects/milestones/show.html.haml | 10 +++++----- app/views/shared/milestones/_top.html.haml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index d0e72a4422c..b94f524b513 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -28,7 +28,7 @@ li.milestone { // Issue title span a { - color: rgba(0,0,0,0.64); + color: $gl-text-color; } } } @@ -51,7 +51,7 @@ li.milestone { margin-top: 7px; .issuable-number { - color: rgba(0,0,0,0.44); + color: $gl-placeholder-color; margin-right: 5px; } .avatar { diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 56543ccd062..6ec84660157 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -24,15 +24,15 @@ - else = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr" do - = icon('trash-o') - Delete - = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do = icon('pencil-square-o') Edit -.detail-page-description.milestone-detail.second-block + = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do + = icon('trash-o') + Delete + +.detail-page-description.milestone-detail %h2.title = markdown escape_once(@milestone.title), pipeline: :single_line %div diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index cab8743a077..7ff947a51db 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -24,7 +24,7 @@ - else = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" -.detail-page-description.gray-content-block.second-block +.detail-page-description.milestone-detail %h2.title = markdown escape_once(milestone.title), pipeline: :single_line From 23a3e3756a4f44aa8bd69310a2e584c1d4f7af1d Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 3 May 2016 13:40:59 +0200 Subject: [PATCH 0101/1431] Inform user about questionable hook success --- app/controllers/projects/hooks_controller.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 366373b0f0a..9869d90831c 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -27,8 +27,10 @@ class Projects::HooksController < Projects::ApplicationController if !@project.empty_repo? status, message = TestHookService.new.execute(hook, current_user) - if status - flash[:notice] = "Hook successfully executed, HTTP #{status} #{message}" + if status && status >= 200 && status < 400 + flash[:notice] = "Hook executed successfully" + elsif status + flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}" else flash[:alert] = "Hook execution failed: #{message}" end From 6957fb7c4f0993ba1e0cbd9949a7e96c2c2def32 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 3 May 2016 13:42:56 +0200 Subject: [PATCH 0102/1431] Always mention HTTP status --- app/controllers/projects/hooks_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 9869d90831c..47524b1cf0b 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -28,7 +28,7 @@ class Projects::HooksController < Projects::ApplicationController status, message = TestHookService.new.execute(hook, current_user) if status && status >= 200 && status < 400 - flash[:notice] = "Hook executed successfully" + flash[:notice] = "Hook executed successfully: HTTP #{status}" elsif status flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}" else From bdda1eeddeb8a49484d221ae7217a9983bf984c4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 3 May 2016 14:30:42 +0200 Subject: [PATCH 0103/1431] Fix 404 on group page if user is not member of page Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/nav/_group.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 87ee765b8f2..0971bccfcd4 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -12,11 +12,11 @@ = link_to projects_group_path(@group), title: 'Projects' do Projects %li.divider - - if @group && can?(current_user, :admin_group, @group) + - if can?(current_user, :admin_group, @group) %li = link_to edit_group_path(@group) do Edit Group - - if access = @group.users.find(current_user.id) + - if access = @group.users.find_by(id: current_user.id) %li = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do From 1fddfa8f2a7b6bd6e71f7e997f152a2f8baa15d4 Mon Sep 17 00:00:00 2001 From: Karlo Soriano Date: Tue, 3 May 2016 21:17:39 +0800 Subject: [PATCH 0104/1431] Link to gitlab code review guide in contributing guidelines I noticed that the contributing guidelines still link to the thoughtbot code review guidelines even though we already have our own. [ci skip] --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24cd5864530..783d6947edb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -349,7 +349,7 @@ on your merge request feel free to mention one of the Merge Marshalls in the Please ensure that your merge request meets the contribution acceptance criteria. When having your code reviewed and when reviewing merge requests please take the -[Thoughtbot code review guide] into account. +[code review guidelines](doc/development/code_review.md) into account. ### Merge request description format @@ -523,4 +523,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design [free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12 [`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/ -[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review From 50d18a1e1d945297de4162b2712fa6bbcee0e42e Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 3 May 2016 09:29:15 -0500 Subject: [PATCH 0105/1431] Rake drop tables with cascade --- lib/tasks/gitlab/db.rake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake index 4921c6e0bcf..1c706dc11b3 100644 --- a/lib/tasks/gitlab/db.rake +++ b/lib/tasks/gitlab/db.rake @@ -29,7 +29,10 @@ namespace :gitlab do tables.delete 'schema_migrations' # Truncate schema_migrations to ensure migrations re-run connection.execute('TRUNCATE schema_migrations') - tables.each { |t| connection.execute("DROP TABLE #{t}") } + # Drop tables with cascade to avoid dependent table errors + # PG: http://www.postgresql.org/docs/current/static/ddl-depend.html + # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html + tables.each { |t| connection.execute("DROP TABLE #{t} CASCADE") } end end end From 94df8d5fc87a6a3a43a23b8f484de9e24af837d1 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 3 May 2016 16:39:08 +0200 Subject: [PATCH 0106/1431] Updated CHANGELOG for 8.7.2 [ci skip] --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b6527780bbf..11b1768c8eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,9 +18,10 @@ v 8.8.0 (unreleased) - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - Added multiple colors for labels in dropdowns when dups happen. -v 8.7.2 (unreleased) +v 8.7.2 - The "New Branch" button is now loaded asynchronously - Fix error 500 when trying to create a wiki page + - Updated spacing between notification label and button v 8.7.1 - Throttle the update of `project.last_activity_at` to 1 minute. !3848 From b7641dbb51102b24e692efa5ba01d52a88bce855 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 3 May 2016 11:24:30 -0400 Subject: [PATCH 0107/1431] Update link to release docs in README [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afa60116ebb..36f4bb12df0 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab ## GitLab release cycle -For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/). +For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/release-tools/blob/master/README.md). ## Upgrading From faaab2aef8e7f6b06f8f04dc68596e1229d507db Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 3 May 2016 11:58:43 -0500 Subject: [PATCH 0108/1431] Add to label :id to response --- app/controllers/dashboard/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 23a4ef21ea2..2a88350a4ca 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,6 +1,6 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index - labels = Label.where(project_id: projects).select(:title, :color).uniq(:title) + labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title) respond_to do |format| format.json { render json: labels } From f53557a4faa02c4596d16819c4b5c7783829677a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 3 May 2016 11:44:55 -0500 Subject: [PATCH 0109/1431] Group navigation design update --- app/assets/stylesheets/framework/blocks.scss | 7 +++++++ app/assets/stylesheets/framework/header.scss | 4 ++++ app/assets/stylesheets/framework/nav.scss | 10 +++++++--- .../stylesheets/framework/variables.scss | 2 ++ app/helpers/nav_helper.rb | 13 +++++++----- app/views/groups/show.html.haml | 20 +++++++------------ app/views/layouts/nav/_group.html.haml | 6 ++++-- 7 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index e72e4aa47ef..57f722c4818 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -150,6 +150,13 @@ right: auto; } } + + &.groups-cover-block { + background: $white-light; + border-bottom: 1px solid $border-color; + text-align: left; + padding: 24px 0; + } } .block-connector { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 5fa10d29a87..97f9d582007 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -30,6 +30,10 @@ header { border: none; border-bottom: 1px solid $border-color; + &.with-horizontal-nav { + border-bottom: none; + } + .container-fluid { width: 100% !important; filter: none; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 5fe687dcec3..40b24e96fa3 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -26,8 +26,8 @@ } &.active a { - color: #000; - border-bottom: 2px solid #4688f1; + border-bottom: 2px solid $link-underline-blue; + color: $black; } .badge { @@ -193,7 +193,7 @@ .controls { float: right; position: relative; - top: 10px; + top: 0; .dropdown { margin-left: 7px; @@ -202,5 +202,9 @@ .nav-links { border-bottom: none; + + a { + padding-top: 2px; + } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b8ed7e8a74c..3b69e50aca7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -74,6 +74,7 @@ $btn-transparent-color: #8f8f8f; $settings-icon-size: 18px; $provider-btn-group-border: #e5e5e5; $provider-btn-not-active-color: #4688f1; +$link-underline-blue: #4a8bee; /* * Color schema @@ -108,6 +109,7 @@ $red-light: #e52c5a; $red-normal: #d22852; $red-dark: darken($red-normal, 5%); +$black: #000000; $black-transparent: rgba(0, 0, 0, 0.3); $border-white-light: #f1f2f4; diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 5d86bd490a8..3aa41030453 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -34,10 +34,13 @@ module NavHelper end def nav_header_class - if nav_menu_collapsed? - "header-collapsed" - else - "header-expanded" - end + class_name = + if nav_menu_collapsed? + "header-collapsed" + else + "header-expanded" + end + class_name += " with-horizontal-nav" if defined?(nav) && nav + class_name end end diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index c71070e6c9c..ffce7b1fc48 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -4,22 +4,16 @@ - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") -.cover-block - .avatar-holder +.cover-block.groups-cover-block + .container-fluid = link_to group_icon(@group), target: '_blank' do = image_tag group_icon(@group), class: "avatar group-avatar s90" - .cover-title - %h1 - = @group.name - %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } - = visibility_level_icon(@group.visibility_level, fw: false) + .cover-title + %h1 @#{@group.path} - .cover-desc.username - @#{@group.path} - - - if @group.description.present? - .cover-desc.description - = markdown(@group.description, pipeline: :description) + - if @group.description.present? + .cover-desc.description + = markdown(@group.description, pipeline: :description) %div{ class: container_class } .top-area diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 0971bccfcd4..705682aeb29 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -44,14 +44,16 @@ %span Issues - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.badge.count= number_with_delimiter(issues.count) + %span.count + (#{number_with_delimiter(issues.count)}) = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do = icon('tasks fw') %span Merge Requests - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.badge.count= number_with_delimiter(merge_requests.count) + %span.count + (#{number_with_delimiter(merge_requests.count)}) = nav_link(controller: [:group_members]) do = link_to group_group_members_path(@group), title: 'Members' do = icon('users fw') From 8bd793c9c58c701882447fba055cd93e55f34af5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 3 May 2016 20:46:14 +0300 Subject: [PATCH 0110/1431] Copyedit performance document [ci skip] --- doc/development/performance.md | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/doc/development/performance.md b/doc/development/performance.md index c74198650e5..41f415812a0 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -7,7 +7,7 @@ consistent performance of GitLab. The process of solving performance problems is roughly as follows: -1. Make sure there's an issue open somewhere (e.g. on the GitLab CE issue +1. Make sure there's an issue open somewhere (e.g., on the GitLab CE issue tracker), create one if there isn't. See [#15607][#15607] for an example. 2. Measure the performance of the code in a production environment such as GitLab.com (see the [Tooling](#tooling) section below). Performance should be @@ -27,7 +27,7 @@ When providing timings make sure to provide: * The 99th percentile * The mean -When providing screenshots of graphs make sure that both the X and Y axes and +When providing screenshots of graphs, make sure that both the X and Y axes and the legend are clearly visible. If you happen to have access to GitLab.com's own monitoring tools you should also provide a link to any relevant graphs/dashboards. @@ -37,7 +37,7 @@ graphs/dashboards. GitLab provides two built-in tools to aid the process of improving performance: * [Sherlock](doc/development/profiling.md#sherlock) -* [GitLab Performance Monitoring](doc/monitoring/performance) +* [GitLab Performance Monitoring](doc/monitoring/performance/monitoring.md) GitLab employees can use GitLab.com's performance monitoring systems located at , this requires you to log in using your @@ -48,7 +48,7 @@ own InfluxDB + Grafana stack. Benchmarks are almost always useless. Benchmarks usually only test small bits of code in isolation and often only measure the best case scenario. On top of that, -benchmarks for libraries (e.g. a Gem) tend to be biased in favour of the +benchmarks for libraries (e.g., a Gem) tend to be biased in favour of the library. After all there's little benefit to an author publishing a benchmark that shows they perform worse than their competitors. @@ -102,7 +102,7 @@ In short: ## Importance of Changes -When working on performance improvements it's important to always ask yourself +When working on performance improvements, it's important to always ask yourself the question "How important is it to improve the performance of this piece of code?". Not every piece of code is equally important and it would be a waste to spend a week trying to improve something that only impacts a tiny fraction of @@ -114,7 +114,7 @@ There is no clear set of steps that you can follow to determine if a certain piece of code is worth optimizing. The only two things you can do are: 1. Think about what the code does, how it's used, how many times it's called and - how much time is spent in it relative to the total execution time (e.g. the + how much time is spent in it relative to the total execution time (e.g., the total time spent in a web request). 2. Ask others (preferably in the form of an issue). @@ -159,7 +159,7 @@ if repository.exists? end ``` -You can instead just write: +You can just write: ```ruby repository.branch_names.each do |name| @@ -170,21 +170,21 @@ end ## Caching Operations that will often return the same result should be cached using Redis, -in particular Git operations. When caching data in Redis make sure the cache is +in particular Git operations. When caching data in Redis, make sure the cache is flushed whenever needed. For example, a cache for the list of tags should be flushed whenever a new tag is pushed or a tag is removed. -When adding cache expiration code for repositories this code should be placed in -one of the before/after hooks residing in the Repository class. For example, if -a cache should be flushed after importing a repository this code should be added -to `Repository#after_import`. This ensures the cache logic stays within the -Repository class instead of leaking into other classes. +When adding cache expiration code for repositories, this code should be placed +in one of the before/after hooks residing in the Repository class. For example, +if a cache should be flushed after importing a repository this code should be +added to `Repository#after_import`. This ensures the cache logic stays within +the Repository class instead of leaking into other classes. -When caching data make sure to also memoize the result in an instance variable. -While retrieving data from Redis is much faster than raw Git operations it still -has overhead. By caching the result in an instance variable repeated calls to +When caching data, make sure to also memoize the result in an instance variable. +While retrieving data from Redis is much faster than raw Git operations, it still +has overhead. By caching the result in an instance variable, repeated calls to the same method won't end up retrieving data from Redis upon every call. When -memoizing cached data in an instance variable make sure to also reset the +memoizing cached data in an instance variable, make sure to also reset the instance variable when flushing the cache. An example: @@ -224,10 +224,10 @@ positive impact on performance. This feature of Ruby wasn't really meant to make things faster directly, instead it was meant to reduce the number of allocations. Depending on the size of the String and how frequently it would be allocated (before the `.freeze` call was -added) this _may_ make things faster, but there's no guarantee it will. +added), this _may_ make things faster, but there's no guarantee it will. -Another common flavour of this is to not only freeze a String but also assign it -to a constant, for example: +Another common flavour of this is to not only freeze a String, but also assign +it to a constant, for example: ```ruby SOME_CONSTANT = 'foo'.freeze @@ -248,8 +248,8 @@ SOME_CONSTANT = 'bar' ### Moving Allocations to Constants Storing an object as a constant so you only allocate it once _may_ improve -performance but there's no guarantee this will. Looking up constants has an -impact on runtime performance and as such using a constant instead of +performance, but there's no guarantee this will. Looking up constants has an +impact on runtime performance, and as such, using a constant instead of referencing an object directly may even slow code down. [#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 From 473e653e2408e818d0d61732332aeba53e5ca499 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 3 May 2016 21:53:54 +0300 Subject: [PATCH 0111/1431] Add clear instructions on installing the pg_trgm extension [ci skip] --- doc/install/installation.md | 56 ++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index e721e70a596..e3af3022262 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -157,22 +157,64 @@ Create a `git` user for GitLab: ## 5. Database -We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1. +We recommend using a PostgreSQL database. For MySQL check the +[MySQL setup guide](database_mysql.md). - # Install the database packages - sudo apt-get install -y postgresql postgresql-client libpq-dev +> **Note**: because we need to make use of extensions you need at least pgsql 9.1. - # Create a user for GitLab +1. Install the database packages: + + ```bash + sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib + ``` + +1. Create a database user for GitLab: + + ```bash sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" + ``` - # Create the GitLab production database & grant all privileges on database +1. Create the GitLab production database and grant all privileges on database: + + ```bash sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;" + ``` - # Try connecting to the new database with the new user +1. Create the `pg_trgm` extension (required for GitLab 8.6+): + + ```bash + sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + ``` + +1. Try connecting to the new database with the new user: + + ```bash sudo -u git -H psql -d gitlabhq_production + ``` - # Quit the database session +1. Check if the `pg_trgm` extension is enabled: + + ```bash + SELECT true AS enabled + FROM pg_available_extensions + WHERE name = 'pg_trgm' + AND installed_version IS NOT NULL; + ``` + + If the extension is enabled this will produce the following output: + + ``` + enabled + --------- + t + (1 row) + ``` + +1. Quit the database session: + + ```bash gitlabhq_production> \q + ``` ## 6. Redis From b898810c8d94b51514a7b5582921ab9ace4e40fb Mon Sep 17 00:00:00 2001 From: connorshea Date: Thu, 31 Mar 2016 19:54:03 -0600 Subject: [PATCH 0112/1431] Improve the Two-factor Authentication sign-in text [ci skip] Resolves #14543. --- CHANGELOG | 1 + app/views/devise/sessions/two_factor.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 11b1768c8eb..4c68c836080 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.8.0 (unreleased) - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - Added multiple colors for labels in dropdowns when dups happen. + - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) v 8.7.2 - The "New Branch" button is now loaded asynchronously diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 22b2c1a186b..c9d1e454a5e 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -4,7 +4,7 @@ %h3 Two-factor Authentication .login-body = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| - = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor authentication code', required: true, autofocus: true - %p.help-block.hint If you've lost your phone, you may enter one of your recovery codes. + = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true + %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. .prepend-top-20 = f.submit "Verify code", class: "btn btn-save" From d6c2d6bab9f00c288df3318424c4c1bbbca614dc Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Sat, 30 Apr 2016 17:38:39 +0200 Subject: [PATCH 0113/1431] Use sign out path only if not empty --- 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 1c53b0b21a3..17b3f49aed1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base end def after_sign_out_path_for(resource) - current_application_settings.after_sign_out_path || new_user_session_path + current_application_settings.after_sign_out_path.presence || new_user_session_path end def abilities From 21ccf89685bceee2b502a9e6a9fbb55310e02ebc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 3 May 2016 15:29:11 -0500 Subject: [PATCH 0114/1431] Redesign profile page and group page mobile navigation --- app/assets/stylesheets/framework/blocks.scss | 20 ++++++++++++ app/assets/stylesheets/framework/mobile.scss | 2 +- app/assets/stylesheets/framework/nav.scss | 32 ++++++++++++++++--- .../stylesheets/framework/variables.scss | 2 +- app/assets/stylesheets/pages/profile.scss | 30 +++++++++++++++++ app/views/groups/show.html.haml | 14 +++++--- 6 files changed, 88 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 57f722c4818..a78b1f30e61 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -156,6 +156,26 @@ border-bottom: 1px solid $border-color; text-align: left; padding: 24px 0; + + .group-info { + p { + margin-bottom: 0; + } + } + + @media (max-width: $screen-xs-max) { + text-align: center; + + .avatar { + float: none; + } + } + } + + .group-info { + h1 { + display: inline; + } } } diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 7eb451c124e..33cbee85987 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -30,7 +30,7 @@ } .rss-btn { - display: none !important; + display: none; } .project-home-links { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 40b24e96fa3..2ada2b9f2f2 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -198,13 +198,35 @@ .dropdown { margin-left: 7px; } - } - .nav-links { - border-bottom: none; + @media (max-width: $screen-md-max) { + float: none; + margin-bottom: 10px; - a { - padding-top: 2px; + .btn { + width: 20%; + } + } + + @media (max-width: $screen-xs-max) { + text-align: center; + + .rss-btn, + .dropdown-new { + display: inline-block; + width: 48%; + } } } } + +.nav-links { + border-bottom: none; + white-space: nowrap; + overflow-x: auto; + overflow-y: hidden; + + a { + padding-top: 2px; + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3b69e50aca7..7c695ba8f46 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -109,7 +109,7 @@ $red-light: #e52c5a; $red-normal: #d22852; $red-dark: darken($red-normal, 5%); -$black: #000000; +$black: #000; $black-transparent: rgba(0, 0, 0, 0.3); $border-white-light: #f1f2f4; diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 01f98479623..7a3a2ddd363 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -205,3 +205,33 @@ text-align: center; } } + +.user-profile { + @media (max-width: $screen-xs-max) { + .cover-block { + padding-top: 20px; + } + + .cover-controls { + position: static; + margin-bottom: 20px; + + .btn { + display: inline-block; + width: 48%; + } + } + } + + .cover-controls { + @media (max-width: $screen-xs-max) { + position: static; + margin-bottom: 20px; + + .btn { + display: inline-block; + width: 48%; + } + } + } +} diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index ffce7b1fc48..8c23a4ed1a4 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -8,12 +8,16 @@ .container-fluid = link_to group_icon(@group), target: '_blank' do = image_tag group_icon(@group), class: "avatar group-avatar s90" - .cover-title - %h1 @#{@group.path} + .group-info + .cover-title + %h1 + @#{@group.path} + %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } + = visibility_level_icon(@group.visibility_level, fw: false) - - if @group.description.present? - .cover-desc.description - = markdown(@group.description, pipeline: :description) + - if @group.description.present? + .cover-desc.description + = markdown(@group.description, pipeline: :description) %div{ class: container_class } .top-area From 3004386b55fede6381e5e0794c12625e2b83de25 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Tue, 3 May 2016 15:37:59 -0600 Subject: [PATCH 0115/1431] Revert "Remove the Devise Async gem." This reverts commit 1cc614f2bdd30b4fce35ee9e680f9272b9012978. It was causing the ActiveJob integration to fail, so unfortunately we'll have to add the gem again. --- CHANGELOG | 1 - Gemfile | 1 + Gemfile.lock | 3 +++ app/models/user.rb | 2 +- config/initializers/devise_async.rb | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 config/initializers/devise_async.rb diff --git a/CHANGELOG b/CHANGELOG index 11b1768c8eb..5e64e776f78 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,7 +10,6 @@ v 8.8.0 (unreleased) - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project - Updated search UI - Display informative message when new milestone is created - - Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea) - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Added button to toggle whitespaces changes on diff view - Backport GitLab Enterprise support from EE diff --git a/Gemfile b/Gemfile index 25c13fda575..890fcbc1318 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ gem "pg", '~> 0.18.2', group: :postgres # Authentication libraries gem 'devise', '~> 3.5.4' gem 'doorkeeper', '~> 3.1' +gem 'devise-async', '~> 0.9.0' gem 'omniauth', '~> 1.3.1' gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' diff --git a/Gemfile.lock b/Gemfile.lock index b1e954e0884..ae91b0fb6dd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -164,6 +164,8 @@ GEM responders thread_safe (~> 0.1) warden (~> 1.2.3) + devise-async (0.9.0) + devise (~> 3.2) devise-two-factor (2.0.1) activesupport attr_encrypted (~> 1.3.2) @@ -920,6 +922,7 @@ DEPENDENCIES database_cleaner (~> 1.4.0) default_value_for (~> 3.0.0) devise (~> 3.5.4) + devise-async (~> 0.9.0) devise-two-factor (~> 2.0.0) diffy (~> 3.0.3) doorkeeper (~> 3.1) diff --git a/app/models/user.rb b/app/models/user.rb index b6f405c6981..ab48f8f1960 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -91,7 +91,7 @@ class User < ActiveRecord::Base devise :two_factor_backupable, otp_number_of_backup_codes: 10 serialize :otp_backup_codes, JSON - devise :lockable, :recoverable, :rememberable, :trackable, + devise :lockable, :async, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable attr_accessor :force_random_password diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb new file mode 100644 index 00000000000..05a1852cdbd --- /dev/null +++ b/config/initializers/devise_async.rb @@ -0,0 +1 @@ +Devise::Async.backend = :sidekiq From 82b5bb91179438bb74f0b6ce31f6fd3cebac6645 Mon Sep 17 00:00:00 2001 From: Aral Balkan Date: Wed, 4 May 2016 06:58:40 +0000 Subject: [PATCH 0116/1431] Made it clearer that issue_id means iid, not id. --- doc/api/notes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/notes.md b/doc/api/notes.md index 7aa1c2155bf..a6b5b1787fd 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -15,7 +15,7 @@ GET /projects/:id/issues/:issue_id/notes Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of an issue +- `issue_id` (required) - The IID of an issue (not ID) ```json [ @@ -73,7 +73,7 @@ GET /projects/:id/issues/:issue_id/notes/:note_id Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of a project issue +- `issue_id` (required) - The IID of a project issue (not ID) - `note_id` (required) - The ID of an issue note ### Create new issue note @@ -87,7 +87,7 @@ POST /projects/:id/issues/:issue_id/notes Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of an issue +- `issue_id` (required) - The IID of an issue (not ID) - `body` (required) - The content of a note - `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z @@ -102,7 +102,7 @@ PUT /projects/:id/issues/:issue_id/notes/:note_id Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The ID of an issue +- `issue_id` (required) - The IID of an issue (not ID) - `note_id` (required) - The ID of a note - `body` (required) - The content of a note @@ -120,7 +120,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | -| `issue_id` | integer | yes | The ID of an issue | +| `issue_id` | integer | yes | The IID of an issue | | `note_id` | integer | yes | The ID of a note | ```bash From b0ddbaa07cd780b0ed86aa4e3c24744c6426b1e1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 08:14:40 -0400 Subject: [PATCH 0117/1431] Added docker registry view --- app/controllers/projects/images_controller.rb | 26 ++++++++++ app/helpers/gitlab_routing_helper.rb | 4 ++ app/helpers/projects_helper.rb | 4 ++ app/models/project.rb | 4 ++ app/models/registry.rb | 42 ++++++++++++++++ app/views/layouts/nav/_project.html.haml | 7 +++ .../projects/images/_header_title.html.haml | 1 + app/views/projects/images/index.html.haml | 48 +++++++++++++++++++ config/initializers/mime_types.rb | 7 +++ config/routes.rb | 2 + lib/gitlab/regex.rb | 4 ++ lib/registry_client.rb | 38 +++++++++++++++ 12 files changed, 187 insertions(+) create mode 100644 app/controllers/projects/images_controller.rb create mode 100644 app/models/registry.rb create mode 100644 app/views/projects/images/_header_title.html.haml create mode 100644 app/views/projects/images/index.html.haml create mode 100644 lib/registry_client.rb diff --git a/app/controllers/projects/images_controller.rb b/app/controllers/projects/images_controller.rb new file mode 100644 index 00000000000..5b10746aa0d --- /dev/null +++ b/app/controllers/projects/images_controller.rb @@ -0,0 +1,26 @@ +class Projects::ImagesController < Projects::ApplicationController + before_action :authorize_read_image! + before_action :authorize_update_image!, only: [:destroy] + before_action :tag, except: [:index] + layout 'project' + + def index + @tags = registry.tags + end + + def destroy + # registry.destroy_tag(tag['fsLayers'].first['blobSum']) + registry.destroy_tag(registry.tag_digest(params[:id])) + redirect_to namespace_project_images_path(project.namespace, project) + end + + private + + def registry + @registry ||= project.registry + end + + def tag + @tag ||= registry.tag(params[:id]) + end +end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f07eff3fb57..66cb41cc496 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -33,6 +33,10 @@ module GitlabRoutingHelper namespace_project_builds_path(project.namespace, project, *args) end + def project_images_path(project, *args) + namespace_project_images_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 3d5e61d2c18..6d1e630a097 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -152,6 +152,10 @@ module ProjectsHelper nav_tabs << :builds end + if can?(current_user, :read_image, project) + nav_tabs << :images + end + if can?(current_user, :admin_project, project) nav_tabs << :settings end diff --git a/app/models/project.rb b/app/models/project.rb index 76265a59ea7..496f9f3e347 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -370,6 +370,10 @@ class Project < ActiveRecord::Base @repository ||= Repository.new(path_with_namespace, self) end + def registry + @registry ||= Registry.new(path_with_namespace, self) + end + def registry_repository_url "#{Gitlab.config.registry.host_with_port}/#{path_with_namespace}" if images_enabled? && Gitlab.config.registry.enabled end diff --git a/app/models/registry.rb b/app/models/registry.rb new file mode 100644 index 00000000000..b4ef60a016f --- /dev/null +++ b/app/models/registry.rb @@ -0,0 +1,42 @@ +require 'net/http' + +class Registry + attr_accessor :path_with_namespace, :project + + def initialize(path_with_namespace, project) + @path_with_namespace = path_with_namespace + @project = project + end + + def tags + @tags ||= client.tags(path_with_namespace) + end + + def tag(reference) + return @tag[reference] if defined?(@tag[reference]) + @tag ||= {} + @tag[reference] ||= client.tag(path_with_namespace, reference) + end + + def tag_digest(reference) + return @tag_digest[reference] if defined?(@tag_digest[reference]) + @tag_digest ||= {} + @tag_digest[reference] ||= client.tag_digest(path_with_namespace, reference) + end + + def destroy_tag(reference) + client.delete_tag(path_with_namespace, reference) + end + + def blob_size(blob) + return @blob_size[blob] if defined?(@blob_size[blob]) + @blob_size ||= {} + @blob_size[blob] ||= client.blob_size(path_with_namespace, blob) + end + + private + + def client + @client ||= RegistryClient.new(Gitlab.config.registry.api_url) + end +end diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 479bde33719..2577afefa95 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -46,6 +46,13 @@ Builds %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) + - if project_nav_tab? :images + = nav_link(controller: %w(images)) do + = link_to project_images_path(@project), title: 'Images', class: 'shortcuts-images' do + = icon('image fw') + %span + Images + - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do diff --git a/app/views/projects/images/_header_title.html.haml b/app/views/projects/images/_header_title.html.haml new file mode 100644 index 00000000000..648aeeef2dc --- /dev/null +++ b/app/views/projects/images/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Images", project_images_path(@project)) diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml new file mode 100644 index 00000000000..338f3e5662c --- /dev/null +++ b/app/views/projects/images/index.html.haml @@ -0,0 +1,48 @@ +- page_title "Images" += render "header_title" + +.top-area + .nav-controls + +.gray-content-block + A list of Docker Images for this project + +%ul.content-list + - if @tags.blank? + %li + .nothing-here-block No images to show + - else + .table-holder + %table.table.builds + %thead + %tr + %th Name + %th Layers + %th Size + %th Created + %th Docker + %th + + - @tags.sort.each do |tag| + - details = @registry.tag(tag) + - layer = details['history'].first + - if layer && layer['v1Compatibility'] + - layer_data = JSON.parse(layer['v1Compatibility']) + %tr + %td + = link_to namespace_project_image_path(@project.namespace, @project, tag) do + #{details['name']}:#{details['tag']} + %td + = details['fsLayers'].length + %td + = number_to_human_size(details['fsLayers'].inject(0) { |sum, d| sum + @registry.blob_size(d['blobSum']) }.bytes) + %td + - if layer_data + = time_ago_in_words(DateTime.rfc3339(layer_data['created'])) + %td + - if layer_data + = layer_data['docker_version'] + %td.content + .controls.hidden-xs.pull-right + = link_to namespace_project_image_path(@project.namespace, @project, tag), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do + = icon("trash cred") diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index ca58ae92d1b..71e3c9d7db4 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -8,3 +8,10 @@ Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :patch Mime::Type.register_alias 'text/html', :markdown Mime::Type.register_alias 'text/html', :md +#Mime::Type.unregister :json +Mime::Type.register_alias 'application/vnd.docker.distribution.manifest.v1+prettyjws', :json +#Mime::Type.register 'application/json', :json, %w( text/plain text/x-json application/jsonrequest ) + +ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup('application/vnd.docker.distribution.manifest.v1+prettyjws')]=lambda do |body| + JSON.parse(body) +end diff --git a/config/routes.rb b/config/routes.rb index 5b48819dd9d..0280898accd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -690,6 +690,8 @@ Rails.application.routes.draw do end end + resources :images, only: [:index, :destroy], constraints: { id: Gitlab::Regex.image_reference_regex } + resources :milestones, constraints: { id: /\d+/ } do member do put :sort_issues diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index ace906a6f59..9b8f416ddfa 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -96,5 +96,9 @@ module Gitlab (? Date: Wed, 4 May 2016 12:17:12 +0200 Subject: [PATCH 0118/1431] Fix a spec that was failing due to !3483 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec were skipped in this MR so that tests started to fail in master instead of in this MR! Signed-off-by: RĂ©my Coutable --- spec/features/login_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 4433ef2d6f1..8c38dd5b122 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -37,7 +37,7 @@ feature 'Login', feature: true do end def enter_code(code) - fill_in 'Two-factor authentication code', with: code + fill_in 'Two-factor Authentication code', with: code click_button 'Verify code' end From 70551217a6cc250dc2e9a61720fc447df74e38c7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 4 May 2016 13:17:43 +0200 Subject: [PATCH 0119/1431] Fixed username links in the performance guide These would end up being rendered as: @yorickpeterse @yorickpeterse [ci skip] --- doc/development/performance.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/development/performance.md b/doc/development/performance.md index 41f415812a0..fb37b3a889c 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -16,7 +16,7 @@ The process of solving performance problems is roughly as follows: timings, etc) to the issue mentioned in step 1. 4. Solve the problem. 5. Create a merge request, assign the "performance" label and ping the right - people (e.g. [@yorickpeterse][@yorickpeterse] and [@joshfng][@joshfng]). + people (e.g. [@yorickpeterse][yorickpeterse] and [@joshfng][joshfng]). 6. Once a change has been deployed make sure to _again_ measure for at least 24 hours to see if your changes have any impact on the production environment. 7. Repeat until you're done. @@ -253,6 +253,6 @@ impact on runtime performance, and as such, using a constant instead of referencing an object directly may even slow code down. [#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 -[@yorickpeterse]: https://gitlab.com/u/yorickpeterse -[@joshfng]: https://gitlab.com/u/joshfng +[yorickpeterse]: https://gitlab.com/u/yorickpeterse +[joshfng]: https://gitlab.com/u/joshfng [anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern From 9243b6d0197ee7e3f1856d7e16f750fa74341851 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 14:22:54 +0200 Subject: [PATCH 0120/1431] Implement Container Registry API client --- app/controllers/projects/images_controller.rb | 16 +++-- app/models/project.rb | 5 +- app/models/registry.rb | 42 ------------ app/views/projects/images/index.html.haml | 28 ++++---- lib/image_registry/blob.rb | 47 ++++++++++++++ lib/image_registry/client.rb | 64 +++++++++++++++++++ lib/image_registry/config.rb | 15 +++++ lib/image_registry/registry.rb | 14 ++++ lib/image_registry/repository.rb | 38 +++++++++++ lib/image_registry/tag.rb | 62 ++++++++++++++++++ lib/registry_client.rb | 38 ----------- 11 files changed, 263 insertions(+), 106 deletions(-) delete mode 100644 app/models/registry.rb create mode 100644 lib/image_registry/blob.rb create mode 100644 lib/image_registry/client.rb create mode 100644 lib/image_registry/config.rb create mode 100644 lib/image_registry/registry.rb create mode 100644 lib/image_registry/repository.rb create mode 100644 lib/image_registry/tag.rb delete mode 100644 lib/registry_client.rb diff --git a/app/controllers/projects/images_controller.rb b/app/controllers/projects/images_controller.rb index 5b10746aa0d..cf3bdd42cf4 100644 --- a/app/controllers/projects/images_controller.rb +++ b/app/controllers/projects/images_controller.rb @@ -5,22 +5,24 @@ class Projects::ImagesController < Projects::ApplicationController layout 'project' def index - @tags = registry.tags + @tags = image_repository.tags end def destroy - # registry.destroy_tag(tag['fsLayers'].first['blobSum']) - registry.destroy_tag(registry.tag_digest(params[:id])) - redirect_to namespace_project_images_path(project.namespace, project) + if tag.delete + redirect_to namespace_project_images_path(project.namespace, project) + else + redirect_to namespace_project_images_path(project.namespace, project), alert: 'Failed to remove tag' + end end private - def registry - @registry ||= project.registry + def image_repository + @image_repository ||= project.image_repository end def tag - @tag ||= registry.tag(params[:id]) + @tag ||= image_repository[params[:id]] end end diff --git a/app/models/project.rb b/app/models/project.rb index 496f9f3e347..b905ebbfcaa 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -370,8 +370,9 @@ class Project < ActiveRecord::Base @repository ||= Repository.new(path_with_namespace, self) end - def registry - @registry ||= Registry.new(path_with_namespace, self) + def image_repository + @registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url) + @image_repository ||= ImageRegistry::Repository.new(@registry, path_with_namespace) end def registry_repository_url diff --git a/app/models/registry.rb b/app/models/registry.rb deleted file mode 100644 index b4ef60a016f..00000000000 --- a/app/models/registry.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'net/http' - -class Registry - attr_accessor :path_with_namespace, :project - - def initialize(path_with_namespace, project) - @path_with_namespace = path_with_namespace - @project = project - end - - def tags - @tags ||= client.tags(path_with_namespace) - end - - def tag(reference) - return @tag[reference] if defined?(@tag[reference]) - @tag ||= {} - @tag[reference] ||= client.tag(path_with_namespace, reference) - end - - def tag_digest(reference) - return @tag_digest[reference] if defined?(@tag_digest[reference]) - @tag_digest ||= {} - @tag_digest[reference] ||= client.tag_digest(path_with_namespace, reference) - end - - def destroy_tag(reference) - client.delete_tag(path_with_namespace, reference) - end - - def blob_size(blob) - return @blob_size[blob] if defined?(@blob_size[blob]) - @blob_size ||= {} - @blob_size[blob] ||= client.blob_size(path_with_namespace, blob) - end - - private - - def client - @client ||= RegistryClient.new(Gitlab.config.registry.api_url) - end -end diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml index 338f3e5662c..0987c7a39eb 100644 --- a/app/views/projects/images/index.html.haml +++ b/app/views/projects/images/index.html.haml @@ -17,32 +17,26 @@ %thead %tr %th Name - %th Layers + %th Revision %th Size %th Created - %th Docker %th - - @tags.sort.each do |tag| - - details = @registry.tag(tag) - - layer = details['history'].first - - if layer && layer['v1Compatibility'] - - layer_data = JSON.parse(layer['v1Compatibility']) + - @tags.each do |tag| %tr %td - = link_to namespace_project_image_path(@project.namespace, @project, tag) do - #{details['name']}:#{details['tag']} + = link_to namespace_project_image_path(@project.namespace, @project, tag.name) do + #{tag.repository.name}:#{tag.name} %td - = details['fsLayers'].length + - if layer = tag.layers.first + \##{layer.short_revision} %td - = number_to_human_size(details['fsLayers'].inject(0) { |sum, d| sum + @registry.blob_size(d['blobSum']) }.bytes) + = pluralize(tag.layers.size, "layer") +   + = number_to_human_size(tag.total_size) %td - - if layer_data - = time_ago_in_words(DateTime.rfc3339(layer_data['created'])) - %td - - if layer_data - = layer_data['docker_version'] + = time_ago_in_words(tag.created_at) %td.content .controls.hidden-xs.pull-right - = link_to namespace_project_image_path(@project.namespace, @project, tag), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do + = link_to namespace_project_image_path(@project.namespace, @project, tag.name), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do = icon("trash cred") diff --git a/lib/image_registry/blob.rb b/lib/image_registry/blob.rb new file mode 100644 index 00000000000..1aeeba7a686 --- /dev/null +++ b/lib/image_registry/blob.rb @@ -0,0 +1,47 @@ +module ImageRegistry + class Blob + attr_reader :repository, :config + + def initialize(repository, config) + @repository = repository + @config = config || {} + end + + def valid? + digest.present? + end + + def digest + config['digest'] + end + + def type + config['mediaType'] + end + + def size + config['size'] + end + + def revision + digest.split(':')[1] + end + + def short_revision + revision[0..8] + end + + def client + @client ||= repository.client + end + + def delete + client.delete_blob(repository.name, digest) + end + + def data + return @data if defined?(@data) + @data ||= client.blob(repository.name, digest, type) + end + end +end diff --git a/lib/image_registry/client.rb b/lib/image_registry/client.rb new file mode 100644 index 00000000000..b2e43ce4aeb --- /dev/null +++ b/lib/image_registry/client.rb @@ -0,0 +1,64 @@ +require 'faraday' +require 'faraday_middleware' + +module ImageRegistry + class Client + attr_accessor :uri + + MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json' + + def initialize(base_uri, options = {}) + @base_uri = base_uri + @faraday = Faraday.new(@base_uri) do |builder| + builder.request :json + builder.headers['Accept'] = MANIFEST_VERSION + + builder.response :json, :content_type => /\bjson$/ + builder.response :json, :content_type => 'application/vnd.docker.distribution.manifest.v1+prettyjws' + + if options[:user] && options[:password] + builder.request(:basic_auth, options[:user].to_s, options[:password].to_s) + elsif options[:token] + builder.request(:authentication, :Bearer, options[:token].to_s) + end + + builder.adapter :net_http + end + end + + def repository_tags(name) + @faraday.get("/v2/#{name}/tags/list").body + end + + def repository_manifest(name, reference) + @faraday.get("/v2/#{name}/manifests/#{reference}").body + end + + def put_repository_manifest(name, reference, manifest) + @faraday.put("/v2/#{name}/manifests/#{reference}", manifest, { "Content-Type" => MANIFEST_VERSION }).success? + end + + def repository_mount_blob(name, digest, from) + @faraday.post("/v2/#{name}/blobls/uploads/?mount=#{digest}&from=#{from}").status == 201 + end + + def repository_tag_digest(name, reference) + response = @faraday.head("/v2/#{name}/manifests/#{reference}") + response.headers['docker-content-digest'] if response.success? + end + + def delete_repository_tag(name, reference) + @faraday.delete("/v2/#{name}/manifests/#{reference}").success? + end + + def blob(name, digest, type = nil) + headers = {} + headers['Accept'] = type if type + @faraday.get("/v2/#{name}/blobs/#{digest}", nil, headers).body + end + + def delete_blob(name, digest) + @faraday.delete("/v2/#{name}/blobs/#{digest}").success? + end + end +end diff --git a/lib/image_registry/config.rb b/lib/image_registry/config.rb new file mode 100644 index 00000000000..1c2abec1bfa --- /dev/null +++ b/lib/image_registry/config.rb @@ -0,0 +1,15 @@ +module ImageRegistry + class Config + attr_reader :tag, :blob, :data + + def initialize(tag, blob) + @tag, @blob = tag, blob + @data = JSON.parse(blob.data) + end + + def [](key) + return unless data + data[key] + end + end +end diff --git a/lib/image_registry/registry.rb b/lib/image_registry/registry.rb new file mode 100644 index 00000000000..d8de8e392e9 --- /dev/null +++ b/lib/image_registry/registry.rb @@ -0,0 +1,14 @@ +module ImageRegistry + class Registry + attr_reader :uri, :client + + def initialize(uri, options = {}) + @uri = URI.parse(uri) + @client = ImageRegistry::Client.new(uri, options) + end + + def [](name) + ImageRegistry::Repository.new(self, name) + end + end +end diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb new file mode 100644 index 00000000000..f4f4ba65afc --- /dev/null +++ b/lib/image_registry/repository.rb @@ -0,0 +1,38 @@ +module ImageRegistry + class Repository + attr_reader :registry, :name + + def initialize(registry, name) + @registry, @name = registry, name + end + + def client + @client ||= registry.client + end + + def [](tag) + ImageRegistry::Tag.new(self, tag) + end + + def manifest + return @manifest if defined?(@manifest) + @manifest = client.repository_tags(name) + end + + def valid? + manifest.present? + end + + def tags + return @tags if defined?(@tags) + return unless manifest && manifest['tags'] + @tags = manifest['tags'].map do |tag| + ImageRegistry::Tag.new(self, tag) + end + end + + def delete + tags.each(:delete) + end + end +end diff --git a/lib/image_registry/tag.rb b/lib/image_registry/tag.rb new file mode 100644 index 00000000000..2bf0b8e345f --- /dev/null +++ b/lib/image_registry/tag.rb @@ -0,0 +1,62 @@ +module ImageRegistry + class Tag + attr_reader :repository, :name + + def initialize(repository, name) + @repository, @name = repository, name + end + + def valid? + manifest.present? + end + + def manifest + return @manifest if defined?(@manifest) + @manifest = client.repository_manifest(repository.name, name) + end + + def [](key) + return unless manifest + manifest[key] + end + + def digest + return @digest if defined?(@digest) + @digest = client.repository_tag_digest(repository.name, name) + end + + def config + return @config if defined?(@config) + return unless manifest && manifest['config'] + blob = ImageRegistry::Blob.new(repository, manifest['config']) + @config = ImageRegistry::Config.new(self, blob) + end + + def created_at + return unless config + @created_at ||= DateTime.rfc3339(config['created']) + end + + def layers + return @layers if defined?(@layers) + return unless manifest + @layers = manifest['layers'].map do |layer| + ImageRegistry::Blob.new(repository, layer) + end + end + + def total_size + return unless layers + layers.map(&:size).sum + end + + def delete + return unless digest + client.delete_repository_tag(repository.name, digest) + end + + def client + @client ||= repository.client + end + end +end diff --git a/lib/registry_client.rb b/lib/registry_client.rb deleted file mode 100644 index 87518a7b39c..00000000000 --- a/lib/registry_client.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'HTTParty' - -class RegistryClient - attr_accessor :uri - - def initialize(uri) - @uri = uri - end - - def tags(name) - response = HTTParty.get("#{uri}/v2/#{name}/tags/list") - response.parsed_response['tags'] - end - - def tag(name, reference) - response = HTTParty.get("#{uri}/v2/#{name}/manifests/#{reference}") - JSON.parse(response) - end - - def tag_digest(name, reference) - response = HTTParty.head("#{uri}/v2/#{name}/manifests/#{reference}") - response.headers['docker-content-digest'].split(':') - end - - def delete_tag(name, reference) - response = HTTParty.delete("#{uri}/v2/#{name}/manifests/#{reference}") - response.parsed_response - end - - def blob_size(name, digest) - response = HTTParty.head("#{uri}/v2/#{name}/blobs/#{digest}") - response.headers.content_length - end - - def delete_blob(name, digest) - HTTParty.delete("#{uri}/v2/#{name}/blobs/#{digest}") - end -end From d85b88962b603d46672ed6ebd01955ca7560fcc6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 14:23:43 +0200 Subject: [PATCH 0121/1431] Remove unused mime_types --- config/initializers/mime_types.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 71e3c9d7db4..ca58ae92d1b 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -8,10 +8,3 @@ Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :patch Mime::Type.register_alias 'text/html', :markdown Mime::Type.register_alias 'text/html', :md -#Mime::Type.unregister :json -Mime::Type.register_alias 'application/vnd.docker.distribution.manifest.v1+prettyjws', :json -#Mime::Type.register 'application/json', :json, %w( text/plain text/x-json application/jsonrequest ) - -ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup('application/vnd.docker.distribution.manifest.v1+prettyjws')]=lambda do |body| - JSON.parse(body) -end From 9bd5ac50b4c0677a2999ccee4f13e2c8e20a2def Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Wed, 4 May 2016 21:33:55 +0900 Subject: [PATCH 0122/1431] Use new build badge URLs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36f4bb12df0..c1a29c3bb1e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GitLab -[![build status](https://ci.gitlab.com/projects/1/status.svg?ref=master)](https://ci.gitlab.com/projects/1?ref=master) +[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.svg?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) From 7168493e8a25836dc7eedf25ec3241afd0d501b8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 14:35:18 +0200 Subject: [PATCH 0123/1431] Remove container registry on project removal --- app/services/projects/destroy_service.rb | 8 ++++++++ lib/image_registry/repository.rb | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index df5054f08d7..70bfc1fd533 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -28,6 +28,10 @@ module Projects Project.transaction do project.destroy! + unless remove_registry_tags + raise_error('Failed to remove project image registry. Please try again or contact administrator') + end + unless remove_repository(repo_path) raise_error('Failed to remove project repository. Please try again or contact administrator') end @@ -61,6 +65,10 @@ module Projects end end + def remove_registry_tags + project.image_registry.delete_tags + end + def raise_error(message) raise DestroyError.new(message) end diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb index f4f4ba65afc..c45fa2911e7 100644 --- a/lib/image_registry/repository.rb +++ b/lib/image_registry/repository.rb @@ -31,7 +31,8 @@ module ImageRegistry end end - def delete + def delete_tags + return unless tags tags.each(:delete) end end From e1c8663a3e7ad1f77a8476888331c376cc35eda5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 15:15:16 +0200 Subject: [PATCH 0124/1431] Allow to copy all manifests from one container repository to another --- lib/image_registry/blob.rb | 4 ++++ lib/image_registry/client.rb | 2 +- lib/image_registry/repository.rb | 16 ++++++++++++++++ lib/image_registry/tag.rb | 21 +++++++++++++++++---- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/image_registry/blob.rb b/lib/image_registry/blob.rb index 1aeeba7a686..43665149e23 100644 --- a/lib/image_registry/blob.rb +++ b/lib/image_registry/blob.rb @@ -43,5 +43,9 @@ module ImageRegistry return @data if defined?(@data) @data ||= client.blob(repository.name, digest, type) end + + def mount_to(to_repository) + client.repository_mount_blob(to_repository.name, digest, repository.name) + end end end diff --git a/lib/image_registry/client.rb b/lib/image_registry/client.rb index b2e43ce4aeb..84375ce8029 100644 --- a/lib/image_registry/client.rb +++ b/lib/image_registry/client.rb @@ -39,7 +39,7 @@ module ImageRegistry end def repository_mount_blob(name, digest, from) - @faraday.post("/v2/#{name}/blobls/uploads/?mount=#{digest}&from=#{from}").status == 201 + @faraday.post("/v2/#{name}/blobs/uploads/?mount=#{digest}&from=#{from}").status == 201 end def repository_tag_digest(name, reference) diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb index c45fa2911e7..763d8669555 100644 --- a/lib/image_registry/repository.rb +++ b/lib/image_registry/repository.rb @@ -29,11 +29,27 @@ module ImageRegistry @tags = manifest['tags'].map do |tag| ImageRegistry::Tag.new(self, tag) end + @tags ||= [] end def delete_tags return unless tags tags.each(:delete) end + + def mount_blob(blob) + return unless blob + client.repository_mount_blob(name, blob.digest, blob.repository.name) + end + + def mount_manifest(tag, manifest) + client.put_repository_manifest(name, tag, manifest) + end + + def copy_to(other_repository) + tags.all? do |tag| + tag.copy_to(other_repository) + end + end end end diff --git a/lib/image_registry/tag.rb b/lib/image_registry/tag.rb index 2bf0b8e345f..76946a6ce5b 100644 --- a/lib/image_registry/tag.rb +++ b/lib/image_registry/tag.rb @@ -25,11 +25,15 @@ module ImageRegistry @digest = client.repository_tag_digest(repository.name, name) end - def config - return @config if defined?(@config) + def config_blob + return @config_blob if defined?(@config_blob) return unless manifest && manifest['config'] - blob = ImageRegistry::Blob.new(repository, manifest['config']) - @config = ImageRegistry::Config.new(self, blob) + @config_blob = ImageRegistry::Blob.new(repository, manifest['config']) + end + + def config + return unless config_blob + @config ||= ImageRegistry::Config.new(self, config_blob) end def created_at @@ -55,6 +59,15 @@ module ImageRegistry client.delete_repository_tag(repository.name, digest) end + def copy_to(repository) + return unless manifest + layers.each do |blob| + repository.mount_blob(blob) + end + repository.mount_blob(config_blob) + repository.mount_manifest(name, manifest.to_json) + end + def client @client ||= repository.client end From f660b2173e408a0e527b9852c92b2d57982885b5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 4 May 2016 12:54:11 +0100 Subject: [PATCH 0125/1431] Instrument methods used in email diffs Make all of the nested constant instrumentation for core app code work the same way, add mailer instrumentation, and add instrumentation to the premailer gem. --- CHANGELOG | 3 +++ config/initializers/metrics.rb | 35 ++++++++++++++++++++-------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4c68c836080..268c7a81108 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,9 @@ v 8.8.0 (unreleased) - Added multiple colors for labels in dropdowns when dups happen. - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) +v 8.7.3 + - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented + v 8.7.2 - The "New Branch" button is now loaded asynchronously - Fix error 500 when trying to create a wiki page diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 283936d0efc..b2d08d87bac 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -61,12 +61,30 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(const) end - Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path| - const = File.basename(path, '.rb').camelize.constantize + # Path to search => prefix to strip from constant + paths_to_instrument = { + ['app', 'finders'] => ['app', 'finders'], + ['app', 'mailers', 'emails'] => ['app', 'mailers'], + ['app', 'services', '**'] => ['app', 'services'], + ['lib', 'gitlab', 'diff'] => ['lib'], + ['lib', 'gitlab', 'email', 'message'] => ['lib'] + } - config.instrument_instance_methods(const) + paths_to_instrument.each do |(path, prefix)| + prefix = Rails.root.join(*prefix) + + Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path| + path = Pathname.new(file_path).relative_path_from(prefix) + const = path.to_s.sub('.rb', '').camelize.constantize + + config.instrument_methods(const) + config.instrument_instance_methods(const) + end end + config.instrument_methods(Premailer::Adapter::Nokogiri) + config.instrument_instance_methods(Premailer::Adapter::Nokogiri) + [ :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository, :Tag, :TagCollection, :Tree @@ -97,17 +115,6 @@ if Gitlab::Metrics.enabled? config.instrument_methods(Gitlab::ReferenceExtractor) config.instrument_instance_methods(Gitlab::ReferenceExtractor) - # Instrument all service classes - services = Rails.root.join('app', 'services') - - Dir[services.join('**', '*.rb')].each do |file_path| - path = Pathname.new(file_path).relative_path_from(services) - const = path.to_s.sub('.rb', '').camelize.constantize - - config.instrument_methods(const) - config.instrument_instance_methods(const) - end - # Instrument the classes used for checking if somebody has push access. config.instrument_instance_methods(Gitlab::GitAccess) config.instrument_instance_methods(Gitlab::GitAccessWiki) From de008127eb9a7a14b06b2e4a3d3d1822ad6a54d7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:16:54 +0200 Subject: [PATCH 0126/1431] Fix bearer token support --- lib/image_registry/client.rb | 2 +- lib/image_registry/repository.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/image_registry/client.rb b/lib/image_registry/client.rb index 84375ce8029..a4432059097 100644 --- a/lib/image_registry/client.rb +++ b/lib/image_registry/client.rb @@ -19,7 +19,7 @@ module ImageRegistry if options[:user] && options[:password] builder.request(:basic_auth, options[:user].to_s, options[:password].to_s) elsif options[:token] - builder.request(:authentication, :Bearer, options[:token].to_s) + builder.request(:authorization, :bearer, options[:token].to_s) end builder.adapter :net_http diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb index 763d8669555..43e8e7720db 100644 --- a/lib/image_registry/repository.rb +++ b/lib/image_registry/repository.rb @@ -25,7 +25,7 @@ module ImageRegistry def tags return @tags if defined?(@tags) - return unless manifest && manifest['tags'] + return [] unless manifest && manifest['tags'] @tags = manifest['tags'].map do |tag| ImageRegistry::Tag.new(self, tag) end From 7731bb59c8d43cfa7e47c945d7aed05e5e3932c1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:17:08 +0200 Subject: [PATCH 0127/1431] Use bearer token to access registry --- app/models/project.rb | 3 ++- app/services/jwt/docker_authentication_service.rb | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index b905ebbfcaa..c50ea45d3eb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -371,7 +371,8 @@ class Project < ActiveRecord::Base end def image_repository - @registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url) + @registry_token ||= Jwt::DockerAuthenticationService.full_access_token(path_with_namespace) + @registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url, token: @registry_token) @image_repository ||= ImageRegistry::Repository.new(@registry, path_with_namespace) end diff --git a/app/services/jwt/docker_authentication_service.rb b/app/services/jwt/docker_authentication_service.rb index ce28085e5d6..16d77193a1e 100644 --- a/app/services/jwt/docker_authentication_service.rb +++ b/app/services/jwt/docker_authentication_service.rb @@ -8,6 +8,17 @@ module Jwt { token: token.encoded } end + def self.full_access_token(*names) + registry = Gitlab.config.registry + token = ::Jwt::RSAToken.new(registry.key) + token.issuer = registry.issuer + token.audience = 'docker' + token[:access] = names.map do |name| + { type: 'repository', name: name, actions: %w(pull push) } + end + token.encoded + end + private def token From 2afae7eac97d24d51eb949b9faa676314f06cdd6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:17:35 +0200 Subject: [PATCH 0128/1431] Use Container Images instead of Images --- .../projects/images/_header_title.html.haml | 2 +- app/views/projects/images/index.html.haml | 30 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/views/projects/images/_header_title.html.haml b/app/views/projects/images/_header_title.html.haml index 648aeeef2dc..f583e7fcfef 100644 --- a/app/views/projects/images/_header_title.html.haml +++ b/app/views/projects/images/_header_title.html.haml @@ -1 +1 @@ -- header_title project_title(@project, "Images", project_images_path(@project)) +- header_title project_title(@project, "Container Images", project_images_path(@project)) diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml index 0987c7a39eb..3732698c088 100644 --- a/app/views/projects/images/index.html.haml +++ b/app/views/projects/images/index.html.haml @@ -1,11 +1,23 @@ -- page_title "Images" +- page_title "Container Images" = render "header_title" -.top-area - .nav-controls +.light.prepend-top-default + %p + A 'container image' is a snapshot of a container. + You can host your 'container images' with GitLab. + %br + To start using container images hosted on GitLab you first need to login: + %pre + %code + docker login #{Gitlab.config.registry.host_port} + %br + Then you are free to create and upload a container images with build and push commands: + %pre + docker build -t #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} . + %br + docker push #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} -.gray-content-block - A list of Docker Images for this project +%hr %ul.content-list - if @tags.blank? @@ -25,15 +37,15 @@ - @tags.each do |tag| %tr %td - = link_to namespace_project_image_path(@project.namespace, @project, tag.name) do - #{tag.repository.name}:#{tag.name} + #{tag.repository.name}:#{tag.name} + = clipboard_button(clipboard_text: "docker pull #{Gitlab.config.registry.host_port}/#{tag.repository.name}:#{tag.name}") %td - if layer = tag.layers.first \##{layer.short_revision} %td - = pluralize(tag.layers.size, "layer") -   = number_to_human_size(tag.total_size) + · + = pluralize(tag.layers.size, "layer") %td = time_ago_in_words(tag.created_at) %td.content From 9e619d3813764566e5f4c0208e5f2c7365351808 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:28:01 +0200 Subject: [PATCH 0129/1431] Use Container Images --- app/views/layouts/nav/_project.html.haml | 4 ++-- app/views/projects/images/index.html.haml | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 2577afefa95..bef350adf34 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -49,9 +49,9 @@ - if project_nav_tab? :images = nav_link(controller: %w(images)) do = link_to project_images_path(@project), title: 'Images', class: 'shortcuts-images' do - = icon('image fw') + = icon('hdd-o fw') %span - Images + Container Images - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml index 3732698c088..08f67345b4a 100644 --- a/app/views/projects/images/index.html.haml +++ b/app/views/projects/images/index.html.haml @@ -29,7 +29,7 @@ %thead %tr %th Name - %th Revision + %th Digest %th Size %th Created %th @@ -41,7 +41,10 @@ = clipboard_button(clipboard_text: "docker pull #{Gitlab.config.registry.host_port}/#{tag.repository.name}:#{tag.name}") %td - if layer = tag.layers.first - \##{layer.short_revision} + %span.has-tooltip(title="#{layer.revision}") + = layer.short_revision + - else + \- %td = number_to_human_size(tag.total_size) · From 44f89eafc08a7967544429a3f930354a5f9bbbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 15 Apr 2016 12:15:52 +0200 Subject: [PATCH 0130/1431] Use Rugged's TagCollection#create instead of gitlab-shell's Repository#add_tag for better performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: RĂ©my Coutable --- CHANGELOG | 1 + app/models/repository.rb | 11 +++++-- app/services/create_tag_service.rb | 44 ++++++++++++-------------- features/steps/project/commits/tags.rb | 4 +-- lib/gitlab/backend/shell.rb | 18 ----------- spec/models/repository_spec.rb | 11 ++++--- spec/requests/api/tags_spec.rb | 4 +-- 7 files changed, 41 insertions(+), 52 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4c5c2b42dfa..577a3643d97 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.8.0 (unreleased) - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Added button to toggle whitespaces changes on diff view - Backport GitLab Enterprise support from EE + - Create tags using Rugged for performance reasons. !3745 - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - Added multiple colors for labels in dropdowns when dups happen. diff --git a/app/models/repository.rb b/app/models/repository.rb index b4319297e43..c4e38980a83 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -146,10 +146,17 @@ class Repository find_branch(branch_name) end - def add_tag(tag_name, ref, message = nil) + def add_tag(user, tag_name, ref, message = nil) before_push_tag - gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) + options = { message: message, tagger: user_to_committer(user) } if message + + tag = rugged.tags.create(tag_name, ref, options) + if tag.annotated? + Gitlab::Git::Tag.new(tag_name, ref, tag.annotation.message) + else + Gitlab::Git::Tag.new(tag_name, ref) + end end def rm_branch(user, branch_name) diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 55985380d31..775f9db2a46 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -8,32 +8,28 @@ class CreateTagService < BaseService end repository = project.repository - existing_tag = repository.find_tag(tag_name) - if existing_tag - return error('Tag already exists') - end - message.strip! if message - - repository.add_tag(tag_name, ref, message) - new_tag = repository.find_tag(tag_name) - - if new_tag - push_data = create_push_data(project, current_user, new_tag) - EventCreateService.new.push(project, current_user, push_data) - project.execute_hooks(push_data.dup, :tag_push_hooks) - project.execute_services(push_data.dup, :tag_push_hooks) - CreateCommitBuildsService.new.execute(project, current_user, push_data) - - if release_description - CreateReleaseService.new(@project, @current_user). - execute(tag_name, release_description) - end - - success(new_tag) - else - error('Invalid reference name') + begin + new_tag = repository.add_tag(current_user, tag_name, ref, message) + rescue Rugged::TagError + return error("Tag #{tag_name} already exists") + rescue Rugged::ReferenceError + return error("Target #{ref} is invalid") end + + push_data = create_push_data(project, current_user, new_tag) + + EventCreateService.new.push(project, current_user, push_data) + project.execute_hooks(push_data.dup, :tag_push_hooks) + project.execute_services(push_data.dup, :tag_push_hooks) + CreateCommitBuildsService.new.execute(project, current_user, push_data) + + if release_description + CreateReleaseService.new(@project, @current_user). + execute(tag_name, release_description) + end + + success(new_tag) end def success(branch) diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb index eff4234a44a..912ba580efd 100644 --- a/features/steps/project/commits/tags.rb +++ b/features/steps/project/commits/tags.rb @@ -57,11 +57,11 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps end step 'I should see new an error that tag ref is invalid' do - expect(page).to have_content 'Invalid reference name' + expect(page).to have_content 'Target foo is invalid' end step 'I should see new an error that tag already exists' do - expect(page).to have_content 'Tag already exists' + expect(page).to have_content 'Tag v1.0.0 already exists' end step "I visit tag 'v1.1.0' page" do diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 5e2fb863a8f..132f9cd1966 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -79,24 +79,6 @@ module Gitlab 'rm-project', "#{name}.git"]) end - # Add repository tag from passed ref - # - # path - project path with namespace - # tag_name - new tag name - # ref - HEAD for new tag - # message - optional message for tag (annotated tag) - # - # Ex. - # add_tag("gitlab/gitlab-ci", "v4.0", "master") - # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message") - # - def add_tag(path, tag_name, ref, message = nil) - cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git - #{tag_name} #{ref}) - cmd << message unless message.nil? || message.empty? - Gitlab::Utils.system_silent(cmd) - end - # Gc repository # # path - project path with namespace diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 397bb5a8028..5cdf644a0e1 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -859,12 +859,15 @@ describe Repository, models: true do describe '#add_tag' do it 'adds a tag' do + user = build_stubbed(:user) expect(repository).to receive(:before_push_tag) + expect(repository.rugged.tags).to receive(:create). + with('8.5', 'master', + hash_including(message: 'foo', + tagger: hash_including(name: user.name, email: user.email))). + and_call_original - expect_any_instance_of(Gitlab::Shell).to receive(:add_tag). - with(repository.path_with_namespace, '8.5', 'master', 'foo') - - repository.add_tag('8.5', 'master', 'foo') + repository.add_tag(user, '8.5', 'master', 'foo') end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index edcb2bedbf7..12e170b232f 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -147,7 +147,7 @@ describe API::API, api: true do tag_name: 'v8.0.0', ref: 'master' expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag already exists') + expect(json_response['message']).to eq('Tag v8.0.0 already exists') end it 'should return 400 if ref name is invalid' do @@ -155,7 +155,7 @@ describe API::API, api: true do tag_name: 'mytag', ref: 'foo' expect(response.status).to eq(400) - expect(json_response['message']).to eq('Invalid reference name') + expect(json_response['message']).to eq('Target foo is invalid') end end From 209f2f1e6fd861dd7bb6a73389400b4bb266d26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 25 Apr 2016 16:31:45 +0200 Subject: [PATCH 0131/1431] Use a similar approach to branch creation for tag creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: RĂ©my Coutable --- app/models/repository.rb | 17 ++++---- app/services/create_branch_service.rb | 5 --- app/services/create_tag_service.rb | 46 +++++++------------- spec/models/repository_spec.rb | 32 ++++++++++---- spec/services/create_tag_service_spec.rb | 53 ++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 52 deletions(-) create mode 100644 spec/services/create_tag_service_spec.rb diff --git a/app/models/repository.rb b/app/models/repository.rb index c4e38980a83..d5d70f41fd3 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -146,17 +146,20 @@ class Repository find_branch(branch_name) end - def add_tag(user, tag_name, ref, message = nil) - before_push_tag + def add_tag(user, tag_name, target, message = nil) + oldrev = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::TAG_REF_PREFIX + tag_name + target = commit(target).try(:id) + + return false unless target options = { message: message, tagger: user_to_committer(user) } if message - tag = rugged.tags.create(tag_name, ref, options) - if tag.annotated? - Gitlab::Git::Tag.new(tag_name, ref, tag.annotation.message) - else - Gitlab::Git::Tag.new(tag_name, ref) + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do + rugged.tags.create(tag_name, target, options) end + + find_tag(tag_name) end def rm_branch(user, branch_name) diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 707c2f7ff85..9f4481a8153 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -43,9 +43,4 @@ class CreateBranchService < BaseService out[:branch] = branch out end - - def build_push_data(project, user, branch) - Gitlab::PushDataBuilder. - build(project, user, Gitlab::Git::BLANK_SHA, branch.target, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) - end end diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 775f9db2a46..91ed0e354d0 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -1,46 +1,30 @@ require_relative 'base_service' class CreateTagService < BaseService - def execute(tag_name, ref, message, release_description = nil) + def execute(tag_name, target, message, release_description = nil) valid_tag = Gitlab::GitRefValidator.validate(tag_name) - if valid_tag == false - return error('Tag name invalid') - end + return error('Tag name invalid') unless valid_tag repository = project.repository message.strip! if message + + new_tag = nil begin - new_tag = repository.add_tag(current_user, tag_name, ref, message) + new_tag = repository.add_tag(current_user, tag_name, target, message) rescue Rugged::TagError return error("Tag #{tag_name} already exists") - rescue Rugged::ReferenceError - return error("Target #{ref} is invalid") + rescue GitHooksService::PreReceiveError + return error('Tag creation was rejected by Git hook') end - push_data = create_push_data(project, current_user, new_tag) - - EventCreateService.new.push(project, current_user, push_data) - project.execute_hooks(push_data.dup, :tag_push_hooks) - project.execute_services(push_data.dup, :tag_push_hooks) - CreateCommitBuildsService.new.execute(project, current_user, push_data) - - if release_description - CreateReleaseService.new(@project, @current_user). - execute(tag_name, release_description) + if new_tag + if release_description + CreateReleaseService.new(@project, @current_user). + execute(tag_name, release_description) + end + success.merge(tag: new_tag) + else + error("Target #{target} is invalid") end - - success(new_tag) - end - - def success(branch) - out = super() - out[:tag] = branch - out - end - - def create_push_data(project, user, tag) - commits = [project.commit(tag.target)].compact - Gitlab::PushDataBuilder. - build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", commits, tag.message) end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 5cdf644a0e1..b3359a42237 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -858,16 +858,30 @@ describe Repository, models: true do end describe '#add_tag' do - it 'adds a tag' do - user = build_stubbed(:user) - expect(repository).to receive(:before_push_tag) - expect(repository.rugged.tags).to receive(:create). - with('8.5', 'master', - hash_including(message: 'foo', - tagger: hash_including(name: user.name, email: user.email))). - and_call_original + context 'with a valid target' do + let(:user) { build_stubbed(:user) } - repository.add_tag(user, '8.5', 'master', 'foo') + it 'creates the tag using rugged' do + expect(repository.rugged.tags).to receive(:create). + with('8.5', repository.commit('master').id, + hash_including(message: 'foo', + tagger: hash_including(name: user.name, email: user.email))). + and_call_original + + repository.add_tag(user, '8.5', 'master', 'foo') + end + + it 'returns a Gitlab::Git::Tag object' do + tag = repository.add_tag(user, '8.5', 'master', 'foo') + + expect(tag).to be_a(Gitlab::Git::Tag) + end + end + + context 'with an invalid target' do + it 'returns false' do + expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false + end end end diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/create_tag_service_spec.rb new file mode 100644 index 00000000000..91f9e663b66 --- /dev/null +++ b/spec/services/create_tag_service_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe CreateTagService, services: true do + let(:project) { create(:project) } + let(:repository) { project.repository } + let(:user) { create(:user) } + let(:service) { described_class.new(project, user) } + + describe '#execute' do + it 'creates the tag and returns success' do + response = service.execute('v42.42.42', 'master', 'Foo') + + expect(response[:status]).to eq(:success) + expect(response[:tag]).to be_a Gitlab::Git::Tag + expect(response[:tag].name).to eq('v42.42.42') + end + + context 'when target is invalid' do + it 'returns an error' do + response = service.execute('v1.1.0', 'foo', 'Foo') + + expect(response).to eq(status: :error, + message: 'Target foo is invalid') + end + end + + context 'when tag already exists' do + it 'returns an error' do + expect(repository).to receive(:add_tag). + with(user, 'v1.1.0', 'master', 'Foo'). + and_raise(Rugged::TagError) + + response = service.execute('v1.1.0', 'master', 'Foo') + + expect(response).to eq(status: :error, + message: 'Tag v1.1.0 already exists') + end + end + + context 'when pre-receive hook fails' do + it 'returns an error' do + expect(repository).to receive(:add_tag). + with(user, 'v1.1.0', 'master', 'Foo'). + and_raise(GitHooksService::PreReceiveError) + + response = service.execute('v1.1.0', 'master', 'Foo') + + expect(response).to eq(status: :error, + message: 'Tag creation was rejected by Git hook') + end + end + end +end From fb5682e7cd587e7e6c871588240fc49b325b042f Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Sat, 30 Apr 2016 16:35:10 +0200 Subject: [PATCH 0132/1431] Use the new admin settings for gravatar --- CHANGELOG | 1 + app/views/profiles/show.html.haml | 4 ++-- config/gitlab.yml.example | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cc6588c6cbd..4505b424987 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.8.0 (unreleased) - Added multiple colors for labels in dropdowns when dups happen. - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) + - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index f59d27f7ed0..eef50d887c7 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -8,11 +8,11 @@ %p - if @user.avatar? You can change your avatar here - - if Gitlab.config.gravatar.enabled + - if gravatar_enabled? or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} - else You can upload an avatar here - - if Gitlab.config.gravatar.enabled + - if gravatar_enabled? or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} .col-lg-9 .clearfix.avatar-image.append-bottom-default diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 07ce4b6d715..e682bcb976d 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -152,7 +152,6 @@ production: &base ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: - enabled: true # Use user avatar image from Gravatar.com (default: true) # gravatar urls: possible placeholders: %{hash} %{size} %{email} # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon From 731b231357f3b0b71f60a3bb1f81f8cd78f3e318 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 3 May 2016 20:55:35 -0700 Subject: [PATCH 0133/1431] Log to application.log when an admin starts and stops impersonating a user Closes gitlab-org/gitlab-ee#536 --- CHANGELOG | 1 + app/controllers/admin/impersonations_controller.rb | 2 ++ app/controllers/admin/users_controller.rb | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b2f5c283a1a..5f1793d472a 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.8.0 (unreleased) - Project#open_branches has been cleaned up and no longer loads entire records into memory. + - Log to application.log when an admin starts and stops impersonating a user - Make build status canceled if any of the jobs was canceled and none failed - Remove future dates from contribution calendar graph. - Support e-mail notifications for comments on project snippets diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb index 2db824c87ef..8be35f00a77 100644 --- a/app/controllers/admin/impersonations_controller.rb +++ b/app/controllers/admin/impersonations_controller.rb @@ -7,6 +7,8 @@ class Admin::ImpersonationsController < Admin::ApplicationController warden.set_user(impersonator, scope: :user) + Gitlab::AppLogger.info("User #{original_user.username} has stopped impersonating #{impersonator.username}") + session[:impersonator_id] = nil redirect_to admin_user_path(original_user) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index b8976fa09a9..f2f654c7bcd 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -41,6 +41,8 @@ class Admin::UsersController < Admin::ApplicationController warden.set_user(user, scope: :user) + Gitlab::AppLogger.info("User #{current_user.username} has started impersonating #{user.username}") + flash[:alert] = "You are now impersonating #{user.username}" redirect_to root_path From b9672d4b38514a14cc39773d30f55c9629973f63 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 4 May 2016 00:59:20 -0700 Subject: [PATCH 0134/1431] Limit the number of merge requests per project to avoid long seeds This step was taking a long time because seed_fu creates N / 2 merge requests for each repo, where N is the number of branches for that repo. At the time of this writing, there are 234 branches on the gitlab-ce repo, leading to 117 merge requests. --- db/fixtures/development/10_merge_requests.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index 0825776ffaa..87fb8e3300d 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -1,6 +1,9 @@ Gitlab::Seeder.quiet do + # Limit the number of merge requests per project to avoid long seeds + MAX_NUM_MERGE_REQUESTS = 10 + Project.all.reject(&:empty_repo?).each do |project| - branches = project.repository.branch_names + branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2) branches.each do |branch_name| break if branches.size < 2 From 003b36b45f38ca3116af5d9179fff4adb26a4eb1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 4 May 2016 11:43:47 -0500 Subject: [PATCH 0135/1431] Fixed layout nav, revert back to badge styles, general enhancements --- app/assets/stylesheets/framework/nav.scss | 36 +++++++++++++------ app/assets/stylesheets/framework/sidebar.scss | 8 +++++ app/views/groups/show.html.haml | 2 +- app/views/layouts/_page.html.haml | 2 +- app/views/layouts/nav/_group.html.haml | 6 ++-- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 2ada2b9f2f2..c79de099d38 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -140,6 +140,12 @@ } } + .project-filter-form { + input { + background-color: $background-color; + } + } + @media (max-width: $screen-xs-max) { padding-bottom: 0; @@ -187,13 +193,17 @@ } .layout-nav { + position: fixed; + top: 58px; + width: 100%; + z-index: 1; background: $background-color; border-bottom: 1px solid $border-color; + transition-duration: .3s; .controls { float: right; - position: relative; - top: 0; + padding: 12px 5px 0 0; .dropdown { margin-left: 7px; @@ -218,15 +228,21 @@ } } } -} -.nav-links { - border-bottom: none; - white-space: nowrap; - overflow-x: auto; - overflow-y: hidden; + .nav-links { + border-bottom: none; - a { - padding-top: 2px; + li { + + .badge { + color: $gl-icon-color; + } + + } } + +} + +.page-with-layout-nav { + margin-top: 56px; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 18189e985c4..32d948e4c53 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -254,6 +254,10 @@ } } } + + .layout-nav { + padding-right: $sidebar_collapsed_width; + } } .page-sidebar-expanded { @@ -280,6 +284,10 @@ } } } + + .layout-nav { + padding-right: $sidebar_width; + } } .right-sidebar-collapsed { diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 8c23a4ed1a4..089de798bd8 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -5,7 +5,7 @@ = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") .cover-block.groups-cover-block - .container-fluid + .container-fluid.container-limited = link_to group_icon(@group), target: '_blank' do = image_tag group_icon(@group), class: "avatar group-avatar s90" .group-info diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index ad8a2e1e6c7..3c3bc41bf0e 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -26,7 +26,7 @@ .layout-nav .container-fluid = render "layouts/nav/#{nav}" - .content-wrapper + .content-wrapper{ class: ('page-with-layout-nav' if defined?(nav) && nav) } = render "layouts/flash" = yield :flash_message %div{ class: (container_class unless @no_container) } diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 705682aeb29..0971bccfcd4 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -44,16 +44,14 @@ %span Issues - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.count - (#{number_with_delimiter(issues.count)}) + %span.badge.count= number_with_delimiter(issues.count) = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do = icon('tasks fw') %span Merge Requests - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute - %span.count - (#{number_with_delimiter(merge_requests.count)}) + %span.badge.count= number_with_delimiter(merge_requests.count) = nav_link(controller: [:group_members]) do = link_to group_group_members_path(@group), title: 'Members' do = icon('users fw') From 17d23283a119fcfc2f6ac070bb5df8d913abf2e7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 4 May 2016 12:46:53 -0500 Subject: [PATCH 0136/1431] Restore "r" shortcut --- app/assets/javascripts/shortcuts_issuable.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee index ad9b3c1c6bf..ccb42ab2168 100644 --- a/app/assets/javascripts/shortcuts_issuable.coffee +++ b/app/assets/javascripts/shortcuts_issuable.coffee @@ -6,6 +6,10 @@ class @ShortcutsIssuable extends ShortcutsNavigation super() Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee')) Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone')) + Mousetrap.bind('r', => + @replyWithSelectedText() + return false + ) Mousetrap.bind('j', => @prevIssue() return false From 4ed9e5f5bebc5d3e11c21a929fdf57b8b014a31b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 4 May 2016 13:11:38 -0500 Subject: [PATCH 0137/1431] Update font colors, font sizes, caret icon, avatar size --- app/assets/stylesheets/framework/avatar.scss | 1 + app/assets/stylesheets/framework/blocks.scss | 8 +++++ app/assets/stylesheets/framework/nav.scss | 33 +++++++++++++++++-- .../stylesheets/framework/variables.scss | 1 + app/views/groups/show.html.haml | 2 +- app/views/layouts/nav/_group.html.haml | 2 +- 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 5aa425dab6c..f5ce70b606b 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -28,6 +28,7 @@ &.s46 { width: 46px; height: 46px; margin-right: 15px; } &.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; } + &.s70 { width: 70px; height: 70px; margin-right: 14px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; } &.s110 { width: 110px; height: 110px; margin-right: 15px; } &.s140 { width: 140px; height: 140px; margin-right: 20px; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index a78b1f30e61..e85eac585f2 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -158,6 +158,10 @@ padding: 24px 0; .group-info { + .cover-title { + margin-top: 9px; + } + p { margin-bottom: 0; } @@ -173,8 +177,12 @@ } .group-info { + h1 { display: inline; + font-weight: normal; + font-size: 24px; + color: $gl-title-color; } } } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index c79de099d38..5c205ab7245 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -203,7 +203,21 @@ .controls { float: right; - padding: 12px 5px 0 0; + padding: 7px 5px 0 0; + + i { + color: $layout-link-gray; + } + + .fa-rss, + .fa-cog { + font-size: 16px; + } + + .fa-caret-down { + margin-left: 5px; + color: $gl-icon-color; + } .dropdown { margin-left: 7px; @@ -234,15 +248,28 @@ li { + a { + padding-top: 10px; + } + + a, i { + color: $layout-link-gray; + } + + &.active { + a, i { + color: $black; + } + } + .badge { color: $gl-icon-color; } - } } } .page-with-layout-nav { - margin-top: 56px; + margin-top: 52px; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 7c695ba8f46..6c8cdcded1f 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -75,6 +75,7 @@ $settings-icon-size: 18px; $provider-btn-group-border: #e5e5e5; $provider-btn-not-active-color: #4688f1; $link-underline-blue: #4a8bee; +$layout-link-gray: #7e7c7c; /* * Color schema diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 089de798bd8..77c297255b8 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -7,7 +7,7 @@ .cover-block.groups-cover-block .container-fluid.container-limited = link_to group_icon(@group), target: '_blank' do - = image_tag group_icon(@group), class: "avatar group-avatar s90" + = image_tag group_icon(@group), class: "avatar group-avatar s70" .group-info .cover-title %h1 diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 0971bccfcd4..b4b528c614f 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -6,7 +6,7 @@ %span.dropdown.group-settings-dropdown %a.dropdown-new.btn.btn-gray#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} = icon('cog') - = icon('angle-down') + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right = nav_link(path: 'groups#projects') do = link_to projects_group_path(@group), title: 'Projects' do From 525e05b6536123a3117b5ff53fd46a96382d5f9d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 28 Apr 2016 16:48:37 -0700 Subject: [PATCH 0138/1431] Expire repository exists? and has_visible_content? caches after a push if necessary Closes #17012 --- CHANGELOG | 1 + app/services/git_push_service.rb | 1 + app/services/git_tag_push_service.rb | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 25d26535b72..925740e5d98 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.8.0 (unreleased) - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) + - Expire repository exists? and has_visible_content? caches after a push if necessary v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index b7af80055bf..66136b62617 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -17,6 +17,7 @@ class GitPushService < BaseService # 6. Checks if the project's main language has changed # def execute + @project.repository.after_create if @project.empty_repo? @project.repository.after_push_commit(branch_name, params[:newrev]) if push_remove_branch? diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 64271d8bc5c..7410442609d 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -2,6 +2,7 @@ class GitTagPushService < BaseService attr_accessor :push_data def execute + project.repository.after_create if project.empty_repo? project.repository.before_push_tag @push_data = build_push_data From 6b4e255aa86864571b7d8dbc338986d4017b4215 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 4 May 2016 15:00:33 -0500 Subject: [PATCH 0139/1431] Remove break-all from links --- app/assets/stylesheets/pages/notes.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 9619d65db85..50ca755bcb6 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -114,10 +114,6 @@ ul.notes { word-break: keep-all; } } - - a { - word-break: break-all; - } } .note-header { From ccd9c4be00a1e783b6d8c821aaec2b975e4a3176 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 4 May 2016 14:19:12 -0500 Subject: [PATCH 0140/1431] Reduce height of nav to 50px --- app/assets/stylesheets/framework/nav.scss | 4 ++-- app/assets/stylesheets/framework/sidebar.scss | 5 +++-- app/assets/stylesheets/framework/variables.scss | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 5c205ab7245..bf09b6dadc6 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -194,7 +194,7 @@ .layout-nav { position: fixed; - top: 58px; + top: $header-height; width: 100%; z-index: 1; background: $background-color; @@ -271,5 +271,5 @@ } .page-with-layout-nav { - margin-top: 52px; + margin-top: 50px; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 32d948e4c53..e940fd7286e 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -3,6 +3,7 @@ position: absolute; width: 58px; cursor: pointer; + margin-top: 8px; } .page-with-sidebar { @@ -62,7 +63,7 @@ float: left; height: $header-height; width: 100%; - padding: 11px 0 11px 22px; + padding-left: 22px; overflow: hidden; outline: none; transition-duration: .3s; @@ -85,7 +86,7 @@ margin: 0; margin-left: 50px; font-size: 19px; - line-height: 41px; + line-height: 50px; font-weight: normal; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 6c8cdcded1f..42772ff5c33 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -65,7 +65,7 @@ $gl-padding-top: 10px; $row-hover: #f4f8fe; $progress-color: #c0392b; $avatar_radius: 50%; -$header-height: 58px; +$header-height: 50px; $fixed-layout-width: 1280px; $gl-avatar-size: 40px; $error-exclamation-point: #e62958; From 8b813277b0cbf57b2a07ad2e1b4cb87dadfb66c5 Mon Sep 17 00:00:00 2001 From: Yatish Mehta Date: Wed, 4 May 2016 14:04:13 -0700 Subject: [PATCH 0141/1431] Fixed typo in zen.scss and corresponding views --- app/assets/stylesheets/framework/zen.scss | 2 +- app/views/projects/_md_preview.html.haml | 2 +- app/views/projects/_zen.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss index f870ea0d87f..ff02ebdd34c 100644 --- a/app/assets/stylesheets/framework/zen.scss +++ b/app/assets/stylesheets/framework/zen.scss @@ -32,7 +32,7 @@ } } -.zen-cotrol { +.zen-control { padding: 0; color: #555; background: none; diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 8de44a6c914..81afea2c60a 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -8,7 +8,7 @@ %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } Preview %li.pull-right - %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 } + %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 } Go full screen .md-write-holder diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index e1e35013968..413477a2d3a 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -4,5 +4,5 @@ = f.text_area attr, class: classes, placeholder: placeholder - else = text_area_tag attr, nil, class: classes, placeholder: placeholder - %a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" } + %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" } = icon('compress') From f18ec70743375024aa7ec7bb86c437ca9198e729 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 4 May 2016 17:05:16 -0400 Subject: [PATCH 0142/1431] Backport changes from gitlab-org/gitlab-ee!372 Mostly replaces several Spinach tests with RSpec Feature tests. --- app/helpers/projects_helper.rb | 3 +- features/project/create.feature | 16 +---- features/steps/project/create.rb | 26 +------- spec/factories/projects.rb | 10 ++- ...r_views_empty_project_instructions_spec.rb | 63 +++++++++++++++++++ spec/helpers/projects_helper_spec.rb | 10 +-- 6 files changed, 80 insertions(+), 48 deletions(-) create mode 100644 spec/features/projects/developer_views_empty_project_instructions_spec.rb diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3d5e61d2c18..62e8c03cc81 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -209,7 +209,8 @@ module ProjectsHelper end def default_url_to_repo(project = @project) - if default_clone_protocol == "ssh" + case default_clone_protocol + when 'ssh' project.ssh_url_to_repo else project.http_url_to_repo diff --git a/features/project/create.feature b/features/project/create.feature index 27136798e36..67336d73bf7 100644 --- a/features/project/create.feature +++ b/features/project/create.feature @@ -7,20 +7,8 @@ Feature: Project Create @javascript Scenario: User create a project Given I sign in as a user - When I visit new project page And I have an ssh key + When I visit new project page And fill project form with valid data Then I should see project page - And I should see empty project instuctions - - @javascript - Scenario: Empty project instructions - Given I sign in as a user - And I have an ssh key - When I visit new project page - And fill project form with valid data - Then I see empty project instuctions - And I click on HTTP - Then Remote url should update to http link - And If I click on SSH - Then Remote url should update to ssh link + And I should see empty project instructions diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb index 422b151eaa2..5f5f806df36 100644 --- a/features/steps/project/create.rb +++ b/features/steps/project/create.rb @@ -13,33 +13,9 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last) end - step 'I should see empty project instuctions' do + step 'I should see empty project instructions' do expect(page).to have_content "git init" expect(page).to have_content "git remote" expect(page).to have_content Project.last.url_to_repo end - - step 'I see empty project instuctions' do - expect(page).to have_content "git init" - expect(page).to have_content "git remote" - expect(page).to have_content Project.last.url_to_repo - end - - step 'I click on HTTP' do - find('#clone-dropdown').click - find('.http-selector').click - end - - step 'Remote url should update to http link' do - expect(page).to have_content "git remote add origin #{Project.last.http_url_to_repo}" - end - - step 'If I click on SSH' do - find('#clone-dropdown').click - find('.ssh-selector').click - end - - step 'Remote url should update to ssh link' do - expect(page).to have_content "git remote add origin #{Project.last.url_to_repo}" - end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index c14b99606ba..5338c88dd84 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -61,6 +61,12 @@ FactoryGirl.define do trait :private do visibility_level Gitlab::VisibilityLevel::PRIVATE end + + trait :empty_repo do + after(:create) do |project| + project.create_repository + end + end end # Project with empty repository @@ -68,9 +74,7 @@ FactoryGirl.define do # This is a case when you just created a project # but not pushed any code there yet factory :project_empty_repo, parent: :empty_project do - after :create do |project| - project.create_repository - end + empty_repo end # Project with test repository diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb new file mode 100644 index 00000000000..0c51fe72ca4 --- /dev/null +++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +feature 'Developer views empty project instructions', feature: true do + let(:project) { create(:empty_project, :empty_repo) } + let(:developer) { create(:user) } + + background do + project.team << [developer, :developer] + + login_as(developer) + end + + context 'without an SSH key' do + scenario 'defaults to HTTP' do + visit_project + + expect_instructions_for('http') + end + + scenario 'switches to SSH', js: true do + visit_project + + select_protocol('SSH') + + expect_instructions_for('ssh') + end + end + + context 'with an SSH key' do + background do + create(:personal_key, user: developer) + end + + scenario 'defaults to SSH' do + visit_project + + expect_instructions_for('ssh') + end + + scenario 'switches to HTTP', js: true do + visit_project + + select_protocol('HTTP') + + expect_instructions_for('http') + end + end + + def visit_project + visit namespace_project_path(project.namespace, project) + end + + def select_protocol(protocol) + find('#clone-dropdown').click + find(".#{protocol.downcase}-selector").click + end + + def expect_instructions_for(protocol) + msg = :"#{protocol.downcase}_url_to_repo" + + expect(page).to have_content("git clone #{project.send(msg)}") + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 62389188d2c..6f7e2ae78fd 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -88,18 +88,18 @@ describe ProjectsHelper do end describe 'default_clone_protocol' do - describe 'using HTTP' do + context 'when user is not logged in and gitlab protocol is HTTP' do it 'returns HTTP' do - expect(helper).to receive(:current_user).and_return(nil) + allow(helper).to receive(:current_user).and_return(nil) expect(helper.send(:default_clone_protocol)).to eq('http') end end - describe 'using HTTPS' do + context 'when user is not logged in and gitlab protocol is HTTPS' do it 'returns HTTPS' do - allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https') - expect(helper).to receive(:current_user).and_return(nil) + stub_config_setting(protocol: 'https') + allow(helper).to receive(:current_user).and_return(nil) expect(helper.send(:default_clone_protocol)).to eq('https') end From 75523d1df91cc871847a00cad4e5e1d44278a647 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 3 May 2016 21:32:49 -0700 Subject: [PATCH 0143/1431] Sanitize repo paths in new project error message Closes #17243 --- CHANGELOG | 1 + app/helpers/projects_helper.rb | 6 ++++++ app/views/projects/imports/new.html.haml | 2 +- spec/helpers/projects_helper_spec.rb | 9 +++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 25d26535b72..e5dc5e5d4a5 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.8.0 (unreleased) - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Make build status canceled if any of the jobs was canceled and none failed + - Sanitize repo paths in new project error message - Remove future dates from contribution calendar graph. - Support e-mail notifications for comments on project snippets - Use ActionDispatch Remote IP for Akismet checking diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3d5e61d2c18..3720b0085e5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -341,4 +341,10 @@ module ProjectsHelper ) end end + + def sanitize_repo_path(message) + return '' unless message.present? + + message.strip.gsub(Gitlab.config.gitlab_shell.repos_path.chomp('/'), "[REPOS PATH]") + end end diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 6027fb23360..a8a8caf7280 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -10,7 +10,7 @@ .panel-body %pre :preserve - #{@project.import_error.try(:strip)} + #{sanitize_repo_path(@project.import_error)} = form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| = render "shared/import_form", f: f diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 62389188d2c..29bcb8c5892 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -131,4 +131,13 @@ describe ProjectsHelper do end end end + + describe '#sanitized_import_error' do + it 'removes the repo path' do + repo = File.join(Gitlab.config.gitlab_shell.repos_path, '/namespace/test.git') + import_error = "Could not clone #{repo}\n" + + expect(sanitize_repo_path(import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') + end + end end From e4c64855e8531a9375de1d64a95f2e593b80c2bd Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Wed, 27 Apr 2016 23:37:07 +0200 Subject: [PATCH 0144/1431] Don't read otp_secret_encryption_key from hardcoded path in models/user Variable `Gitlab::Application.config.secret_key_base` is set in config/initializers/secret_token.rb. It's very bad practice to use hard-coded paths inside an application and really unnecessary in this case. --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index ab48f8f1960..a468b6ea075 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -85,7 +85,7 @@ class User < ActiveRecord::Base default_value_for :theme_id, gitlab_config.default_theme devise :two_factor_authenticatable, - otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp + otp_secret_encryption_key: Gitlab::Application.config.secret_key_base alias_attribute :two_factor_enabled, :otp_required_for_login devise :two_factor_backupable, otp_number_of_backup_codes: 10 From 7878eb9fa69123981281c2d71a2964dca1b459ee Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 25 Apr 2016 15:02:15 -0500 Subject: [PATCH 0145/1431] Start builds redesign --- app/assets/stylesheets/framework/blocks.scss | 9 +++++-- app/assets/stylesheets/framework/buttons.scss | 2 +- .../stylesheets/framework/tw_bootstrap.scss | 2 +- .../stylesheets/framework/typography.scss | 4 ++-- .../stylesheets/framework/variables.scss | 3 ++- app/assets/stylesheets/pages/commit.scss | 12 ++++++++-- app/assets/stylesheets/pages/detail_page.scss | 2 +- .../stylesheets/pages/merge_requests.scss | 20 ++++++++++++++-- app/assets/stylesheets/pages/projects.scss | 2 +- app/views/admin/builds/_build.html.haml | 20 ++++++++-------- app/views/admin/builds/index.html.haml | 3 ++- app/views/admin/logs/show.html.haml | 2 +- app/views/admin/users/index.html.haml | 2 +- app/views/dashboard/todos/index.html.haml | 2 +- app/views/events/_event_last_push.html.haml | 2 +- app/views/explore/groups/index.html.haml | 2 +- app/views/explore/snippets/index.html.haml | 2 +- app/views/groups/issues.html.haml | 2 +- app/views/groups/merge_requests.html.haml | 2 +- app/views/groups/milestones/index.html.haml | 2 +- app/views/help/ui.html.haml | 6 ++--- app/views/projects/_last_push.html.haml | 2 +- app/views/projects/_readme.html.haml | 2 +- app/views/projects/artifacts/browse.html.haml | 2 +- app/views/projects/branches/index.html.haml | 2 +- app/views/projects/builds/index.html.haml | 3 ++- app/views/projects/builds/show.html.haml | 4 ++-- app/views/projects/ci/builds/_build.html.haml | 3 ++- .../projects/commit/_ci_commit.html.haml | 6 +++-- .../projects/commit/_commit_box.html.haml | 24 +++++++++---------- app/views/projects/commits/show.html.haml | 2 +- app/views/projects/compare/index.html.haml | 2 +- app/views/projects/compare/show.html.haml | 2 +- app/views/projects/empty.html.haml | 2 +- app/views/projects/graphs/ci.html.haml | 2 +- app/views/projects/graphs/commits.html.haml | 2 +- app/views/projects/graphs/languages.html.haml | 2 +- app/views/projects/graphs/show.html.haml | 2 +- app/views/projects/network/_head.html.haml | 2 +- app/views/projects/releases/edit.html.haml | 2 +- app/views/projects/show.html.haml | 2 +- app/views/projects/snippets/index.html.haml | 2 +- app/views/projects/tags/index.html.haml | 2 +- app/views/projects/tags/show.html.haml | 2 +- app/views/projects/wikis/git_access.html.haml | 2 +- app/views/search/_results.html.haml | 2 +- app/views/shared/issuable/_filter.html.haml | 4 ++-- app/views/shared/issuable/_form.html.haml | 2 +- app/views/shared/milestones/_top.html.haml | 2 +- app/views/shared/snippets/_header.html.haml | 2 +- .../sherlock/file_samples/show.html.haml | 2 +- app/views/sherlock/queries/show.html.haml | 2 +- .../sherlock/transactions/index.html.haml | 2 +- .../sherlock/transactions/show.html.haml | 2 +- app/views/users/show.html.haml | 2 +- 55 files changed, 118 insertions(+), 85 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index e72e4aa47ef..af6f22da281 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -18,7 +18,7 @@ line-height: 36px; } -.gray-content-block { +.row-content-block { margin-top: 0; margin-bottom: -$gl-padding; background-color: $background-color; @@ -81,6 +81,11 @@ margin-left: 10px; } } + + &.build-content { + background-color: $white-light; + border-top: none; + } } .cover-block { @@ -113,7 +118,7 @@ line-height: 1.1; h1 { - color: #313236; + color: $gl-gray-dark; margin-bottom: 6px; font-size: 23px; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 062da397b6b..edb5ee71f98 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -59,7 +59,7 @@ } @mixin btn-gray { - @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236); + @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark); } @mixin btn-white { diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 96bab7880c2..6a45c34ccbb 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -81,7 +81,7 @@ // Labels .label { - padding: 2px 4px; + padding: 4px 5px; font-size: 13px; font-style: normal; font-weight: normal; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index b2535ddf4bd..2779cd56788 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -42,14 +42,14 @@ margin: 24px 0 12px; padding: 0 0 10px; border-bottom: 1px solid #e7e9ed; - color: #313236; + color: $gl-gray-dark; } h2 { font-size: 1.2em; font-weight: 600; margin: 24px 0 12px; - color: #313236; + color: $gl-gray-dark; } h3 { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 92ecfe50488..4227839eed3 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -20,7 +20,7 @@ $background-color: #fafafa; */ $gl-font-size: 15px; $gl-title-color: #333; -$gl-text-color: #555; +$gl-text-color: #5c5c5c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; @@ -30,6 +30,7 @@ $gl-placeholder-color: #8f8f8f; $gl-icon-color: $gl-placeholder-color; $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; +$gl-gray-dark: #313236; $gl-header-color: $gl-title-color; /* diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 358d2f4ab9d..5c945e2d4c9 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -31,9 +31,17 @@ } .commit-committer-link, .commit-author-link { - color: #444; + color: $gl-gray; font-weight: bold; } + + .ci-status { + margin-right: 10px; + } + + .fa-clipboard { + color: $dropdown-title-btn-color; + } } .commit-box { @@ -42,7 +50,7 @@ .commit-title { margin: 0; font-size: 23px; - color: #313236; + color: $gl-gray-dark; } .commit-description { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 3438dbe4958..5e61e61d85c 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -22,7 +22,7 @@ .title { margin: 0; font-size: 23px; - color: #313236; + color: $gl-gray-dark; } .description { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 4ef548ffbe7..c4005ba1e69 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -104,7 +104,7 @@ font-weight: 600; font-size: 17px; margin: 5px 0; - color: #313236; + color: $gl-gray-dark; } p:last-child { @@ -136,7 +136,7 @@ } .label-branch { - color: #313236; + color: $gl-gray-dark; font-family: $monospace_font; font-weight: bold; overflow: hidden; @@ -272,3 +272,19 @@ display: inline-block; width: 250px; } + +.table-holder { + .builds { + + th { + background-color: $white-light; + color: $gl-placeholder-color; + } + + th, + td { + padding: 16px; + } + } +} + diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 99108e9bfc4..c20f04653fc 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -178,7 +178,7 @@ .option-title { font-weight: normal; display: inline-block; - color: #313236; + color: $gl-gray-dark; } .option-descr { diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 3571eefd570..84b78a6f36a 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -35,15 +35,15 @@ %td #{build.stage} / #{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 + - 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 @@ -69,4 +69,4 @@ %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do - %i.fa.fa-repeat + %i.fa.fa-refresh diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 5931efdefe6..804d7851bdb 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -19,7 +19,7 @@ - if @all_builds.running_or_pending.any? = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post -.gray-content-block.second-block +.row-content-block.second-block #{(@scope || 'running').capitalize} builds %ul.content-list @@ -38,6 +38,7 @@ %th Ref %th Runner %th Name + %th Tags %th Duration %th Finished at %th diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 4b475a4d8fa..698feb571ac 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -7,7 +7,7 @@ %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } = link_to klass::file_name, "##{klass::file_name_noext}", 'data-toggle' => 'tab' -.gray-content-block +.row-content-block To prevent performance issues admin logs output the last 2000 lines .tab-content - loggers.each do |klass| diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 0ee8dc962b9..d6743081c8e 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -32,7 +32,7 @@ Without projects %small.badge= number_with_delimiter(User.without_projects.count) - .gray-content-block.second-block + .row-content-block.second-block .pull-right .dropdown.inline %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 49ab8aad1d5..e27fefc0afd 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -25,7 +25,7 @@ = icon('spinner spin') .todos-filters - .gray-content-block.second-block + .row-content-block.second-block = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do .filter-item.inline = select_tag('project_id', todo_projects_options, diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 5753158c24d..a1a282178e7 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -1,5 +1,5 @@ - if show_last_push_widget?(event) - .gray-content-block.clear-block.last-push-widget + .row-content-block.clear-block.last-push-widget .event-last-push .event-last-push-text %span You pushed to diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 8ffca96bb4e..57f6e7e0612 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -6,7 +6,7 @@ - else = render 'explore/head' -.gray-content-block.clearfix +.row-content-block.clearfix .pull-left = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| = hidden_field_tag :sort, @sort diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 0f100c39ffb..9b838b9f3b7 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -6,7 +6,7 @@ - else = render 'explore/head' -.gray-content-block +.row-content-block - if current_user .pull-right = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index aea35c50862..df6db8c23d3 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -16,7 +16,7 @@ = render 'shared/issuable/filter', type: :issues -.gray-content-block.second-block +.row-content-block.second-block Only issues from %strong #{@group.name} group are listed here. diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index e1c9dd931ee..0cc2305b7d7 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -8,7 +8,7 @@ = render 'shared/issuable/filter', type: :merge_requests -.gray-content-block.second-block +.row-content-block.second-block Only merge requests from %strong #{@group.name} group are listed here. diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index ab307708b75..ddb8be0d496 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -10,7 +10,7 @@ = icon('plus') New Milestone -.gray-content-block +.row-content-block Only milestones from %strong #{@group.name} group are listed here. diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index f12df5c8ffe..d676bc28c89 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -48,14 +48,14 @@ .lead Gray content block with side padding using - %code .gray-content-block + %code .row-content-block .example - .gray-content-block + .row-content-block %h4 Normal block inside content = lorem - .gray-content-block.second-block + .row-content-block.second-block %h4 Second block = lorem diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index f0a3e416db7..7c2b8d01508 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,7 +1,7 @@ - if event = last_push_event - if show_last_push_widget?(event) - .gray-content-block.top-block.clear-block.hidden-xs + .row-content-block.top-block.clear-block.hidden-xs .event-last-push .event-last-push-text %span You pushed to diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index a9908eaecca..369a847e7d4 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -7,7 +7,7 @@ = cache(readme_cache_key) do = render_readme(readme) - else - .gray-content-block.second-block.center + .row-content-block.second-block.center %h3.page-title This project does not have a README yet - if can?(current_user, :push_code, @project) diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 84034c8bf16..49f95ff37db 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -1,7 +1,7 @@ - page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds' = render 'projects/builds/header_title' -.top-block.gray-content-block.clearfix +.top-block.row-content-block.clearfix .pull-right = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-default download' do diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 88266e21230..ac7790421a4 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,7 +1,7 @@ - page_title "Branches" = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block .pull-right - if can? current_user, :push_code, @project = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 0406fc21d77..d842dd54e6b 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -34,7 +34,7 @@ = icon('wrench') %span CI Lint -.gray-content-block +.row-content-block #{(@scope || 'running').capitalize} builds from this project %ul.content-list @@ -52,6 +52,7 @@ %th Ref %th Stage %th Name + %th Tags %th Duration %th Finished at - if @project.build_coverage_enabled? diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 99d72aa7935..c0f7a7686f0 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -2,7 +2,7 @@ = render "header_title" .build-page - .gray-content-block.top-block + .row-content-block.top-block Build ##{@build.id} for commit %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) from @@ -34,7 +34,7 @@ %i.fa.fa-warning This build was retried. - .gray-content-block.middle-block + .row-content-block.middle-block .build-head .clearfix = ci_status_with_icon(@build.status) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index e123eb1cc7a..e5a5efb3153 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -40,6 +40,7 @@ %td = build.name + %td .label-container - if build.tags.any? - build.tags.each do |tag| @@ -76,4 +77,4 @@ %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do - %i.fa.fa-repeat + %i.fa.fa-refresh diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index d3acd33116c..e849aefb188 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,4 +1,4 @@ -.gray-content-block.middle-block +.row-content-block.build-content.middle-block .pull-right - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) @@ -40,6 +40,7 @@ %th Build ID %th Stage %th Name + %th Tags %th Duration %th Finished at - if @project.build_coverage_enabled? @@ -49,7 +50,7 @@ = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true - if ci_commit.retried.any? - .gray-content-block.second-block + .row-content-block.second-block Retried builds .table-holder @@ -61,6 +62,7 @@ %th Ref %th Stage %th Name + %th Tags %th Duration %th Finished at - if @project.build_coverage_enabled? diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 3d7c18a5f58..b73cbf3f1d8 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -22,10 +22,12 @@ %div %p - %span.light Commit - = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" - = clipboard_button(clipboard_text: @commit.id) .commit-info-row + - if @commit.status + = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do + = ci_icon_for_status(@commit.status) + build: + = ci_label_for_status(@commit.status) %span.light Authored by %strong = commit_author_link(@commit, avatar: true, size: 24) @@ -39,19 +41,15 @@ #{time_ago_with_tooltip(@commit.committed_date)} .commit-info-row + %span.light Commit + = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" + = clipboard_button(clipboard_text: @commit.id) %span.cgray= pluralize(@commit.parents.count, "parent") - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @commit.status - .pull-right - = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do - = ci_icon_for_status(@commit.status) - build: - = ci_label_for_status(@commit.status) - -.commit-info-row.branches - %i.fa.fa-spinner.fa-spin + %span.commit-info.branches + %i.fa.fa-spinner.fa-spin .commit-box.content-block %h3.commit-title @@ -61,4 +59,4 @@ = preserve(markdown(escape_once(@commit.description), pipeline: :single_line)) :javascript - $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); + $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index bcdb09208aa..088eaa28013 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -6,7 +6,7 @@ = render "head" -.gray-content-block.second-block +.row-content-block.second-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'commits' diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 02be5a2d07f..5e188dd0f3c 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block Compare branches, tags or commit ranges. %br Fill input field with commit id like diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index da731f28bb6..62525168239 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -3,7 +3,7 @@ = render "projects/commits/head" -.gray-content-block +.row-content-block = render "form" - if @commits.present? diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 52d093871b4..1a2e59752fe 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -7,7 +7,7 @@ = render "home_panel" -.gray-content-block.second-block.center +.row-content-block.second-block.center %h3.page-title The repository for this project is empty - if can?(current_user, :push_code, @project) diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml index 6fa77cc10c6..9f05be9982b 100644 --- a/app/views/projects/graphs/ci.html.haml +++ b/app/views/projects/graphs/ci.html.haml @@ -1,7 +1,7 @@ - page_title "Continuous Integration", "Graphs" = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .oneline A collection of graphs for Continuous Integration diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index fc465ab273b..da9f648cc9c 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs_commits' %ul.breadcrumb.repo-breadcrumb diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml index a7fab5b6d72..ebecab1dbfc 100644 --- a/app/views/projects/graphs/languages.html.haml +++ b/app/views/projects/graphs/languages.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .oneline Programming languages used in this repository diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 882e7d6b6ee..ad4a932d391 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'head' -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs' %ul.breadcrumb.repo-breadcrumb diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index 28a617538b5..c609c505def 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -1,4 +1,4 @@ -.gray-content-block.append-bottom-default +.row-content-block.append-bottom-default .tree-ref-holder = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 6f0b32aa165..0d59cec322c 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block .oneline .title Release notes for tag diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index d854ac21725..74feb9e3282 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -12,7 +12,7 @@ = render 'projects/last_push' = render "home_panel" -.project-stats.gray-content-block.second-block +.project-stats.row-content-block.second-block %ul.nav %li = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 4af963e14da..103ff447464 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,7 +1,7 @@ - page_title "Snippets" = render "header_title" -.gray-content-block.top-block +.row-content-block.top-block .pull-right = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do = icon('plus') diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 760347de0a9..dc6ece30dd2 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block - if can? current_user, :push_code, @project .pull-right = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 1dc9b799a95..9c916fd02de 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -2,7 +2,7 @@ = render "projects/commits/header_title" = render "projects/commits/head" -.gray-content-block +.row-content-block .pull-right - if can?(current_user, :push_code, @project) = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index dd27ea2b11b..ba3f2cadc48 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -2,7 +2,7 @@ = render "header_title" = render 'nav' -.gray-content-block +.row-content-block %span.oneline Git access for %strong= @project_wiki.path_with_namespace diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 711337f308e..252c37532e1 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -1,7 +1,7 @@ - if @search_objects.empty? = render partial: "search/results/empty" - else - .gray-content-block + .row-content-block = search_entries_info(@search_objects, @scope, @search_term) - unless @show_snippets - if @project diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index fc1101646fb..9474462cbd1 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,5 +1,5 @@ .issues-filters - .issues-details-filters.gray-content-block.second-block + .issues-details-filters.row-content-block.second-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) .check-all-holder @@ -48,7 +48,7 @@ = button_tag "Update issues", class: "btn update_selected_issues btn-save" - if !@labels.nil? - .gray-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } + .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } - if @labels.any? = render "shared/labels_row", labels: @labels diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 18b091df39b..5c52cc6d1da 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -116,7 +116,7 @@ = link_to 'Change branches', mr_change_branches_path(@merge_request) - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) -.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")} +.row-content-block{class: (is_footer ? "footer-block" : "middle-block")} - if issuable.new_record? = f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create' - else diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 7ff947a51db..8bca971bcf2 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -24,7 +24,7 @@ - else = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" -.detail-page-description.milestone-detail +.detail-page-description.milestone-detailrow-content-block.second-block %h2.title = markdown escape_once(milestone.title), pipeline: :single_line diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 3c445f67236..e65b1814872 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -20,6 +20,6 @@ - else = render "snippets/actions" -.detail-page-description.gray-content-block.second-block +.detail-page-description.row-content-block.second-block %h2.title = markdown escape_once(@snippet.title), pipeline: :single_line diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml index cfd11e45b6a..94d4dd4fa7d 100644 --- a/app/views/sherlock/file_samples/show.html.haml +++ b/app/views/sherlock/file_samples/show.html.haml @@ -3,7 +3,7 @@ - header_title t('sherlock.title'), sherlock_transactions_path -.gray-content-block +.row-content-block .pull-right = link_to(sherlock_transaction_path(@transaction), class: 'btn') do %i.fa.fa-arrow-left diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml index 83f61ce4b07..fc2863dca8e 100644 --- a/app/views/sherlock/queries/show.html.haml +++ b/app/views/sherlock/queries/show.html.haml @@ -9,7 +9,7 @@ %a(href="#tab-backtrace" data-toggle="tab") = t('sherlock.backtrace') -.gray-content-block +.row-content-block .pull-right = link_to(sherlock_transaction_path(@transaction), class: 'btn') do %i.fa.fa-arrow-left diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml index 010e1a2a902..da969c02765 100644 --- a/app/views/sherlock/transactions/index.html.haml +++ b/app/views/sherlock/transactions/index.html.haml @@ -1,7 +1,7 @@ - page_title t('sherlock.title') - header_title t('sherlock.title'), sherlock_transactions_path -.gray-content-block +.row-content-block .pull-right = link_to(destroy_all_sherlock_transactions_path, class: 'btn btn-danger', diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml index 9d4b0b2724c..8aa6b437d95 100644 --- a/app/views/sherlock/transactions/show.html.haml +++ b/app/views/sherlock/transactions/show.html.haml @@ -16,7 +16,7 @@ %span.badge #{@transaction.file_samples.length} -.gray-content-block +.row-content-block .pull-right = link_to(sherlock_transactions_path, class: 'btn') do %i.fa.fa-arrow-left diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 03511b0654f..3c0b89c6741 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -85,7 +85,7 @@ %div{ class: container_class } .tab-content #activity.tab-pane - .gray-content-block.calender-block.white.second-block.hidden-xs + .row-content-block.calender-block.white.second-block.hidden-xs %div{ class: container_class } .user-calendar{data: {href: user_calendar_path}} %h4.center.light From fc20639c8d76b367dc0e0b66ef94033697b28dc7 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 26 Apr 2016 13:20:58 -0500 Subject: [PATCH 0146/1431] Builds page redesign --- app/assets/stylesheets/framework/buttons.scss | 6 ++++++ app/assets/stylesheets/pages/builds.scss | 13 +++++++++++++ app/assets/stylesheets/pages/commit.scss | 18 ++++++++++++++++-- app/assets/stylesheets/pages/status.scss | 2 +- app/helpers/application_helper.rb | 3 +-- app/views/admin/builds/_build.html.haml | 2 +- app/views/dashboard/snippets/index.html.haml | 16 ++++++++-------- app/views/dashboard/todos/index.html.haml | 8 ++++---- app/views/projects/builds/index.html.haml | 13 +++++++------ app/views/projects/ci/builds/_build.html.haml | 2 +- app/views/projects/commit/_ci_menu.html.haml | 4 ++-- .../projects/commit/_commit_box.html.haml | 2 +- app/views/projects/commit/branches.html.haml | 1 - app/views/projects/commits/_head.html.haml | 9 ++++++--- .../projects/merge_requests/_show.html.haml | 12 ++++++++---- 15 files changed, 75 insertions(+), 36 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index edb5ee71f98..1f2f9f7754c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -251,3 +251,9 @@ .btn-file-option { background: linear-gradient(180deg, $white-light 25%, $gray-light 100%); } + +.btn-refresh { + i { + color: $gl-icon-color; + } +} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 201f3e5ca46..3340cee9643 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -83,3 +83,16 @@ } } } + +table.builds { + + .build-link { + a { + color: $gl-dark-link-color; + } + } + + .btn-refresh { + margin-left: 10px; + } +} diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 5c945e2d4c9..c2cd227571f 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -35,13 +35,19 @@ font-weight: bold; } - .ci-status { - margin-right: 10px; + .time_ago { + margin-left: 8px; } .fa-clipboard { color: $dropdown-title-btn-color; } + + .commit-info { + &.branches { + margin-left: 8px; + } + } } .commit-box { @@ -91,6 +97,14 @@ } } +.commit-action-buttons { + i { + color: $gl-icon-color; + font-size: 13px; + margin-right: 3px; + } +} + /* * Commit message textarea for web editor and * custom merge request message diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index dbb6daf0d70..2370d35924e 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,7 +1,7 @@ .container-fluid { .ci-status { padding: 2px 7px; - margin-right: 5px; + margin-right: 10px; border: 1px solid #eee; white-space: nowrap; @include border-radius(4px); diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3e0074da394..313a8fd56c5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -316,8 +316,7 @@ module ApplicationHelper html = content_tag :span, entity_title if count.present? - html += " " - html += content_tag :span, number_with_delimiter(count), class: 'badge' + html += content_tag :span, " (#{number_with_delimiter(count)})" end html.html_safe diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 84b78a6f36a..2b99f360d6f 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -68,5 +68,5 @@ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-refresh' do %i.fa.fa-refresh diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index d4e7862981c..53f1094ec07 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -13,26 +13,26 @@ %li{ class: ("active" unless params[:scope]) } = link_to dashboard_snippets_path do All - %span.badge - = current_user.snippets.count + %span + (#{current_user.snippets.count}) %li{ class: ("active" if params[:scope] == "are_private") } = link_to dashboard_snippets_path(scope: 'are_private') do Private - %span.badge - = current_user.snippets.are_private.count + %span + (#{current_user.snippets.are_private.count}) %li{ class: ("active" if params[:scope] == "are_internal") } = link_to dashboard_snippets_path(scope: 'are_internal') do Internal - %span.badge - = current_user.snippets.are_internal.count + %span + (#{current_user.snippets.are_internal.count}) %li{ class: ("active" if params[:scope] == "are_public") } = link_to dashboard_snippets_path(scope: 'are_public') do Public - %span.badge - = current_user.snippets.are_public.count + %span + (#{current_user.snippets.are_public.count}) = render 'snippets/snippets' diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index e27fefc0afd..a87b796c6f3 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -8,15 +8,15 @@ = link_to todos_filter_path(state: 'pending') do %span To do - %span{class: 'badge'} - = todos_pending_count + %span + (#{todos_pending_count}) - todo_done_active = ('active' if params[:state] == 'done') %li{class: "todos-done #{todo_done_active}"} = link_to todos_filter_path(state: 'done') do %span Done - %span{class: 'badge'} - = todos_done_count + %span + (#{todos_done_count}) .nav-controls - if @todos.any?(&:pending?) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index d842dd54e6b..fcb1e656d72 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -6,20 +6,21 @@ %li{class: ('active' if @scope.nil?)} = link_to project_builds_path(@project) do All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_builds.count(:id)) + %span.js-totalbuilds-count + (#{number_with_delimiter(@all_builds.count(:id))}) + %li{class: ('active' if @scope == 'running')} = link_to project_builds_path(@project, scope: :running) do Running - %span.badge.js-running-count - = number_with_delimiter(@all_builds.running_or_pending.count(:id)) + %span.js-running-count + (#{number_with_delimiter(@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 - = number_with_delimiter(@all_builds.finished.count(:id)) + %span.js-running-count + (#{number_with_delimiter(@all_builds.finished.count(:id))}) .nav-controls - if can?(current_user, :update_build, @project) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index e5a5efb3153..1b382e48365 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -76,5 +76,5 @@ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-refresh' do %i.fa.fa-refresh diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index ea33aa472a6..b2d3db0bdc6 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -2,8 +2,8 @@ = nav_link(path: 'commit#show') do = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do Changes - %span.badge= @diffs.count + %span (#{@diffs.count}) = nav_link(path: 'commit#builds') do = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do Builds - %span.badge= @statuses.count + %span (#{@statuses.count}) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index b73cbf3f1d8..01163e526b2 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,4 +1,4 @@ -.pull-right +.pull-right.commit-action-buttons %div - if @notes_count > 0 %span.btn.disabled.btn-grouped diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml index 82aac1fbd15..2b0c9a4b4de 100644 --- a/app/views/projects/commit/branches.html.haml +++ b/app/views/projects/commit/branches.html.haml @@ -3,7 +3,6 @@ - branch = commit_default_branch(@project, @branches) = link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do %span.label.label-gray - %i.fa.fa-code-fork = branch - if @branches.any? || @tags.any? = link_to("#", class: "js-details-expand") do diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 7a5b0d993db..2c8fecae760 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -2,7 +2,8 @@ = nav_link(controller: [:commit, :commits]) do = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do Commits - %span.badge= number_with_delimiter(@repository.commit_count) + %span + (#{number_with_delimiter(@repository.commit_count)}) = nav_link(controller: %w(network)) do = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do @@ -15,9 +16,11 @@ = nav_link(html_options: {class: branches_tab_class}) do = link_to namespace_project_branches_path(@project.namespace, @project) do Branches - %span.badge.js-totalbranch-count= @repository.branch_count + %span.js-totalbranch-count + (#{@repository.branch_count}) = nav_link(controller: [:tags, :releases]) do = link_to namespace_project_tags_path(@project.namespace, @project) do Tags - %span.badge.js-totaltags-count= @repository.tag_count + %span.js-totaltags-count + (#{@repository.tag_count}) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 290753d57c6..0c97a29847d 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -50,20 +50,24 @@ %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do Discussion - %span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count + %span + (#{@merge_request.mr_and_commit_notes.user.nonawards.count}) %li.commits-tab = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits - %span.badge= @commits.size + %span + (#{@commits.size}) - if @ci_commit %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do Builds - %span.badge= @statuses.size + %span + (#{@statuses.size}) %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes - %span.badge= @merge_request.diff_size + %span + (#{@merge_request.diff_size}) .tab-content #notes.notes.tab-pane.voting_notes From 3a3f0bf436007b3eecc0b85d0fc696951245ab6a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 27 Apr 2016 09:46:31 -0500 Subject: [PATCH 0147/1431] Change build icons into buttons; update tests --- app/assets/stylesheets/framework/buttons.scss | 3 ++- app/assets/stylesheets/pages/builds.scss | 4 ---- app/views/admin/builds/_build.html.haml | 6 +++--- app/views/projects/ci/builds/_build.html.haml | 6 +++--- features/project/merge_requests.feature | 8 ++++---- features/steps/dashboard/todos.rb | 8 ++++---- features/steps/project/merge_requests.rb | 10 +++++----- spec/features/notes_on_merge_requests_spec.rb | 2 +- 8 files changed, 22 insertions(+), 25 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 1f2f9f7754c..eaf85bb17ca 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -252,7 +252,8 @@ background: linear-gradient(180deg, $white-light 25%, $gray-light 100%); } -.btn-refresh { +.btn-build { + margin-left: 10px; i { color: $gl-icon-color; } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 3340cee9643..aa41565f812 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -91,8 +91,4 @@ table.builds { color: $gl-dark-link-color; } } - - .btn-refresh { - margin-left: 10px; - } } diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 2b99f360d6f..967151bc33b 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -61,12 +61,12 @@ %td .pull-right - if can?(current_user, :read_build, project) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do + = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do %i.fa.fa-download - if can?(current_user, :update_build, build.project) - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do + = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-refresh' do + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do %i.fa.fa-refresh diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 1b382e48365..8e95f040273 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -69,12 +69,12 @@ %td .pull-right - if can?(current_user, :read_build, build) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do + = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do %i.fa.fa-download - if can?(current_user, :update_build, build) - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do + = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do %i.fa.fa-remove.cred - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-refresh' do + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do %i.fa.fa-refresh diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index ecda4ea8240..aec590f7d9e 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -135,7 +135,7 @@ Feature: Project Merge Requests And I leave a comment like "Line is wrong" on diff And I switch to the merge request's comments tab Then I should see a discussion has started on diff - And I should see a badge of "1" next to the discussion link + And I should see a badge of "(1)" next to the discussion link @javascript Scenario: I see a new comment on merge request diff from another user in the discussion tab @@ -143,7 +143,7 @@ Feature: Project Merge Requests And I visit merge request page "Bug NS-05" And user "John Doe" leaves a comment like "Line is wrong" on diff Then I should see a discussion by user "John Doe" has started on diff - And I should see a badge of "1" next to the discussion link + And I should see a badge of "(1)" next to the discussion link @javascript Scenario: I edit a comment on a merge request diff @@ -161,11 +161,11 @@ Feature: Project Merge Requests And I visit merge request page "Bug NS-05" And I click on the Changes tab And I leave a comment like "Line is wrong" on diff - And I should see a badge of "1" next to the discussion link + And I should see a badge of "(1)" next to the discussion link And I delete the comment "Line is wrong" on diff And I click on the Discussion tab Then I should not see any discussion - And I should see a badge of "0" next to the discussion link + And I should see a badge of "(0)" next to the discussion link @javascript Scenario: I comment on a line of a commit in merge request diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb index 2b23df6764b..da380250cd7 100644 --- a/features/steps/dashboard/todos.rb +++ b/features/steps/dashboard/todos.rb @@ -27,8 +27,8 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps step 'I should see todos assigned to me' do page.within('.nav-sidebar') { expect(page).to have_content 'Todos 4' } - expect(page).to have_content 'To do 4' - expect(page).to have_content 'Done 0' + expect(page).to have_content 'To do (4)' + expect(page).to have_content 'Done (0)' expect(page).to have_link project.name_with_namespace should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title) @@ -43,8 +43,8 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps end page.within('.nav-sidebar') { expect(page).to have_content 'Todos 3' } - expect(page).to have_content 'To do 3' - expect(page).to have_content 'Done 1' + expect(page).to have_content 'To do (3)' + expect(page).to have_content 'Done (1)' should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 3b1a00f628a..463235742fb 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -344,12 +344,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end end - step 'I should see a badge of "1" next to the discussion link' do - expect_discussion_badge_to_have_counter("1") + step 'I should see a badge of "(1)" next to the discussion link' do + expect_discussion_badge_to_have_counter("(1)") end - step 'I should see a badge of "0" next to the discussion link' do - expect_discussion_badge_to_have_counter("0") + step 'I should see a badge of "(0)" next to the discussion link' do + expect_discussion_badge_to_have_counter("(0)") end step 'I should see a discussion has started on commit diff' do @@ -572,7 +572,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end def expect_discussion_badge_to_have_counter(value) - page.within(".notes-tab .badge") do + page.within(".notes-tab span") do page.should have_content value end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 389812ff7e1..0aef4cba238 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -151,7 +151,7 @@ describe 'Comments', feature: true do visit namespace_project_merge_request_path(project.namespace, project, merge_request) expect(merge_request.mr_and_commit_notes.count).to eq 2 - expect(find('.notes-tab span.badge').text).to eq "1" + expect(find('.notes-tab span').text).to eq "(1)" end end end From 8d19955c05f18675fc16dee6ca0f4da24013e816 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 4 May 2016 16:25:10 -0500 Subject: [PATCH 0148/1431] Revert counter parentheses back to badges --- app/helpers/application_helper.rb | 3 ++- app/views/dashboard/snippets/index.html.haml | 16 ++++++++-------- app/views/dashboard/todos/index.html.haml | 8 ++++---- app/views/projects/builds/index.html.haml | 12 ++++++------ app/views/projects/commit/_ci_menu.html.haml | 4 ++-- app/views/projects/commits/_head.html.haml | 10 ++++------ .../projects/merge_requests/_show.html.haml | 12 ++++-------- app/views/shared/milestones/_top.html.haml | 2 +- features/project/merge_requests.feature | 8 ++++---- features/steps/dashboard/todos.rb | 8 ++++---- features/steps/project/merge_requests.rb | 10 +++++----- spec/features/notes_on_merge_requests_spec.rb | 2 +- 12 files changed, 45 insertions(+), 50 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 313a8fd56c5..3e0074da394 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -316,7 +316,8 @@ module ApplicationHelper html = content_tag :span, entity_title if count.present? - html += content_tag :span, " (#{number_with_delimiter(count)})" + html += " " + html += content_tag :span, number_with_delimiter(count), class: 'badge' end html.html_safe diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 53f1094ec07..d4e7862981c 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -13,26 +13,26 @@ %li{ class: ("active" unless params[:scope]) } = link_to dashboard_snippets_path do All - %span - (#{current_user.snippets.count}) + %span.badge + = current_user.snippets.count %li{ class: ("active" if params[:scope] == "are_private") } = link_to dashboard_snippets_path(scope: 'are_private') do Private - %span - (#{current_user.snippets.are_private.count}) + %span.badge + = current_user.snippets.are_private.count %li{ class: ("active" if params[:scope] == "are_internal") } = link_to dashboard_snippets_path(scope: 'are_internal') do Internal - %span - (#{current_user.snippets.are_internal.count}) + %span.badge + = current_user.snippets.are_internal.count %li{ class: ("active" if params[:scope] == "are_public") } = link_to dashboard_snippets_path(scope: 'are_public') do Public - %span - (#{current_user.snippets.are_public.count}) + %span.badge + = current_user.snippets.are_public.count = render 'snippets/snippets' diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index a87b796c6f3..fc42e5dcc66 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -8,15 +8,15 @@ = link_to todos_filter_path(state: 'pending') do %span To do - %span - (#{todos_pending_count}) + %span.badge + = todos_pending_count - todo_done_active = ('active' if params[:state] == 'done') %li{class: "todos-done #{todo_done_active}"} = link_to todos_filter_path(state: 'done') do %span Done - %span - (#{todos_done_count}) + %span.badge + = todos_done_count .nav-controls - if @todos.any?(&:pending?) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index fcb1e656d72..2e8015d119b 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -6,21 +6,21 @@ %li{class: ('active' if @scope.nil?)} = link_to project_builds_path(@project) do All - %span.js-totalbuilds-count - (#{number_with_delimiter(@all_builds.count(:id))}) + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_builds.count(:id)) %li{class: ('active' if @scope == 'running')} = link_to project_builds_path(@project, scope: :running) do Running - %span.js-running-count - (#{number_with_delimiter(@all_builds.running_or_pending.count(:id))}) + %span.badge.js-running-count + = number_with_delimiter(@all_builds.running_or_pending.count(:id)) %li{class: ('active' if @scope == 'finished')} = link_to project_builds_path(@project, scope: :finished) do Finished - %span.js-running-count - (#{number_with_delimiter(@all_builds.finished.count(:id))}) + %span.badge.js-running-count + = number_with_delimiter(@all_builds.finished.count(:id)) .nav-controls - if can?(current_user, :update_build, @project) diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index b2d3db0bdc6..ea33aa472a6 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -2,8 +2,8 @@ = nav_link(path: 'commit#show') do = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do Changes - %span (#{@diffs.count}) + %span.badge= @diffs.count = nav_link(path: 'commit#builds') do = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do Builds - %span (#{@statuses.count}) + %span.badge= @statuses.count diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 2c8fecae760..d1bd76ab529 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -2,8 +2,8 @@ = nav_link(controller: [:commit, :commits]) do = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do Commits - %span - (#{number_with_delimiter(@repository.commit_count)}) + %span.badge + = number_with_delimiter(@repository.commit_count) = nav_link(controller: %w(network)) do = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do @@ -16,11 +16,9 @@ = nav_link(html_options: {class: branches_tab_class}) do = link_to namespace_project_branches_path(@project.namespace, @project) do Branches - %span.js-totalbranch-count - (#{@repository.branch_count}) + %span.badge.js-totalbranch-count= @repository.branch_count = nav_link(controller: [:tags, :releases]) do = link_to namespace_project_tags_path(@project.namespace, @project) do Tags - %span.js-totaltags-count - (#{@repository.tag_count}) + %span.badge.js-totaltags-count= @repository.tag_count diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 0c97a29847d..290753d57c6 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -50,24 +50,20 @@ %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do Discussion - %span - (#{@merge_request.mr_and_commit_notes.user.nonawards.count}) + %span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count %li.commits-tab = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits - %span - (#{@commits.size}) + %span.badge= @commits.size - if @ci_commit %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do Builds - %span - (#{@statuses.size}) + %span.badge= @statuses.size %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes - %span - (#{@merge_request.diff_size}) + %span.badge= @merge_request.diff_size .tab-content #notes.notes.tab-pane.voting_notes diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 8bca971bcf2..7ff947a51db 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -24,7 +24,7 @@ - else = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" -.detail-page-description.milestone-detailrow-content-block.second-block +.detail-page-description.milestone-detail %h2.title = markdown escape_once(milestone.title), pipeline: :single_line diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index aec590f7d9e..ecda4ea8240 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -135,7 +135,7 @@ Feature: Project Merge Requests And I leave a comment like "Line is wrong" on diff And I switch to the merge request's comments tab Then I should see a discussion has started on diff - And I should see a badge of "(1)" next to the discussion link + And I should see a badge of "1" next to the discussion link @javascript Scenario: I see a new comment on merge request diff from another user in the discussion tab @@ -143,7 +143,7 @@ Feature: Project Merge Requests And I visit merge request page "Bug NS-05" And user "John Doe" leaves a comment like "Line is wrong" on diff Then I should see a discussion by user "John Doe" has started on diff - And I should see a badge of "(1)" next to the discussion link + And I should see a badge of "1" next to the discussion link @javascript Scenario: I edit a comment on a merge request diff @@ -161,11 +161,11 @@ Feature: Project Merge Requests And I visit merge request page "Bug NS-05" And I click on the Changes tab And I leave a comment like "Line is wrong" on diff - And I should see a badge of "(1)" next to the discussion link + And I should see a badge of "1" next to the discussion link And I delete the comment "Line is wrong" on diff And I click on the Discussion tab Then I should not see any discussion - And I should see a badge of "(0)" next to the discussion link + And I should see a badge of "0" next to the discussion link @javascript Scenario: I comment on a line of a commit in merge request diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb index da380250cd7..2b23df6764b 100644 --- a/features/steps/dashboard/todos.rb +++ b/features/steps/dashboard/todos.rb @@ -27,8 +27,8 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps step 'I should see todos assigned to me' do page.within('.nav-sidebar') { expect(page).to have_content 'Todos 4' } - expect(page).to have_content 'To do (4)' - expect(page).to have_content 'Done (0)' + expect(page).to have_content 'To do 4' + expect(page).to have_content 'Done 0' expect(page).to have_link project.name_with_namespace should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title) @@ -43,8 +43,8 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps end page.within('.nav-sidebar') { expect(page).to have_content 'Todos 3' } - expect(page).to have_content 'To do (3)' - expect(page).to have_content 'Done (1)' + expect(page).to have_content 'To do 3' + expect(page).to have_content 'Done 1' should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 463235742fb..3b1a00f628a 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -344,12 +344,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end end - step 'I should see a badge of "(1)" next to the discussion link' do - expect_discussion_badge_to_have_counter("(1)") + step 'I should see a badge of "1" next to the discussion link' do + expect_discussion_badge_to_have_counter("1") end - step 'I should see a badge of "(0)" next to the discussion link' do - expect_discussion_badge_to_have_counter("(0)") + step 'I should see a badge of "0" next to the discussion link' do + expect_discussion_badge_to_have_counter("0") end step 'I should see a discussion has started on commit diff' do @@ -572,7 +572,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end def expect_discussion_badge_to_have_counter(value) - page.within(".notes-tab span") do + page.within(".notes-tab .badge") do page.should have_content value end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 0aef4cba238..389812ff7e1 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -151,7 +151,7 @@ describe 'Comments', feature: true do visit namespace_project_merge_request_path(project.namespace, project, merge_request) expect(merge_request.mr_and_commit_notes.count).to eq 2 - expect(find('.notes-tab span').text).to eq "(1)" + expect(find('.notes-tab span.badge').text).to eq "1" end end end From 655b2640ae9cbe2737369401969e99caeddf192d Mon Sep 17 00:00:00 2001 From: Benedikt Huss Date: Wed, 4 May 2016 09:34:25 +0200 Subject: [PATCH 0149/1431] Merge request widget displays TeamCity build state and code coverage correctly again --- .../merge_request_widget.js.coffee | 12 +++---- .../merge_request_widget_spec.js.coffee | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 spec/javascripts/merge_request_widget_spec.js.coffee diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 065626beeb8..1abda3530c4 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -68,13 +68,13 @@ class @MergeRequestWidget $.getJSON @opts.ci_status_url, (data) => @readyForCICheck = true - if @firstCICheck - @firstCICheck = false + if @firstCICheck || @opts.ci_status is '' + if @firstCICheck + @firstCICheck = false @opts.ci_status = data.status - - if @opts.ci_status is '' - @opts.ci_status = data.status - return + @showCIStatus data.status + if data.coverage + @showCICoverage data.coverage if data.status isnt @opts.ci_status and data.status? @showCIStatus data.status diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee new file mode 100644 index 00000000000..e1fb610654e --- /dev/null +++ b/spec/javascripts/merge_request_widget_spec.js.coffee @@ -0,0 +1,35 @@ +#= require merge_request_widget + +describe 'MergeRequestWidget', -> + + beforeEach -> + window.notifyPermissions = () -> + @opts = {ci_status_url:"http://sampledomain.local/ci/getstatus",ci_status:""} + @class = new MergeRequestWidget(@opts) + @ciStatusData = {"title":"Sample MR title","sha":"12a34bc5","status":"success","coverage":98} + + describe 'getCIStatus', -> + beforeEach -> + spyOn(jQuery, 'getJSON').and.callFake (req, cb) => + cb(@ciStatusData) + + it 'should call showCIStatus even if a notification should not be displayed', -> + spy = spyOn(@class, 'showCIStatus').and.stub() + @class.getCIStatus(false) + expect(spy).toHaveBeenCalledWith(@ciStatusData.status) + + it 'should call showCIStatus when a notification should be displayed', -> + spy = spyOn(@class, 'showCIStatus').and.stub() + @class.getCIStatus(true) + expect(spy).toHaveBeenCalledWith(@ciStatusData.status) + + it 'should call showCICoverage when the coverage rate is set', -> + spy = spyOn(@class, 'showCICoverage').and.stub() + @class.getCIStatus(true) + expect(spy).toHaveBeenCalledWith(@ciStatusData.coverage) + + it 'should not call showCICoverage when the coverage rate is not set', -> + @ciStatusData.coverage = null + spy = spyOn(@class, 'showCICoverage').and.stub() + @class.getCIStatus(true) + expect(spy).not.toHaveBeenCalled() From 92c25f42009ce42c439efa4236d14ac1440711e4 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 4 May 2016 17:33:58 -0400 Subject: [PATCH 0150/1431] Use `number_to_human_size` helper to show repository size This will intelligently format large repository sizes in GBs (or, shudder, TBs). --- app/helpers/projects_helper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3d5e61d2c18..058233c88ad 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -200,7 +200,8 @@ module ProjectsHelper end def repository_size(project = @project) - "#{project.repository_size} MB" + size_in_bytes = project.repository_size * 1.megabyte + number_to_human_size(size_in_bytes, delimiter: ',', precision: 2) rescue # In order to prevent 500 error # when application cannot allocate memory From 5448970950008fb49092fcfbdb155161d707e10f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 4 May 2016 17:39:06 -0400 Subject: [PATCH 0151/1431] Remove `rescue` clause from `repository_size` helper The repository size has since become calculated (and cached) more intelligently, and this should no longer be necessary. --- app/helpers/projects_helper.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 058233c88ad..20e47f77b48 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -202,11 +202,6 @@ module ProjectsHelper def repository_size(project = @project) size_in_bytes = project.repository_size * 1.megabyte number_to_human_size(size_in_bytes, delimiter: ',', precision: 2) - rescue - # In order to prevent 500 error - # when application cannot allocate memory - # to calculate repo size - just show 'Unknown' - 'unknown' end def default_url_to_repo(project = @project) From 0e9c2e721b08e244e63f1d26ac3771a8d858cd76 Mon Sep 17 00:00:00 2001 From: Benedikt Huss Date: Wed, 4 May 2016 20:45:12 +0200 Subject: [PATCH 0152/1431] Feedback from stanhu --- CHANGELOG | 1 + .../merge_request_widget.js.coffee | 19 ++++++++---------- .../merge_request_widget_spec.js.coffee | 20 ++++++++++++++++--- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0e6e2505ec6..d2ff4c32f2d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.8.0 (unreleased) - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - Expire repository exists? and has_visible_content? caches after a push if necessary + - Merge request widget displays TeamCity build state and code coverage correctly again. v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 1abda3530c4..17a5a057a94 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -68,20 +68,18 @@ class @MergeRequestWidget $.getJSON @opts.ci_status_url, (data) => @readyForCICheck = true - if @firstCICheck || @opts.ci_status is '' - if @firstCICheck - @firstCICheck = false + if data.status is '' + return + + if @firstCiCheck || data.status isnt @opts.ci_status and data.status? @opts.ci_status = data.status @showCIStatus data.status if data.coverage @showCICoverage data.coverage - if data.status isnt @opts.ci_status and data.status? - @showCIStatus data.status - if data.coverage - @showCICoverage data.coverage - - if showNotification + # The first check should only update the UI, a notification + # should only be displayed on status changes + if showNotification and not @firstCiCheck status = @ciLabelForStatus(data.status) if status is "preparing" @@ -104,8 +102,7 @@ class @MergeRequestWidget @close() Turbolinks.visit _this.opts.builds_path ) - - @opts.ci_status = data.status + @firstCiCheck = false showCIStatus: (state) -> $('.ci_widget').hide() diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee index e1fb610654e..c0bd8a29e43 100644 --- a/spec/javascripts/merge_request_widget_spec.js.coffee +++ b/spec/javascripts/merge_request_widget_spec.js.coffee @@ -4,7 +4,21 @@ describe 'MergeRequestWidget', -> beforeEach -> window.notifyPermissions = () -> - @opts = {ci_status_url:"http://sampledomain.local/ci/getstatus",ci_status:""} + window.notify = () -> + @opts = { + ci_status_url:"http://sampledomain.local/ci/getstatus", + ci_status:"", + ci_message: { + normal: "Build {{status}} for \"{{title}}\"", + preparing: "{{status}} build for \"{{title}}\"" + }, + ci_title: { + preparing: "{{status}} build", + normal: "Build {{status}}" + }, + gitlab_icon:"gitlab_logo.png", + builds_path:"http://sampledomain.local/sampleBuildsPath" + } @class = new MergeRequestWidget(@opts) @ciStatusData = {"title":"Sample MR title","sha":"12a34bc5","status":"success","coverage":98} @@ -25,11 +39,11 @@ describe 'MergeRequestWidget', -> it 'should call showCICoverage when the coverage rate is set', -> spy = spyOn(@class, 'showCICoverage').and.stub() - @class.getCIStatus(true) + @class.getCIStatus(false) expect(spy).toHaveBeenCalledWith(@ciStatusData.coverage) it 'should not call showCICoverage when the coverage rate is not set', -> @ciStatusData.coverage = null spy = spyOn(@class, 'showCICoverage').and.stub() - @class.getCIStatus(true) + @class.getCIStatus(false) expect(spy).not.toHaveBeenCalled() From e37b31456774c92fd718f7ef0a576266ffcca894 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Wed, 4 May 2016 15:49:39 -0700 Subject: [PATCH 0153/1431] Add tests for setting trusted_proxies Each test reloads the trusted_proxies initializer, which in turn will set Rails.application.config.action_dispatch.trusted_proxies to something new. This will leak into the other tests, but the middleware that it is used in has already been loaded for the whole test suite, so it should have no impact. --- spec/initializers/trusted_proxies_spec.rb | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 spec/initializers/trusted_proxies_spec.rb diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb new file mode 100644 index 00000000000..4bb149f25ff --- /dev/null +++ b/spec/initializers/trusted_proxies_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'trusted_proxies', lib: true do + context 'with default config' do + before do + set_trusted_proxies([]) + end + + it 'preserves private IPs as remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '10.1.5.89') + expect(request.remote_ip).to eq('10.1.5.89') + end + + it 'filters out localhost from remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '1.1.1.1, 10.1.5.89, 127.0.0.1') + expect(request.remote_ip).to eq('10.1.5.89') + end + end + + context 'with private IP ranges added' do + before do + set_trusted_proxies([ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ]) + end + + it 'filters out private and local IPs from remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 10.1.5.89, 127.0.0.1') + expect(request.remote_ip).to eq('1.1.1.1') + end + end + + context 'with proxy IP added' do + before do + set_trusted_proxies([ "60.98.25.47" ]) + end + + it 'filters out proxy IP from remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 60.98.25.47, 127.0.0.1') + expect(request.remote_ip).to eq('1.1.1.1') + end + end + + def stub_request(headers = {}) + ActionDispatch::RemoteIp.new(Proc.new { }, false, Rails.application.config.action_dispatch.trusted_proxies).call(headers) + ActionDispatch::Request.new(headers) + end + + def set_trusted_proxies(proxies = []) + stub_config_setting('trusted_proxies' => proxies) + load File.join(__dir__, '../../config/initializers/trusted_proxies.rb') + end +end From 8dc19494c3fdae366daa8849b5e2a3f58f98878c Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 5 May 2016 13:26:36 +0700 Subject: [PATCH 0154/1431] Remove unused code, update spec, and update changelog --- CHANGELOG | 1 + app/controllers/snippets_controller.rb | 20 +------------------- spec/routing/routing_spec.rb | 8 -------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 558897ad892..6c044192d06 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.8.0 (unreleased) + - Snippets tab under user profile. !4001 (Long Nguyen) - Remove future dates from contribution calendar graph. - Fix error when visiting commit builds page before build was updated - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 2daceed039b..f0bd842ca56 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -10,29 +10,11 @@ class SnippetsController < ApplicationController # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - skip_before_action :authenticate_user!, only: [:index, :user_index, :show, :raw] + skip_before_action :authenticate_user!, only: [:show, :raw] layout 'snippets' respond_to :html - def index - if params[:username].present? - @user = User.find_by(username: params[:username]) - - render_404 and return unless @user - - @snippets = SnippetsFinder.new.execute(current_user, { - filter: :by_user, - user: @user, - scope: params[:scope] }). - page(params[:page]) - - render 'index' - else - redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path) - end - end - def new @snippet = PersonalSnippet.new end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 1527eddfa48..9deffd0a1e3 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -27,18 +27,10 @@ end # PUT /snippets/:id(.:format) snippets#update # DELETE /snippets/:id(.:format) snippets#destroy describe SnippetsController, "routing" do - it "to #user_index" do - expect(get("/s/User")).to route_to('snippets#index', username: 'User') - end - it "to #raw" do expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1') end - it "to #index" do - expect(get("/snippets")).to route_to('snippets#index') - end - it "to #create" do expect(post("/snippets")).to route_to('snippets#create') end From 91f693c0c4f1ebb62d78e6c82b833f8a19c4dc62 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 5 May 2016 13:59:09 +0700 Subject: [PATCH 0155/1431] Update changelog, improve specs --- CHANGELOG | 2 +- app/services/issues/move_service.rb | 2 +- spec/services/issues/move_service_spec.rb | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2d3a9e051e5..27c89a7c1ec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.8.0 (unreleased) - - Assign labels and milestone to target project when moving issue. !3934 + - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - Remove future dates from contribution calendar graph. - Fix error when visiting commit builds page before build was updated - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index fe5df8f18cb..e61628086f0 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -46,7 +46,7 @@ module Issues project: @new_project, author: @old_issue.author, description: rewrite_content(@old_issue.description) } - new_params = @old_issue.serializable_hash.merge(new_params) + new_params = @old_issue.serializable_hash.symbolize_keys.merge(new_params) CreateService.new(@new_project, @current_user, new_params).execute end diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 7399003bd75..c15e26189a5 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -7,9 +7,7 @@ describe Issues::MoveService, services: true do let(:description) { 'Some issue description' } let(:old_project) { create(:project) } let(:new_project) { create(:project) } - let!(:milestone1) do - create(:milestone, project_id: old_project.id, title: 'v9.0') - end + let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') } let(:old_issue) do create(:issue, title: title, description: description, @@ -57,6 +55,7 @@ describe Issues::MoveService, services: true do it 'assigns milestone to new issue' do expect(new_issue.reload.milestone.title).to eq 'v9.0' + expect(new_issue.reload.milestone).to eq(milestone2) end it 'assign labels to new issue' do @@ -64,6 +63,11 @@ describe Issues::MoveService, services: true do expect(expected_label_titles).to include 'label1' expect(expected_label_titles).to include 'label2' expect(expected_label_titles.size).to eq 2 + + new_issue.labels.each do |label| + expect(new_project.labels).to include(label) + expect(old_project.labels).not_to include(label) + end end it 'rewrites issue title' do From ae29ec31e4f71d722e975bfce945aaed7e0d0bd1 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 5 May 2016 14:57:34 +0700 Subject: [PATCH 0156/1431] Remove unused view and update redirect when destroy snippet --- app/controllers/snippets_controller.rb | 2 +- app/views/snippets/index.html.haml | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 app/views/snippets/index.html.haml diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index f0bd842ca56..2c038bdfda5 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -43,7 +43,7 @@ class SnippetsController < ApplicationController @snippet.destroy - redirect_to snippets_path + redirect_to dashboard_snippets_path end def raw diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml deleted file mode 100644 index 7e4918a6085..00000000000 --- a/app/views/snippets/index.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -- page_title "By #{@user.name}", "Snippets" - -%ol.breadcrumb - %li - = link_to snippets_path do - Snippets - %li - = @user.name - .pull-right.hidden-xs - = link_to user_path(@user) do - #{@user.name} profile page - -= render 'snippets' From 92ed02c54896d395160e9801f286ba16eb8e3ea6 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 5 May 2016 10:09:26 +0200 Subject: [PATCH 0157/1431] Moved TeamCity changelog entry to 8.7.3 [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ecd8b7c3474..e989e622b97 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,10 +23,10 @@ v 8.8.0 (unreleased) - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - Expire repository exists? and has_visible_content? caches after a push if necessary - - Merge request widget displays TeamCity build state and code coverage correctly again. v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented + - Merge request widget displays TeamCity build state and code coverage correctly again. v 8.7.2 - The "New Branch" button is now loaded asynchronously From 6fbf6b2936e8fa4aecae1bb798d38ec82d2989d2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 3 May 2016 02:45:46 -0300 Subject: [PATCH 0158/1431] Fix the line code when importing PR review comments from GitHub Pull Request Review Comments are comments on a portion of the unified diff. --- lib/gitlab/github_import/comment_formatter.rb | 21 ++++++++++--- .../github_import/comment_formatter_spec.rb | 30 ++++++++----------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb index 7d58e53991a..7d679eaec6a 100644 --- a/lib/gitlab/github_import/comment_formatter.rb +++ b/lib/gitlab/github_import/comment_formatter.rb @@ -28,13 +28,26 @@ module Gitlab end def line_code - if on_diff? - Gitlab::Diff::LineCode.generate(raw_data.path, raw_data.position, 0) - end + return unless on_diff? + + parsed_lines = Gitlab::Diff::Parser.new.parse(diff_hunk.lines) + generate_line_code(parsed_lines.to_a.last) + end + + def generate_line_code(line) + Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) end def on_diff? - raw_data.path && raw_data.position + diff_hunk.present? + end + + def diff_hunk + raw_data.diff_hunk + end + + def file_path + raw_data.path end def note diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index a324a82e69f..55e86d4ceac 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -2,23 +2,25 @@ require 'spec_helper' describe Gitlab::GithubImport::CommentFormatter, lib: true do let(:project) { create(:project) } - let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') } + let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') } let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') } - let(:base_data) do + let(:base) do { body: "I'm having a problem with this.", user: octocat, + commit_id: nil, + diff_hunk: nil, created_at: created_at, updated_at: updated_at } end - subject(:comment) { described_class.new(project, raw_data)} + subject(:comment) { described_class.new(project, raw)} describe '#attributes' do context 'when do not reference a portion of the diff' do - let(:raw_data) { OpenStruct.new(base_data) } + let(:raw) { double(base) } it 'returns formatted attributes' do expected = { @@ -36,24 +38,23 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do end context 'when on a portion of the diff' do - let(:diff_data) do + let(:diff) do { body: 'Great stuff', commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e', - diff_hunk: '@@ -16,33 +16,40 @@ public class Connection : IConnection...', - path: 'file1.txt', - position: 1 + diff_hunk: "@@ -1,5 +1,9 @@\n class User\n def name\n- 'John Doe'\n+ 'Jane Doe'", + path: 'file1.txt' } end - let(:raw_data) { OpenStruct.new(base_data.merge(diff_data)) } + let(:raw) { double(base.merge(diff)) } it 'returns formatted attributes' do expected = { project: project, note: "*Created by: octocat*\n\nGreat stuff", commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e', - line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_0_1', + line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_4_3', author_id: project.creator_id, created_at: created_at, updated_at: updated_at @@ -64,15 +65,10 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do end context 'when author is a GitLab user' do - let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) } + let(:raw) { double(base.merge(user: octocat)) } - it 'returns project#creator_id as author_id when is not a GitLab user' do - expect(comment.attributes.fetch(:author_id)).to eq project.creator_id - end - - it 'returns GitLab user id as author_id when is a GitLab user' do + it 'returns GitLab user id as author_id' do gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github') - expect(comment.attributes.fetch(:author_id)).to eq gl_user.id end end From 3bf43e7c2710f150720392e28757862bc4e021b3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 5 May 2016 10:47:08 -0300 Subject: [PATCH 0159/1431] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 0020e580b86..bcb3ecec26c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.8.0 (unreleased) v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented - Merge request widget displays TeamCity build state and code coverage correctly again. + - Fix the line code when importing PR review comments from GitHub. !4010 v 8.7.2 - The "New Branch" button is now loaded asynchronously From c24958365fa739357e7a090e10c233860008dcd3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 5 May 2016 09:00:29 -0500 Subject: [PATCH 0160/1431] Remove mobile styles; horizontally scroll layout nav links --- app/assets/stylesheets/framework/nav.scss | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index bf09b6dadc6..7c18e93a261 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -222,29 +222,13 @@ .dropdown { margin-left: 7px; } - - @media (max-width: $screen-md-max) { - float: none; - margin-bottom: 10px; - - .btn { - width: 20%; - } - } - - @media (max-width: $screen-xs-max) { - text-align: center; - - .rss-btn, - .dropdown-new { - display: inline-block; - width: 48%; - } - } } .nav-links { border-bottom: none; + height: 51px; + white-space: nowrap; + overflow-x: auto; li { From 4bef939e27d7a6b2df9984f445485207f3dacd43 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 May 2016 16:31:19 +0200 Subject: [PATCH 0161/1431] Remove group rss icon since it depends on context and duplciates on other pages Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/nav/_group.html.haml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index b4b528c614f..5c11d3c36c6 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,8 +1,5 @@ - if current_user .controls - - if current_path?('groups#show') - = link_to icon('rss'), group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn btn-gray rss-btn' - %span.dropdown.group-settings-dropdown %a.dropdown-new.btn.btn-gray#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} = icon('cog') From 5a8b8ba6c76a74dcad401a627b271b10bb7a1088 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 5 May 2016 16:37:41 +0200 Subject: [PATCH 0162/1431] Fix group settings dropdown for group members and visitors Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/nav/_group.html.haml | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 5c11d3c36c6..dff188e0b15 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,19 +1,19 @@ - if current_user - .controls - %span.dropdown.group-settings-dropdown - %a.dropdown-new.btn.btn-gray#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} - = icon('cog') - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group), title: 'Projects' do - Projects - %li.divider - - if can?(current_user, :admin_group, @group) - %li - = link_to edit_group_path(@group) do - Edit Group - - if access = @group.users.find_by(id: current_user.id) + - if access = @group.users.find_by(id: current_user.id) + .controls + %span.dropdown.group-settings-dropdown + %a.dropdown-new.btn.btn-gray#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + - if can?(current_user, :admin_group, @group) + = nav_link(path: 'groups#projects') do + = link_to projects_group_path(@group), title: 'Projects' do + Projects + %li.divider + %li + = link_to edit_group_path(@group) do + Edit Group %li = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do From 280ea4f600f9084d16d84fdaf57bf54830787fe3 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 5 May 2016 09:17:46 -0700 Subject: [PATCH 0163/1431] Upgrade Sidekiq to 4.1.2 --- CHANGELOG | 1 + Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bcb3ecec26c..8bc554efbe8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.8.0 (unreleased) - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Log to application.log when an admin starts and stops impersonating a user - Make build status canceled if any of the jobs was canceled and none failed + - Upgrade Sidekiq to 4.1.2 - Sanitize repo paths in new project error message - Bump mail_room to 0.7.0 to fix stuck IDLE connections - Remove future dates from contribution calendar graph. diff --git a/Gemfile.lock b/Gemfile.lock index 41d2922f63e..a8f7bfe43ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,7 +134,7 @@ GEM execjs coffee-script-source (1.10.0) colorize (0.7.7) - concurrent-ruby (1.0.1) + concurrent-ruby (1.0.2) connection_pool (2.2.0) coveralls (0.8.13) json (~> 1.8) @@ -740,7 +740,7 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.1.1) + sidekiq (4.1.2) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) redis (~> 3.2, >= 3.2.1) From e449a6c05c734ff9e38e13013591d07d03c0fff6 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 4 May 2016 13:14:11 +0200 Subject: [PATCH 0164/1431] Added documentation on how to instrument methods [ci skip] --- doc/development/instrumentation.md | 129 +++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index c1cf2e77c26..9168c70945a 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -1,12 +1,125 @@ # Instrumenting Ruby Code -GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby -code. This can be used to measure the time spent in a specific part of a larger -chunk of code. The resulting data is stored as a field in the transaction that -executed the block. +GitLab Performance Monitoring allows instrumenting of both methods and custom +blocks of Ruby code. Method instrumentation is the primary form of +instrumentation with block-based instrumentation only being used when we want to +drill down to specific regions of code within a method. -To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure` -and give it a name: +## Instrumenting Methods + +Instrumenting methods is done by using the `Gitlab::Metrics::Instrumentation` +module. This module offers a few different methods that can be used to +instrument code: + +* `instrument_method`: instruments a single class method. +* `instrument_instance_method`: instruments a single instance method. +* `instrument_class_hierarchy`: given a Class this method will recursively + instrument all sub-classes (both class and instance methods). +* `instrument_methods`: instruments all public class methods of a Module. +* `instrument_instance_methods`: instruments all public instance methods of a + Module. + +To remove the need for typing the full `Gitlab::Metrics::Instrumentation` +namespace you can use the `configure` class method. This method simply yields +the supplied block while passing `Gitlab::Metrics::Instrumentation` as its +argument. An example: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_method(Foo, :bar) + conf.instrument_method(Foo, :baz) +end +``` + +Using this method is in general preferred over directly calling the various +instrumentation methods. + +Method instrumentation should be added in the initializer +`config/initializers/metrics.rb`. + +### Examples + +Instrumenting a single method: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_method(User, :find_by) +end +``` + +Instrumenting an entire class hierarchy: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_class_hierarchy(ActiveRecord::Base) +end +``` + +Instrumenting all public class methods: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_methods(User) +end +``` + +### Checking Instrumented Methods + +The easiest way to check if a method has been instrumented is to check its +source location. For example: + +``` +method = Rugged::TagCollection.instance_method(:[]) + +method.source_location +``` + +If the source location points to `lib/gitlab/metrics/instrumentation.rb` you +know the method has been instrumented. + +If you're using Pry you can use the `$` command to display the source code of a +method (along with its source location), this is easier than running the above +Ruby code. In case of the above snippet you'd run the following: + +``` +$ Rugged::TagCollection#[] +``` + +This will print out something along the lines of: + +``` +From: /path/to/your/gitlab/lib/gitlab/metrics/instrumentation.rb @ line 148: +Owner: # +Visibility: public +Number of lines: 21 + +def #{name}(#{args_signature}) + trans = Gitlab::Metrics::Instrumentation.transaction + + if trans + start = Time.now + retval = super + duration = (Time.now - start) * 1000.0 + + if duration >= Gitlab::Metrics.method_call_threshold + trans.increment(:method_duration, duration) + + trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, + { duration: duration }, + method: #{label.inspect}) + end + + retval + else + super + end +end +``` + +## Instrumenting Ruby Blocks + +Measuring blocks of Ruby code is done by calling `Gitlab::Metrics.measure` and +passing it a block. For example: ```ruby Gitlab::Metrics.measure(:foo) do @@ -14,6 +127,10 @@ Gitlab::Metrics.measure(:foo) do end ``` +The block is executed and the execution time is stored as a set of fields in the +currently running transaction. If no transaction is present the block is yielded +without measuring anything. + 3 values are measured for a block: 1. The real time elapsed, stored in NAME_real_time. From 8159555491d0f1f9524ee818236b4bc552012bfa Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 5 May 2016 19:31:10 +0200 Subject: [PATCH 0165/1431] Added CHANGELOG entry for merge request !3931 [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8bc554efbe8..d8dfaaf7d0b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented - Merge request widget displays TeamCity build state and code coverage correctly again. - Fix the line code when importing PR review comments from GitHub. !4010 + - Wikis are now initialized on legacy projects when checking repositories v 8.7.2 - The "New Branch" button is now loaded asynchronously From d04146b0e0450f6363193fb703d90321df9920e5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 5 May 2016 19:33:05 +0200 Subject: [PATCH 0166/1431] Added CHANGELOG entry for merge request !3880 [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d8dfaaf7d0b..975157c683f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.7.3 - Merge request widget displays TeamCity build state and code coverage correctly again. - Fix the line code when importing PR review comments from GitHub. !4010 - Wikis are now initialized on legacy projects when checking repositories + - Label titles in filters are now escaped properly v 8.7.2 - The "New Branch" button is now loaded asynchronously From 0cf59281cda96e970a525b939d9db5f5ca7a3be4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 5 May 2016 19:40:08 +0200 Subject: [PATCH 0167/1431] Moved entry for !3880 to 8.7.2 Apparently this was already released in 8.7.2 but no changelog entry was added. [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 975157c683f..5469723beca 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,12 +31,12 @@ v 8.7.3 - Merge request widget displays TeamCity build state and code coverage correctly again. - Fix the line code when importing PR review comments from GitHub. !4010 - Wikis are now initialized on legacy projects when checking repositories - - Label titles in filters are now escaped properly v 8.7.2 - The "New Branch" button is now loaded asynchronously - Fix error 500 when trying to create a wiki page - Updated spacing between notification label and button + - Label titles in filters are now escaped properly v 8.7.1 - Throttle the update of `project.last_activity_at` to 1 minute. !3848 From 6e47a1924b71c44b989769e6af41459b0f5da6d2 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 4 May 2016 10:44:43 +0200 Subject: [PATCH 0168/1431] Updated gitlab_git to 10.1.0 --- CHANGELOG | 2 ++ Gemfile.lock | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5469723beca..769ffb2476a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,8 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.8.0 (unreleased) - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Log to application.log when an admin starts and stops impersonating a user + - Updated gitlab_git to 10.1.0 + - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists - Make build status canceled if any of the jobs was canceled and none failed - Upgrade Sidekiq to 4.1.2 - Sanitize repo paths in new project error message diff --git a/Gemfile.lock b/Gemfile.lock index a8f7bfe43ba..fe083c2b566 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -353,7 +353,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (10.0.2) + gitlab_git (10.1.0) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) From 93ce229665f875efcd7ee25b006834300c2e37be Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 29 Apr 2016 21:41:39 +0200 Subject: [PATCH 0169/1431] Use tag_exists? in GitAccess#protected_tag? This removes the need for retrieving the entire list of tags just to check if a specific one exists. --- lib/gitlab/git_access.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 3ed1eec517c..ad2da2b81cf 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -176,7 +176,7 @@ module Gitlab end def protected_tag?(tag_name) - project.repository.tag_names.include?(tag_name) + project.repository.tag_exists?(tag_name) end def user_allowed? From 003671207db67eee5f3ceb605a061346dfad945d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 4 May 2016 12:06:40 +0200 Subject: [PATCH 0170/1431] Fix passing nil to protected_tag? Previously this method would directly receive the output of tag_name(). This method could either return a String or nil. In the previous setup this would somehow magically work but because Rugged::TagCollection#[] doesn't accept nil values it started to fail. To work around this the elsif in change_access_check() assigns the result of tag_name() to a local and then _only_ calls protected_tag?() if the tag name is not nil. The extra parenthesis are put in place to ensure that things are parsed correctly, without these the code would be parsed as follows: elsif tag_ref = (tag_name(ref) && protected_tag(tag_ref)) During runtime this would basically resolve to: elsif tag_ref = (tag_name(ref) && protected_tag(nil)) This is because when you refer to the variable you're assigning _in_ the assignment Ruby returns nil instead of raising an error. --- lib/gitlab/git_access.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index ad2da2b81cf..6cb41239871 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -128,7 +128,7 @@ module Gitlab action = if project.protected_branch?(branch_name(ref)) protected_branch_action(oldrev, newrev, branch_name(ref)) - elsif protected_tag?(tag_name(ref)) + elsif (tag_ref = tag_name(ref)) && protected_tag?(tag_ref) # Prevent any changes to existing git tag unless user has permissions :admin_project else From 00b3eedf7c1a4c94bd82ef513c5aecc21d1296d0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 5 May 2016 12:55:01 -0500 Subject: [PATCH 0171/1431] Remove duplicate cover-control mobile styles --- app/assets/stylesheets/pages/profile.scss | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 7a3a2ddd363..abc5a0e9877 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -222,16 +222,4 @@ } } } - - .cover-controls { - @media (max-width: $screen-xs-max) { - position: static; - margin-bottom: 20px; - - .btn { - display: inline-block; - width: 48%; - } - } - } } From d028863eda8b97f6e4db129ef57f0d3a2130c9b3 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 4 May 2016 18:21:57 -0300 Subject: [PATCH 0172/1431] Sanitize milestones and label titles --- app/models/label.rb | 5 +++++ app/models/milestone.rb | 5 +++++ spec/lib/banzai/filter/milestone_reference_filter_spec.rb | 2 +- spec/models/label_spec.rb | 8 ++++++++ spec/models/milestone_spec.rb | 8 ++++++++ 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/models/label.rb b/app/models/label.rb index 60bdce32952..0b34911a4e9 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -117,6 +117,11 @@ class Label < ActiveRecord::Base LabelsHelper::text_color_for_bg(self.color) end + def title= value + value = Sanitize.clean(value.to_s) if value + write_attribute(:title, Sanitize.clean(value)) + end + private def label_format_reference(format = :id) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 986184dd301..ed81791c69c 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -129,6 +129,11 @@ class Milestone < ActiveRecord::Base nil end + def title= value + value = Sanitize.clean(value.to_s) if value + write_attribute(:title, value) + end + # Sorts the issues for the given IDs. # # This method runs a single SQL query using a CASE statement to update the diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index ebf3d7489b5..5beb61dac5c 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -43,7 +43,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do milestone.update_attribute(:title, %{">whateverwhatever" end it 'includes default classes' do diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 0614ca1e7c9..b61c55a3f6d 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -55,6 +55,14 @@ describe Label, models: true do end end + describe "#title" do + let(:label) { create(:label, title: "test") } + + it "sanitizes title" do + expect(label.title).to eq("test") + end + end + describe '#to_reference' do context 'using id' do it 'returns a String reference to the object' do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 72a4ea70228..e2c89a4b3e6 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -34,6 +34,14 @@ describe Milestone, models: true do let(:issue) { create(:issue) } let(:user) { create(:user) } + describe "#title" do + let(:milestone) { create(:milestone, title: "test") } + + it "sanitizes title" do + expect(milestone.title).to eq("test") + end + end + describe "unique milestone title per project" do it "shouldn't accept the same title in a project twice" do new_milestone = Milestone.new(project: milestone.project, title: milestone.title) From d6f67dba0144bdae20bd86051f3eb98ce201439e Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Thu, 5 May 2016 13:46:58 -0700 Subject: [PATCH 0173/1431] Add note to requirements doc on swap recommendation, and ensuring enough available memory. --- doc/install/requirements.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 58f409746cd..df8e8bdc476 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -64,7 +64,10 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ### Memory You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab! -With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage. +The operating system and any other running applications will also be using memory +so keep in mind that you need at least 2GB available before running GitLab. With +less memory GitLab will give strange errors during the reconfigure run and 500 +errors during usage. - 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice. - 1GB RAM + 1GB swap supports up to 100 users but it will be very slow @@ -77,6 +80,10 @@ With less memory GitLab will give strange errors during the reconfigure run and - 128GB RAM supports up to 32,000 users - More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/) +We recommend having at least 1GB of swap on your server, even if you currently have +enough available RAM. Having swap will help reduce the chance of errors occuring +if your available memory changes. + Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those. ## Gitlab Runner From df9a7c6b4acf0a662366b2e31d897cda370c2216 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 5 May 2016 17:22:18 -0400 Subject: [PATCH 0174/1431] Add a note to testing docs about the `:each` argument to RSpec hooks [ci skip] --- doc/development/testing.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/development/testing.md b/doc/development/testing.md index 672e3fb4649..33eed29ba5c 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -64,6 +64,7 @@ the command line via `bundle exec teaspoon`, or via a web browser at methods. - Use `context` to test branching logic. - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). +- Don't supply the `:each` argument to hooks since it's the default. - Prefer `not_to` to `to_not`. - Try to match the ordering of tests to the ordering within the class. - Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines From 80b30500e44b621c0ecd3c062349b34ddbb0a0fb Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Fri, 6 May 2016 00:42:13 +0000 Subject: [PATCH 0175/1431] blost -> blog --- doc/ci/examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index cc059dc4376..3a152c9116a 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -10,6 +10,6 @@ ## Outside the documentation -- [Blost post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) +- [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) From 47da013cf83b4a42fc9f4b049d8ba41dc3f325d2 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 4 May 2016 19:42:10 +0200 Subject: [PATCH 0176/1431] Annotate the models --- app/models/appearance.rb | 13 ++++ app/models/application_setting.rb | 16 ++-- app/models/audit_event.rb | 4 +- app/models/broadcast_message.rb | 4 +- app/models/ci/build.rb | 14 ++-- app/models/ci/commit.rb | 10 ++- app/models/ci/runner.rb | 14 ++-- app/models/ci/trigger.rb | 2 +- app/models/ci/variable.rb | 6 +- app/models/commit_status.rb | 17 +++-- app/models/deploy_key.rb | 6 +- app/models/email.rb | 2 +- app/models/event.rb | 4 +- app/models/generic_commit_status.rb | 17 +++-- app/models/group.rb | 21 +++--- app/models/hooks/project_hook.rb | 2 +- app/models/hooks/service_hook.rb | 2 +- app/models/hooks/system_hook.rb | 2 +- app/models/hooks/web_hook.rb | 2 +- app/models/identity.rb | 4 +- app/models/issue.rb | 9 ++- app/models/key.rb | 6 +- app/models/label.rb | 16 ++-- app/models/label_link.rb | 2 +- app/models/lfs_object.rb | 4 +- app/models/member.rb | 8 +- app/models/members/group_member.rb | 8 +- app/models/members/project_member.rb | 8 +- app/models/merge_request.rb | 13 ++-- app/models/merge_request_diff.rb | 4 +- app/models/milestone.rb | 4 +- app/models/namespace.rb | 20 ++--- app/models/note.rb | 8 +- app/models/notification_setting.rb | 13 ++++ app/models/personal_snippet.rb | 6 +- app/models/project.rb | 75 ++++++++++--------- app/models/project_group_link.rb | 12 +++ app/models/project_import_data.rb | 9 ++- app/models/project_services/asana_service.rb | 13 ++-- .../project_services/assembla_service.rb | 13 ++-- app/models/project_services/bamboo_service.rb | 13 ++-- .../project_services/buildkite_service.rb | 13 ++-- .../project_services/builds_email_service.rb | 13 ++-- .../project_services/campfire_service.rb | 13 ++-- app/models/project_services/ci_service.rb | 13 ++-- .../custom_issue_tracker_service.rb | 13 ++-- .../project_services/drone_ci_service.rb | 13 ++-- .../emails_on_push_service.rb | 13 ++-- .../project_services/external_wiki_service.rb | 13 ++-- .../project_services/flowdock_service.rb | 13 ++-- .../project_services/gemnasium_service.rb | 13 ++-- .../project_services/gitlab_ci_service.rb | 13 ++-- .../gitlab_issue_tracker_service.rb | 13 ++-- .../project_services/hipchat_service.rb | 13 ++-- app/models/project_services/irker_service.rb | 13 ++-- .../project_services/issue_tracker_service.rb | 13 ++-- app/models/project_services/jira_service.rb | 13 ++-- .../pivotaltracker_service.rb | 13 ++-- .../project_services/pushover_service.rb | 13 ++-- .../project_services/redmine_service.rb | 13 ++-- app/models/project_services/slack_service.rb | 13 ++-- .../project_services/teamcity_service.rb | 13 ++-- app/models/project_snippet.rb | 6 +- app/models/protected_branch.rb | 2 +- app/models/release.rb | 2 +- app/models/security_event.rb | 4 +- app/models/sent_notification.rb | 8 +- app/models/service.rb | 13 ++-- app/models/snippet.rb | 6 +- app/models/spam_log.rb | 17 +++++ app/models/subscription.rb | 2 +- app/models/user.rb | 49 ++++++------ 72 files changed, 485 insertions(+), 323 deletions(-) diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 4cf8dd9a8ce..4528760fefa 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,3 +1,16 @@ +# == Schema Information +# +# Table name: appearances +# +# id :integer not null, primary key +# title :string +# description :text +# header_logo :string +# logo :string +# created_at :datetime not null +# updated_at :datetime not null +# + class Appearance < ActiveRecord::Base validates :title, presence: true validates :description, presence: true diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 36f88154232..72ec91d2909 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -10,21 +10,20 @@ # sign_in_text :text # created_at :datetime # updated_at :datetime -# home_page_url :string(255) +# home_page_url :string # default_branch_protection :integer default(2) # restricted_visibility_levels :text # version_check_enabled :boolean default(TRUE) # max_attachment_size :integer default(10), not null # default_project_visibility :integer # default_snippet_visibility :integer -# default_group_visibility :integer # restricted_signup_domains :text # user_oauth_applications :boolean default(TRUE) -# after_sign_out_path :string(255) +# after_sign_out_path :string # session_expire_delay :integer default(10080), not null # import_sources :text # help_page_text :text -# admin_notification_email :string(255) +# admin_notification_email :string # shared_runners_enabled :boolean default(TRUE), not null # max_artifacts_size :integer default(100), not null # runners_registration_token :string @@ -32,8 +31,6 @@ # two_factor_grace_period :integer default(48) # metrics_enabled :boolean default(FALSE) # metrics_host :string default("localhost") -# metrics_username :string -# metrics_password :string # metrics_pool_size :integer default(16) # metrics_timeout :integer default(10) # metrics_method_call_threshold :integer default(10) @@ -41,9 +38,16 @@ # recaptcha_site_key :string # recaptcha_private_key :string # metrics_port :integer default(8089) +# metrics_sample_interval :integer default(15) # sentry_enabled :boolean default(FALSE) # sentry_dsn :string +# akismet_enabled :boolean default(FALSE) +# akismet_api_key :string # email_author_in_body :boolean default(FALSE) +# default_group_visibility :integer +# repository_checks_enabled :boolean default(FALSE) +# metrics_packet_size :integer default(1) +# shared_runners_text :text # class ApplicationSetting < ActiveRecord::Base diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 0ed0dd98a59..44b090260e7 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -4,9 +4,9 @@ # # id :integer not null, primary key # author_id :integer not null -# type :string(255) not null +# type :string not null # entity_id :integer not null -# entity_type :string(255) not null +# entity_type :string not null # details :text # created_at :datetime # updated_at :datetime diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 8a0a8a4c2a9..075ac733bfc 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -8,8 +8,8 @@ # ends_at :datetime # created_at :datetime # updated_at :datetime -# color :string(255) -# font :string(255) +# color :string +# font :string # class BroadcastMessage < ActiveRecord::Base diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 553cd447971..4bc3a225e2c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer -# status :string(255) +# status :string # finished_at :datetime # trace :text # created_at :datetime @@ -15,19 +15,19 @@ # commit_id :integer # commands :text # job_id :integer -# name :string(255) +# name :string # deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null -# stage :string(255) +# stage :string # trigger_request_id :integer # stage_idx :integer # tag :boolean -# ref :string(255) +# ref :string # user_id :integer -# type :string(255) -# target_url :string(255) -# description :string(255) +# type :string +# target_url :string +# description :string # artifacts_file :text # gl_project_id :integer # artifacts_metadata :text diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index f2667e5476b..4ac4e0fb8b2 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -4,9 +4,9 @@ # # id :integer not null, primary key # project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) +# ref :string +# sha :string +# before_sha :string # push_data :text # created_at :datetime # updated_at :datetime @@ -14,6 +14,10 @@ # yaml_errors :text # committed_at :datetime # gl_project_id :integer +# status :string +# started_at :datetime +# finished_at :datetime +# duration :integer # module Ci diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 90349a07594..add59a08892 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -3,18 +3,18 @@ # Table name: ci_runners # # id :integer not null, primary key -# token :string(255) +# token :string # created_at :datetime # updated_at :datetime -# description :string(255) +# description :string # contacted_at :datetime # active :boolean default(TRUE), not null # is_shared :boolean default(FALSE) -# name :string(255) -# version :string(255) -# revision :string(255) -# platform :string(255) -# architecture :string(255) +# name :string +# version :string +# revision :string +# platform :string +# architecture :string # module Ci diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 2b9a457c8ab..4f3f4d79fac 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -3,7 +3,7 @@ # Table name: ci_triggers # # id :integer not null, primary key -# token :string(255) +# token :string # project_id :integer # deleted_at :datetime # created_at :datetime diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index e786bd7dd93..4229fe085a1 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -4,11 +4,11 @@ # # id :integer not null, primary key # project_id :integer -# key :string(255) +# key :string # value :text # encrypted_value :text -# encrypted_value_salt :string(255) -# encrypted_value_iv :string(255) +# encrypted_value_salt :string +# encrypted_value_iv :string # gl_project_id :integer # diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index aa56314aa16..1260c448de3 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer -# status :string(255) +# status :string # finished_at :datetime # trace :text # created_at :datetime @@ -15,21 +15,24 @@ # commit_id :integer # commands :text # job_id :integer -# name :string(255) +# name :string # deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null -# stage :string(255) +# stage :string # trigger_request_id :integer # stage_idx :integer # tag :boolean -# ref :string(255) +# ref :string # user_id :integer -# type :string(255) -# target_url :string(255) -# description :string(255) +# type :string +# target_url :string +# description :string # artifacts_file :text # gl_project_id :integer +# artifacts_metadata :text +# erased_by_id :integer +# erased_at :datetime # class CommitStatus < ActiveRecord::Base diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 9ab663c04ad..43cf625f770 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -7,9 +7,9 @@ # created_at :datetime # updated_at :datetime # key :text -# title :string(255) -# type :string(255) -# fingerprint :string(255) +# title :string +# type :string +# fingerprint :string # public :boolean default(FALSE), not null # diff --git a/app/models/email.rb b/app/models/email.rb index b323d1edd10..eae2472f337 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # user_id :integer not null -# email :string(255) not null +# email :string not null # created_at :datetime # updated_at :datetime # diff --git a/app/models/event.rb b/app/models/event.rb index 897518aadc7..25c7c3e6dc7 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -3,9 +3,9 @@ # Table name: events # # id :integer not null, primary key -# target_type :string(255) +# target_type :string # target_id :integer -# title :string(255) +# title :string # data :text # project_id :integer # created_at :datetime diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb index 97f4f03a9a5..d4afd8cbe84 100644 --- a/app/models/generic_commit_status.rb +++ b/app/models/generic_commit_status.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer -# status :string(255) +# status :string # finished_at :datetime # trace :text # created_at :datetime @@ -15,21 +15,24 @@ # commit_id :integer # commands :text # job_id :integer -# name :string(255) +# name :string # deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null -# stage :string(255) +# stage :string # trigger_request_id :integer # stage_idx :integer # tag :boolean -# ref :string(255) +# ref :string # user_id :integer -# type :string(255) -# target_url :string(255) -# description :string(255) +# type :string +# target_url :string +# description :string # artifacts_file :text # gl_project_id :integer +# artifacts_metadata :text +# erased_by_id :integer +# erased_at :datetime # class GenericCommitStatus < CommitStatus diff --git a/app/models/group.rb b/app/models/group.rb index 1f8432e3320..cff76877958 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -2,16 +2,17 @@ # # Table name: namespaces # -# id :integer not null, primary key -# name :string(255) not null -# path :string(255) not null -# owner_id :integer -# visibility_level :integer default(20), not null -# created_at :datetime -# updated_at :datetime -# type :string(255) -# description :string(255) default(""), not null -# avatar :string(255) +# id :integer not null, primary key +# name :string not null +# path :string not null +# owner_id :integer +# created_at :datetime +# updated_at :datetime +# type :string +# description :string default(""), not null +# avatar :string +# share_with_group_lock :boolean default(FALSE) +# visibility_level :integer default(20), not null # require 'carrierwave/orm/activerecord' diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index d149511b868..64f570b12ae 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -16,7 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null -# token :string +# wiki_page_events :boolean default(FALSE), not null # class ProjectHook < WebHook diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb index f45145eeb3a..23f6468ee52 100644 --- a/app/models/hooks/service_hook.rb +++ b/app/models/hooks/service_hook.rb @@ -16,7 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null -# token :string +# wiki_page_events :boolean default(FALSE), not null # class ServiceHook < WebHook diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index 012cc8ec005..5e9046f3623 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -16,7 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null -# token :string +# wiki_page_events :boolean default(FALSE), not null # class SystemHook < WebHook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 1e3b4815596..a8f17ac16a6 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -16,7 +16,7 @@ # note_events :boolean default(FALSE), not null # enable_ssl_verification :boolean default(TRUE) # build_events :boolean default(FALSE), not null -# token :string +# wiki_page_events :boolean default(FALSE), not null # class WebHook < ActiveRecord::Base diff --git a/app/models/identity.rb b/app/models/identity.rb index e1915b079d4..ef4d5f99091 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -3,8 +3,8 @@ # Table name: identities # # id :integer not null, primary key -# extern_uid :string(255) -# provider :string(255) +# extern_uid :string +# provider :string # user_id :integer # created_at :datetime # updated_at :datetime diff --git a/app/models/issue.rb b/app/models/issue.rb index ea1bfb776ee..abaa509707c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -3,20 +3,23 @@ # Table name: issues # # id :integer not null, primary key -# title :string(255) +# title :string # assignee_id :integer # author_id :integer # project_id :integer # created_at :datetime # updated_at :datetime # position :integer default(0) -# branch_name :string(255) +# branch_name :string # description :text # milestone_id :integer -# state :string(255) +# state :string # iid :integer # updated_by_id :integer # moved_to_id :integer +# confidential :boolean default(FALSE) +# deleted_at :datetime +# due_date :date # require 'carrierwave/orm/activerecord' diff --git a/app/models/key.rb b/app/models/key.rb index 0282ad18139..b2b57849f8a 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -7,9 +7,9 @@ # created_at :datetime # updated_at :datetime # key :text -# title :string(255) -# type :string(255) -# fingerprint :string(255) +# title :string +# type :string +# fingerprint :string # public :boolean default(FALSE), not null # diff --git a/app/models/label.rb b/app/models/label.rb index 60bdce32952..9a22398d952 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -2,14 +2,14 @@ # # Table name: labels # -# id :integer not null, primary key -# title :string(255) -# color :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# template :boolean default(FALSE) -# description :string(255) +# id :integer not null, primary key +# title :string +# color :string +# project_id :integer +# created_at :datetime +# updated_at :datetime +# template :boolean default(FALSE) +# description :string # class Label < ActiveRecord::Base diff --git a/app/models/label_link.rb b/app/models/label_link.rb index b94c9c777af..7b8e872b6dd 100644 --- a/app/models/label_link.rb +++ b/app/models/label_link.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # label_id :integer # target_id :integer -# target_type :string(255) +# target_type :string # created_at :datetime # updated_at :datetime # diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 86b1b7e2f99..927e764af92 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -3,11 +3,11 @@ # Table name: lfs_objects # # id :integer not null, primary key -# oid :string(255) not null +# oid :string not null # size :integer not null # created_at :datetime # updated_at :datetime -# file :string(255) +# file :string # class LfsObject < ActiveRecord::Base diff --git a/app/models/member.rb b/app/models/member.rb index 60efafef211..cca82da89f1 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -5,15 +5,15 @@ # id :integer not null, primary key # access_level :integer not null # source_id :integer not null -# source_type :string(255) not null +# source_type :string not null # user_id :integer # notification_level :integer not null -# type :string(255) +# type :string # created_at :datetime # updated_at :datetime # created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) +# invite_email :string +# invite_token :string # invite_accepted_at :datetime # diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 9fb474a1a93..a48c1943e6f 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -5,15 +5,15 @@ # id :integer not null, primary key # access_level :integer not null # source_id :integer not null -# source_type :string(255) not null +# source_type :string not null # user_id :integer # notification_level :integer not null -# type :string(255) +# type :string # created_at :datetime # updated_at :datetime # created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) +# invite_email :string +# invite_token :string # invite_accepted_at :datetime # diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 07ddb02ae9d..143350a0b55 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -5,15 +5,15 @@ # id :integer not null, primary key # access_level :integer not null # source_id :integer not null -# source_type :string(255) not null +# source_type :string not null # user_id :integer # notification_level :integer not null -# type :string(255) +# type :string # created_at :datetime # updated_at :datetime # created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) +# invite_email :string +# invite_token :string # invite_accepted_at :datetime # diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d00919c3b0c..4175e1e5fba 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -3,28 +3,29 @@ # Table name: merge_requests # # id :integer not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null +# target_branch :string not null +# source_branch :string not null # source_project_id :integer not null # author_id :integer # assignee_id :integer -# title :string(255) +# title :string # created_at :datetime # updated_at :datetime # milestone_id :integer -# state :string(255) -# merge_status :string(255) +# state :string +# merge_status :string # target_project_id :integer not null # iid :integer # description :text # position :integer default(0) # locked_at :datetime # updated_by_id :integer -# merge_error :string(255) +# merge_error :string # merge_params :text # merge_when_build_succeeds :boolean default(FALSE), not null # merge_user_id :integer # merge_commit_sha :string +# deleted_at :datetime # class MergeRequest < ActiveRecord::Base diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0580cafdd1b..8951e92a0b8 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -3,12 +3,14 @@ # Table name: merge_request_diffs # # id :integer not null, primary key -# state :string(255) +# state :string # st_commits :text # st_diffs :text # merge_request_id :integer not null # created_at :datetime # updated_at :datetime +# base_commit_sha :string +# real_size :string # class MergeRequestDiff < ActiveRecord::Base diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 986184dd301..5ee8a965ad8 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -3,13 +3,13 @@ # Table name: milestones # # id :integer not null, primary key -# title :string(255) not null +# title :string not null # project_id :integer not null # description :text # due_date :date # created_at :datetime # updated_at :datetime -# state :string(255) +# state :string # iid :integer # diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 55842df1e2d..741e912171d 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -2,15 +2,17 @@ # # Table name: namespaces # -# id :integer not null, primary key -# name :string(255) not null -# path :string(255) not null -# owner_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) -# description :string(255) default(""), not null -# avatar :string(255) +# id :integer not null, primary key +# name :string not null +# path :string not null +# owner_id :integer +# created_at :datetime +# updated_at :datetime +# type :string +# description :string default(""), not null +# avatar :string +# share_with_group_lock :boolean default(FALSE) +# visibility_level :integer default(20), not null # class Namespace < ActiveRecord::Base diff --git a/app/models/note.rb b/app/models/note.rb index 71b4293d57a..deee2b9e885 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -4,14 +4,14 @@ # # id :integer not null, primary key # note :text -# noteable_type :string(255) +# noteable_type :string # author_id :integer # created_at :datetime # updated_at :datetime # project_id :integer -# attachment :string(255) -# line_code :string(255) -# commit_id :string(255) +# attachment :string +# line_code :string +# commit_id :string # noteable_id :integer # system :boolean default(FALSE), not null # st_diff :text diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 5001738f411..846773752a6 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,3 +1,16 @@ +# == Schema Information +# +# Table name: notification_settings +# +# id :integer not null, primary key +# user_id :integer not null +# source_id :integer not null +# source_type :string not null +# level :integer default(0), not null +# created_at :datetime not null +# updated_at :datetime not null +# + class NotificationSetting < ActiveRecord::Base enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 } diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index 452f3913eef..1d5f4c50254 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -3,14 +3,14 @@ # Table name: snippets # # id :integer not null, primary key -# title :string(255) +# title :string # content :text # author_id :integer not null # project_id :integer # created_at :datetime # updated_at :datetime -# file_name :string(255) -# type :string(255) +# file_name :string +# type :string # visibility_level :integer default(0), not null # diff --git a/app/models/project.rb b/app/models/project.rb index af62e8ecd90..9403acf7759 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,41 +2,46 @@ # # Table name: projects # -# id :integer not null, primary key -# name :string(255) -# path :string(255) -# description :text -# created_at :datetime -# updated_at :datetime -# creator_id :integer -# issues_enabled :boolean default(TRUE), not null -# wall_enabled :boolean default(TRUE), not null -# merge_requests_enabled :boolean default(TRUE), not null -# wiki_enabled :boolean default(TRUE), not null -# namespace_id :integer -# issues_tracker :string(255) default("gitlab"), not null -# issues_tracker_id :string(255) -# snippets_enabled :boolean default(TRUE), not null -# last_activity_at :datetime -# import_url :string(255) -# visibility_level :integer default(0), not null -# archived :boolean default(FALSE), not null -# avatar :string(255) -# import_status :string(255) -# repository_size :float default(0.0) -# star_count :integer default(0), not null -# import_type :string(255) -# import_source :string(255) -# commit_count :integer default(0) -# import_error :text -# ci_id :integer -# builds_enabled :boolean default(TRUE), not null -# shared_runners_enabled :boolean default(TRUE), not null -# runners_token :string -# build_coverage_regex :string -# build_allow_git_fetch :boolean default(TRUE), not null -# build_timeout :integer default(3600), not null -# pending_delete :boolean +# id :integer not null, primary key +# name :string +# path :string +# description :text +# created_at :datetime +# updated_at :datetime +# creator_id :integer +# issues_enabled :boolean default(TRUE), not null +# wall_enabled :boolean default(TRUE), not null +# merge_requests_enabled :boolean default(TRUE), not null +# wiki_enabled :boolean default(TRUE), not null +# namespace_id :integer +# issues_tracker :string default("gitlab"), not null +# issues_tracker_id :string +# snippets_enabled :boolean default(TRUE), not null +# last_activity_at :datetime +# import_url :string +# visibility_level :integer default(0), not null +# archived :boolean default(FALSE), not null +# avatar :string +# import_status :string +# repository_size :float default(0.0) +# star_count :integer default(0), not null +# import_type :string +# import_source :string +# commit_count :integer default(0) +# import_error :text +# ci_id :integer +# builds_enabled :boolean default(TRUE), not null +# shared_runners_enabled :boolean default(TRUE), not null +# runners_token :string +# build_coverage_regex :string +# build_allow_git_fetch :boolean default(TRUE), not null +# build_timeout :integer default(3600), not null +# pending_delete :boolean default(FALSE) +# public_builds :boolean default(TRUE), not null +# main_language :string +# pushes_since_gc :integer default(0) +# last_repository_check_failed :boolean +# last_repository_check_at :datetime # require 'carrierwave/orm/activerecord' diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index e52a6bd7c84..66f5a609bf5 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: project_group_links +# +# id :integer not null, primary key +# project_id :integer not null +# group_id :integer not null +# created_at :datetime +# updated_at :datetime +# group_access :integer default(30), not null +# + class ProjectGroupLink < ActiveRecord::Base GUEST = 10 REPORTER = 20 diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 2c0ae312f1b..3f0080205dd 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -2,9 +2,12 @@ # # Table name: project_import_data # -# id :integer not null, primary key -# project_id :integer -# data :text +# id :integer not null, primary key +# project_id :integer +# data :text +# encrypted_credentials :text +# encrypted_credentials_iv :text +# encrypted_credentials_salt :text # require 'carrierwave/orm/activerecord' diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 792ad804575..368485a060a 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require 'asana' diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 29d841faed8..ffb7455b014 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class AssemblaService < Service diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 060062aaf7a..c36ee95e378 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class BambooService < CiService diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 861cc974ec4..f9f4897a065 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require "addressable/uri" diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index 6ab6d7417b7..20cdfcaffb2 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class BuildsEmailService < Service diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 6e8f0842524..28c969fe57f 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class CampfireService < Service diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index d9f0849d147..9bc8f982da6 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # # Base class for CI services diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 88a3e9218cb..4d1319eb6f8 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class CustomIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index b4724bb647e..d8e00e018cc 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class DroneCiService < CiService diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index b831577cd97..2dbd29062df 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class EmailsOnPushService < Service diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index b402b68665a..5469049bb5e 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class ExternalWikiService < Service diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 8605ce66e48..3dc1e0fbe8b 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 61babe9cfe5..b4c311cf664 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require "gemnasium/gitlab_service" diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 33f0d7ea01a..a92f7226083 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # # TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index eaa5654b9c6..1adaeeb3b2b 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class GitlabIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 064ef8e8674..f9ddf588722 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class HipchatService < Service diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 04c714bfaad..b9a592d7096 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # require 'uri' diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index c5501e06411..98a3a7c6b86 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class IssueTrackerService < Service diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index b4418ba9284..ba68658f0bd 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class JiraService < IssueTrackerService diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index c9a890c7e3f..acaa0c39365 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class PivotaltrackerService < Service diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index e76d9eca2ab..a640c8cb440 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class PushoverService < Service diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index de974354c77..e2137e92c62 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class RedmineService < IssueTrackerService diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 7092b757549..83ffa53a407 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class SlackService < Service diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 8dceee5e2c5..4015da31509 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # class TeamcityService < CiService diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index d48f0546159..b4b2807eba4 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -3,14 +3,14 @@ # Table name: snippets # # id :integer not null, primary key -# title :string(255) +# title :string # content :text # author_id :integer not null # project_id :integer # created_at :datetime # updated_at :datetime -# file_name :string(255) -# type :string(255) +# file_name :string +# type :string # visibility_level :integer default(0), not null # diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 8ebd790a89e..3d2052c892c 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -4,7 +4,7 @@ # # id :integer not null, primary key # project_id :integer not null -# name :string(255) not null +# name :string not null # created_at :datetime # updated_at :datetime # developers_can_push :boolean default(FALSE), not null diff --git a/app/models/release.rb b/app/models/release.rb index 89f70278af5..dc700d1ea5a 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -3,7 +3,7 @@ # Table name: releases # # id :integer not null, primary key -# tag :string(255) +# tag :string # description :text # project_id :integer # created_at :datetime diff --git a/app/models/security_event.rb b/app/models/security_event.rb index 68c00adad59..0bee03974f1 100644 --- a/app/models/security_event.rb +++ b/app/models/security_event.rb @@ -4,9 +4,9 @@ # # id :integer not null, primary key # author_id :integer not null -# type :string(255) not null +# type :string not null # entity_id :integer not null -# entity_type :string(255) not null +# entity_type :string not null # details :text # created_at :datetime # updated_at :datetime diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 77115597d71..99279a2e083 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -5,11 +5,11 @@ # id :integer not null, primary key # project_id :integer # noteable_id :integer -# noteable_type :string(255) +# noteable_type :string # recipient_id :integer -# commit_id :string(255) -# line_code :string(255) -# reply_key :string(255) not null +# commit_id :string +# reply_key :string not null +# line_code :string # class SentNotification < ActiveRecord::Base diff --git a/app/models/service.rb b/app/models/service.rb index 2645b8321d7..bf16a545307 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -3,12 +3,12 @@ # Table name: services # # id :integer not null, primary key -# type :string(255) -# title :string(255) +# type :string +# title :string # project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean not null # properties :text # template :boolean default(FALSE) # push_events :boolean default(TRUE) @@ -17,6 +17,9 @@ # tag_push_events :boolean default(TRUE) # note_events :boolean default(TRUE), not null # build_events :boolean default(FALSE), not null +# category :string default("common"), not null +# default :boolean default(FALSE) +# wiki_page_events :boolean default(TRUE) # # To add new service you should build a class inherited from Service diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 0fd08061925..2f905a90942 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -3,14 +3,14 @@ # Table name: snippets # # id :integer not null, primary key -# title :string(255) +# title :string # content :text # author_id :integer not null # project_id :integer # created_at :datetime # updated_at :datetime -# file_name :string(255) -# type :string(255) +# file_name :string +# type :string # visibility_level :integer default(0), not null # diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb index 12df68ef83b..f49eb7d88e2 100644 --- a/app/models/spam_log.rb +++ b/app/models/spam_log.rb @@ -1,3 +1,20 @@ +# == Schema Information +# +# Table name: spam_logs +# +# id :integer not null, primary key +# user_id :integer +# source_ip :string +# user_agent :string +# via_api :boolean +# project_id :integer +# noteable_type :string +# title :string +# description :text +# created_at :datetime not null +# updated_at :datetime not null +# + class SpamLog < ActiveRecord::Base belongs_to :user diff --git a/app/models/subscription.rb b/app/models/subscription.rb index dd800ce110f..242faa7d32e 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -5,7 +5,7 @@ # id :integer not null, primary key # user_id :integer # subscribable_id :integer -# subscribable_type :string(255) +# subscribable_type :string # subscribed :boolean # created_at :datetime # updated_at :datetime diff --git a/app/models/user.rb b/app/models/user.rb index ab48f8f1960..959b1f93758 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,55 +3,55 @@ # Table name: users # # id :integer not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(255) default(""), not null -# reset_password_token :string(255) +# email :string default(""), not null +# encrypted_password :string default(""), not null +# reset_password_token :string # reset_password_sent_at :datetime # remember_created_at :datetime # sign_in_count :integer default(0) # current_sign_in_at :datetime # last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) +# current_sign_in_ip :string +# last_sign_in_ip :string # created_at :datetime # updated_at :datetime -# name :string(255) +# name :string # admin :boolean default(FALSE), not null # projects_limit :integer default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) +# skype :string default(""), not null +# linkedin :string default(""), not null +# twitter :string default(""), not null +# authentication_token :string # theme_id :integer default(1), not null -# bio :string(255) +# bio :string # failed_attempts :integer default(0) # locked_at :datetime -# username :string(255) +# username :string # can_create_group :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null -# state :string(255) +# state :string # color_scheme_id :integer default(1), not null # notification_level :integer default(1), not null # password_expires_at :datetime # created_by_id :integer # last_credential_check_at :datetime -# avatar :string(255) -# confirmation_token :string(255) +# avatar :string +# confirmation_token :string # confirmed_at :datetime # confirmation_sent_at :datetime -# unconfirmed_email :string(255) +# unconfirmed_email :string # hide_no_ssh_key :boolean default(FALSE) -# website_url :string(255) default(""), not null -# notification_email :string(255) +# website_url :string default(""), not null +# notification_email :string # hide_no_password :boolean default(FALSE) # password_automatically_set :boolean default(FALSE) -# location :string(255) -# encrypted_otp_secret :string(255) -# encrypted_otp_secret_iv :string(255) -# encrypted_otp_secret_salt :string(255) +# location :string +# encrypted_otp_secret :string +# encrypted_otp_secret_iv :string +# encrypted_otp_secret_salt :string # otp_required_for_login :boolean default(FALSE), not null # otp_backup_codes :text -# public_email :string(255) default(""), not null +# public_email :string default(""), not null # dashboard :integer default(0) # project_view :integer default(0) # consumed_timestep :integer @@ -59,7 +59,8 @@ # hide_project_limit :boolean default(FALSE) # unlock_token :string # otp_grace_period_started_at :datetime -# external :boolean default(FALSE) +# ldap_email :boolean default(FALSE), not null +# external :boolean default(FALSE) # require 'carrierwave/orm/activerecord' From 8278b763d96ef10c6494409b18b7eb541463af29 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 6 May 2016 08:27:40 +0200 Subject: [PATCH 0177/1431] Auto annotate models on migration [ci skip] --- lib/tasks/auto_annotate_models.rake | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/tasks/auto_annotate_models.rake diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake new file mode 100644 index 00000000000..16bad4bd2bd --- /dev/null +++ b/lib/tasks/auto_annotate_models.rake @@ -0,0 +1,44 @@ +if Rails.env.development? + task :set_annotation_options do + # You can override any of these by setting an environment variable of the + # same name. + Annotate.set_defaults( + 'routes' => 'false', + 'position_in_routes' => 'before', + 'position_in_class' => 'before', + 'position_in_test' => 'before', + 'position_in_fixture' => 'before', + 'position_in_factory' => 'before', + 'position_in_serializer' => 'before', + 'show_foreign_keys' => 'true', + 'show_indexes' => 'false', + 'simple_indexes' => 'false', + 'model_dir' => 'app/models', + 'root_dir' => '', + 'include_version' => 'false', + 'require' => '', + 'exclude_tests' => 'true', + 'exclude_fixtures' => 'true', + 'exclude_factories' => 'true', + 'exclude_serializers' => 'true', + 'exclude_scaffolds' => 'true', + 'exclude_controllers' => 'true', + 'exclude_helpers' => 'true', + 'ignore_model_sub_dir' => 'false', + 'ignore_columns' => nil, + 'ignore_unknown_models' => 'false', + 'hide_limit_column_types' => 'integer,boolean', + 'skip_on_db_migrate' => 'false', + 'format_bare' => 'true', + 'format_rdoc' => 'false', + 'format_markdown' => 'false', + 'sort' => 'false', + 'force' => 'false', + 'trace' => 'false', + 'wrapper_open' => nil, + 'wrapper_close' => nil, + ) + end + + Annotate.load_tasks +end From b8b54252fde4dc636db9d41330fa910af803284e Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Fri, 6 May 2016 07:54:00 +0000 Subject: [PATCH 0178/1431] clarify changelog entry on GitHub Enterprise import support --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 769ffb2476a..99e294c9ae1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,7 +18,7 @@ v 8.8.0 (unreleased) - Display informative message when new milestone is created - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Added button to toggle whitespaces changes on diff view - - Backport GitLab Enterprise support from EE + - Backport GitHub Enterprise import support from EE - Create tags using Rugged for performance reasons. !3745 - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) From 3bdc57f0a710b3769381ecad7ea4098223ecff56 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Sat, 16 Apr 2016 21:09:08 +0200 Subject: [PATCH 0179/1431] Create table for award emoji --- .../concerns/toggle_award_emoji.rb | 20 +++++ app/controllers/projects/issues_controller.rb | 4 +- .../projects/merge_requests_controller.rb | 6 +- app/controllers/projects/notes_controller.rb | 35 ++------ app/controllers/projects_controller.rb | 2 +- app/helpers/issues_helper.rb | 13 +-- app/models/award_emoji.rb | 35 ++++++++ app/models/concerns/awardable.rb | 81 ++++++++++++++++++ app/models/concerns/issuable.rb | 32 +------- app/models/merge_request.rb | 1 + app/models/note.rb | 53 +++--------- app/models/user.rb | 1 + app/services/notes/create_service.rb | 5 ++ app/services/notes/post_process_service.rb | 2 +- app/services/notification_service.rb | 1 - app/services/todo_service.rb | 8 ++ app/services/toggle_award_emoji_service.rb | 21 +++++ app/views/award_emoji/_awards_block.html.haml | 15 ++++ app/views/emojis/index.html.haml | 4 +- app/views/projects/issues/_issue.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 2 +- .../projects/merge_requests/_show.html.haml | 4 +- app/views/votes/_votes_block.html.haml | 8 +- config/initializers/inflections.rb | 4 + config/routes.rb | 7 +- db/migrate/20160416180807_add_award_emoji.rb | 15 ++++ ...82152_convert_award_note_to_emoji_award.rb | 17 ++++ .../20160416190505_remove_note_is_award.rb | 5 ++ db/schema.rb | 53 +++++++----- lib/api/entities.rb | 7 +- lib/award_emoji.rb | 80 ------------------ lib/gitlab/award_emoji.rb | 82 +++++++++++++++++++ spec/controllers/groups_controller_spec.rb | 12 +-- spec/factories/award_emoji.rb | 7 ++ spec/factories/notes.rb | 6 -- spec/helpers/issues_helper_spec.rb | 11 +-- spec/lib/{ => gitlab}/award_emoji_spec.rb | 6 +- spec/models/award_emoji_spec.rb | 31 +++++++ spec/models/concerns/issuable_spec.rb | 14 ---- spec/models/note_spec.rb | 39 --------- 41 files changed, 446 insertions(+), 307 deletions(-) create mode 100644 app/controllers/concerns/toggle_award_emoji.rb create mode 100644 app/models/award_emoji.rb create mode 100644 app/models/concerns/awardable.rb create mode 100644 app/services/toggle_award_emoji_service.rb create mode 100644 app/views/award_emoji/_awards_block.html.haml create mode 100644 db/migrate/20160416180807_add_award_emoji.rb create mode 100644 db/migrate/20160416182152_convert_award_note_to_emoji_award.rb create mode 100644 db/migrate/20160416190505_remove_note_is_award.rb delete mode 100644 lib/award_emoji.rb create mode 100644 lib/gitlab/award_emoji.rb create mode 100644 spec/factories/award_emoji.rb rename spec/lib/{ => gitlab}/award_emoji_spec.rb (75%) create mode 100644 spec/models/award_emoji_spec.rb diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb new file mode 100644 index 00000000000..9cd522d1c30 --- /dev/null +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -0,0 +1,20 @@ +module ToggleAwardEmoji + extend ActiveSupport::Concern + + included do + before_action :authenticate_user!, only: [:toggle_award_emoji] + end + + def toggle_award_emoji + name = params.require(:name) + awardable.toggle_award_emoji(name, current_user) + + render json: { ok: true } + end + + private + + def awardable + raise NotImplementedError + end +end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 38214f04793..86ba40facc5 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -1,6 +1,7 @@ class Projects::IssuesController < Projects::ApplicationController include ToggleSubscriptionAction include IssuableActions + include ToggleAwardEmoji before_action :module_enabled before_action :issue, @@ -61,7 +62,7 @@ class Projects::IssuesController < Projects::ApplicationController def show @note = @project.notes.new(noteable: @issue) - @notes = @issue.notes.nonawards.with_associations.fresh + @notes = @issue.notes.with_associations.fresh @noteable = @issue respond_to do |format| @@ -158,6 +159,7 @@ class Projects::IssuesController < Projects::ApplicationController end alias_method :subscribable_resource, :issue alias_method :issuable, :issue + alias_method :awardable, :issue def authorize_read_issue! return render_404 unless can?(current_user, :read_issue, @issue) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3e0cfc6aa65..9117f9242cd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController include ToggleSubscriptionAction include DiffHelper include IssuableActions + include ToggleAwardEmoji before_action :module_enabled before_action :merge_request, only: [ @@ -195,7 +196,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active? MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) - .execute(@merge_request) + .execute(@merge_request) @status = :merge_when_build_succeeds else MergeWorker.perform_async(@merge_request.id, current_user.id, params) @@ -264,6 +265,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end alias_method :subscribable_resource, :merge_request alias_method :issuable, :merge_request + alias_method :awardable, :merge_request def closes_issues @closes_issues ||= @merge_request.closes_issues @@ -299,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_show_vars # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) - @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh + @notes = @merge_request.mr_and_commit_notes.inc_author.fresh @discussions = Note.discussions_from_notes(@notes) @noteable = @merge_request diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 707a0d0e5c6..9000e0adf63 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] - before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle] + before_action :find_current_user_notes, only: [:index] def index current_fetched_at = Time.now.to_i @@ -22,8 +22,10 @@ class Projects::NotesController < Projects::ApplicationController def create @note = Notes::CreateService.new(project, current_user, note_params).execute + @note = note.is_a?(AwardEmoji) ? @note.to_note_json : note_json(@note) + respond_to do |format| - format.json { render json: note_json(@note) } + format.json { render json: @note } format.html { redirect_back_or_default } end end @@ -56,35 +58,12 @@ class Projects::NotesController < Projects::ApplicationController end end - def award_toggle - noteable = if note_params[:noteable_type] == "issue" - project.issues.find(note_params[:noteable_id]) - else - project.merge_requests.find(note_params[:noteable_id]) - end - - data = { - author: current_user, - is_award: true, - note: note_params[:note].delete(":") - } - - note = noteable.notes.find_by(data) - - if note - note.destroy - else - Notes::CreateService.new(project, current_user, note_params).execute - end - - render json: { ok: true } - end - private def note @note ||= @project.notes.find(params[:id]) end + alias_method :awardable, :note def note_to_html(note) render_to_string( @@ -137,7 +116,7 @@ class Projects::NotesController < Projects::ApplicationController id: note.id, discussion_id: note.discussion_id, html: note_to_html(note), - award: note.is_award, + award: false, note: note.note, discussion_html: note_to_discussion_html(note), discussion_with_diff_html: note_to_discussion_with_diff_html(note) @@ -145,7 +124,7 @@ class Projects::NotesController < Projects::ApplicationController else { valid: false, - award: note.is_award, + award: false, errors: note.errors } end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3768efe142a..85a987c2cb2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -145,7 +145,7 @@ class ProjectsController < Projects::ApplicationController participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id) @suggestions = { - emojis: AwardEmoji.urls, + emojis: Gitlab::AwardEmoji.urls, issues: autocomplete.issues, mergerequests: autocomplete.merge_requests, members: participants diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 4cb8adcebad..38de0b442ca 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -144,16 +144,17 @@ module IssuesHelper end end - def emoji_author_list(notes, current_user) - list = notes.map do |note| - note.author == current_user ? "me" : note.author.name - end + def award_user_list(awards, current_user) + list = + awards.map do |award| + award.user == current_user ? "me" : award.user.name + end list.join(", ") end - def note_active_class(notes, current_user) - if current_user && notes.pluck(:author_id).include?(current_user.id) + def award_active_class(awards, current_user) + if current_user && awards.find { |a| a.user_id == current_user.id } "active" else "" diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb new file mode 100644 index 00000000000..44a9b55a8a6 --- /dev/null +++ b/app/models/award_emoji.rb @@ -0,0 +1,35 @@ +class AwardEmoji < ActiveRecord::Base + DOWNVOTE_NAME = "thumbsdown".freeze + UPVOTE_NAME = "thumbsup".freeze + + include Participable + + belongs_to :awardable, polymorphic: true + belongs_to :user + + validates :awardable, :user, presence: true + validates :name, presence: true, inclusion: { in: Emoji.emojis_names } + validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] } + + participant :user + + scope :downvotes, -> { where(name: DOWNVOTE_NAME) } + scope :upvotes, -> { where(name: UPVOTE_NAME) } + + def downvote? + self.name == DOWNVOTE_NAME + end + + def upvote? + self.name == UPVOTE_NAME + end + + def to_note_json + { + valid: valid?, + award: true, + id: id, + name: name + } + end +end diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb new file mode 100644 index 00000000000..b4e3e9eb3dd --- /dev/null +++ b/app/models/concerns/awardable.rb @@ -0,0 +1,81 @@ +module Awardable + extend ActiveSupport::Concern + + included do + has_many :award_emoji, as: :awardable, dependent: :destroy + + if self < Participable + participant :award_emoji + end + end + + module ClassMethods + def order_upvotes_desc + order_votes_desc(AwardEmoji::UPVOTE_NAME) + end + + def order_downvotes_desc + order_votes_desc(AwardEmoji::DOWNVOTE_NAME) + end + + def order_votes_desc(emoji_name) + awardable_table = self.arel_table + awards_table = AwardEmoji.arel_table + + join_clause = awardable_table.join(awards_table, Arel::Nodes::OuterJoin).on( + awards_table[:awardable_id].eq(awardable_table[:id]).and( + awards_table[:awardable_type].eq(self.name).and( + awards_table[:name].eq(emoji_name) + ) + ) + ).join_sources + + joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) DESC") + end + end + + def grouped_awards(with_thumbs = true) + awards = award_emoji.group_by(&:name) + + if with_thumbs + awards[AwardEmoji::UPVOTE_NAME] ||= AwardEmoji.none + awards[AwardEmoji::DOWNVOTE_NAME] ||= AwardEmoji.none + end + + awards + end + + def downvotes + award_emoji.where(name: AwardEmoji::DOWNVOTE_NAME).count + end + + def upvotes + award_emoji.where(name: AwardEmoji::UPVOTE_NAME).count + end + + def emoji_awardable? + true + end + + def awarded_emoji?(emoji_name, current_user) + award_emoji.where(name: emoji_name, user: current_user).exists? + end + + def create_award_emoji(name, current_user) + return unless emoji_awardable? + + award_emoji.create(name: name, user: current_user) + end + + def remove_award_emoji(name, current_user) + award_emoji.where(name: name, user: current_user).destroy_all + end + + def toggle_award_emoji(emoji_name, current_user) + if awarded_emoji?(emoji_name, current_user) + remove_award_emoji(emoji_name, current_user) + else + create_award_emoji(emoji_name, current_user) + end + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index afa2ca039ae..6af76c97cd3 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -10,6 +10,7 @@ module Issuable include Mentionable include Subscribable include StripAttribute + include Awardable included do belongs_to :author, class_name: "User" @@ -99,29 +100,6 @@ module Issuable order_by(method) end end - - def order_downvotes_desc - order_votes_desc('thumbsdown') - end - - def order_upvotes_desc - order_votes_desc('thumbsup') - end - - def order_votes_desc(award_emoji_name) - issuable_table = self.arel_table - note_table = Note.arel_table - - join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on( - note_table[:noteable_id].eq(issuable_table[:id]).and( - note_table[:noteable_type].eq(self.name).and( - note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name)) - ) - ) - ).join_sources - - joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC") - end end def today? @@ -144,14 +122,6 @@ module Issuable opened? || reopened? end - def downvotes - notes.awards.where(note: "thumbsdown").count - end - - def upvotes - notes.awards.where(note: "thumbsup").count - end - def subscribed_without_subscriptions?(user) participants(user).include?(user) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e410febdfff..2cb3e8b0176 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -36,6 +36,7 @@ class MergeRequest < ActiveRecord::Base include Referable include Sortable include Taskable + include Awardable belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" diff --git a/app/models/note.rb b/app/models/note.rb index 87ced65c650..b992b2e76f0 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -16,7 +16,6 @@ # system :boolean default(FALSE), not null # st_diff :text # updated_by_id :integer -# is_award :boolean default(FALSE), not null # require 'carrierwave/orm/activerecord' @@ -43,12 +42,9 @@ class Note < ActiveRecord::Base delegate :name, to: :project, prefix: true delegate :name, :email, to: :author, prefix: true - before_validation :set_award! before_validation :clear_blank_line_code! validates :note, :project, presence: true - validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award } - validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award } validates :line_code, line_code: true, allow_blank: true # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } @@ -60,8 +56,6 @@ class Note < ActiveRecord::Base mount_uploader :attachment, AttachmentUploader # Scopes - scope :awards, ->{ where(is_award: true) } - scope :nonawards, ->{ where(is_award: false) } scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } scope :inline, ->{ where("line_code IS NOT NULL") } scope :not_inline, ->{ where(line_code: nil) } @@ -119,19 +113,6 @@ class Note < ActiveRecord::Base where(table[:note].matches(pattern)) end - - def grouped_awards - notes = {} - - awards.select(:note).distinct.map do |note| - notes[note.note] = where(note: note.note) - end - - notes["thumbsup"] ||= Note.none - notes["thumbsdown"] ||= Note.none - - notes - end end def cross_reference? @@ -347,37 +328,25 @@ class Note < ActiveRecord::Base Event.reset_event_cache_for(self) end - def downvote? - is_award && note == "thumbsdown" - end - - def upvote? - is_award && note == "thumbsup" + def system? + read_attribute(:system) end def editable? - !system? && !is_award + !system? end def cross_reference_not_visible_for?(user) cross_reference? && referenced_mentionables(user).empty? end - # Checks if note is an award added as a comment - # - # If note is an award, this method sets is_award to true - # and changes content of the note to award name. - # - # Method is executed as a before_validation callback. - # - def set_award! - return unless awards_supported? && contains_emoji_only? - - self.is_award = true - self.note = award_emoji_name + def award_emoji? + award_emoji_supported? && contains_emoji_only? end - private + def create_award_emoji + self.noteable.award_emoji(award_emoji_name, author) + end def clear_blank_line_code! self.line_code = nil if self.line_code.blank? @@ -389,8 +358,8 @@ class Note < ActiveRecord::Base diffs.find { |d| d.new_path == self.diff.new_path } end - def awards_supported? - (for_issue? || for_merge_request?) && !for_diff_line? + def award_emoji_supported? + noteable.is_a?(Awardable) && !for_diff_line? end def contains_emoji_only? @@ -399,6 +368,6 @@ class Note < ActiveRecord::Base def award_emoji_name original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] - AwardEmoji.normilize_emoji_name(original_name) + Gitlab::AwardEmoji.normilize_emoji_name(original_name) end end diff --git a/app/models/user.rb b/app/models/user.rb index 031315debd7..52f2904f450 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -144,6 +144,7 @@ class User < ActiveRecord::Base has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :todos, dependent: :destroy has_many :notification_settings, dependent: :destroy + has_many :award_emoji, as: :awardable, dependent: :destroy # # Validations diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 2bb312bb252..c5be21ba897 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -5,6 +5,11 @@ module Notes note.author = current_user note.system = false + if note.award_emoji? + return ToggleAwardEmojiService.new(project, current_user, params). + execute(note.noteable, note.note) + end + if note.save # Finish the harder work in the background NewNoteWorker.perform_in(2.seconds, note.id, params) diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb index e818f58d13c..c1bf46bdfb3 100644 --- a/app/services/notes/post_process_service.rb +++ b/app/services/notes/post_process_service.rb @@ -8,7 +8,7 @@ module Notes def execute # Skip system notes, like status changes and cross-references and awards - unless @note.system || @note.is_award + unless @note.system EventCreateService.new.leave_note(@note, @note.author) @note.create_cross_references! execute_note_hooks diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 42ec1ac9e1a..703636658b7 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -131,7 +131,6 @@ class NotificationService # ignore gitlab service messages return true if note.note.start_with?('Status changed to closed') return true if note.cross_reference? && note.system == true - return true if note.is_award target = note.noteable diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 42c5bca90fd..da1b77c0f9e 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -98,6 +98,14 @@ class TodoService handle_note(note, current_user) end + # When an emoji is awarded we should: + # + # * mark all pending todos related to the awardable for the current user as done + # + def new_award_emoji(awardable, current_user) + mark_pending_todos_as_done(awardable, current_user) + end + # When marking pending todos as done we should: # # * mark all pending todos related to the target for the current user as done diff --git a/app/services/toggle_award_emoji_service.rb b/app/services/toggle_award_emoji_service.rb new file mode 100644 index 00000000000..b77b4e79bf2 --- /dev/null +++ b/app/services/toggle_award_emoji_service.rb @@ -0,0 +1,21 @@ +require_relative 'base_service' + +class ToggleAwardEmojiService < BaseService + # For an award emoji being posted we should: + # - Mark the TODO as done for this issuable (skip on snippets) + # - Save the award emoji + def execute(awardable, emoji) + todo_service.new_award_emoji(awardable, current_user) + + # Needed if its posted as a note containing only :+1: + emoji = award_emoji_name(emoji) if emoji.start_with? ':' + awardable.toggle_award_emoji(emoji, current_user) + end + + private + + def award_emoji_name(emoji) + original_name = emoji.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] + Gitlab::AwardEmoji.normalize_emoji_name(original_name) + end +end diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml new file mode 100644 index 00000000000..63c953195fe --- /dev/null +++ b/app/views/award_emoji/_awards_block.html.haml @@ -0,0 +1,15 @@ +- grouped_awards = awardable.grouped_awards(inline) +.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.size == 0), data: { award_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) } } + - awards_sort(grouped_awards).each do |emoji, awards| + %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), title: award_user_list(awards, current_user), data: { placement: "bottom" } } + = emoji_icon(emoji) + %span.award-control-text.js-counter + = awards.count + + - if current_user + .award-menu-holder.js-award-holder + %button.btn.award-control.js-add-award{ type: "button", data: { award_menu_url: emojis_path } } + = icon('smile-o', {class: "award-control-icon award-control-icon-normal"}) + = icon('spinner spin', {class: "award-control-icon award-control-icon-loading"}) + %span.award-control-text + Add diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml index 3443a8e2307..97401a2e618 100644 --- a/app/views/emojis/index.html.haml +++ b/app/views/emojis/index.html.haml @@ -1,9 +1,9 @@ .emoji-menu .emoji-menu-content = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control" - - AwardEmoji.emoji_by_category.each do |category, emojis| + - Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis| %h5.emoji-menu-title - = AwardEmoji::CATEGORIES[category] + = Gitlab::AwardEmoji::CATEGORIES[category] %ul.clearfix.emoji-menu-list - emojis.each do |emoji| %li.pull-left.text-center.emoji-menu-list-item diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 7a8009f6da4..4aa92d0b39e 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -27,7 +27,7 @@ = icon('thumbs-down') = downvotes - - note_count = issue.notes.user.nonawards.count + - note_count = issue.notes.user.count - if note_count > 0 %li = link_to issue_path(issue) + "#notes" do diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 5fe5ddc0819..c4cdd4b3d43 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -72,7 +72,7 @@ .content-block.content-block-small = render 'new_branch' - = render 'votes/votes_block', votable: @issue + = render 'award_emoji/awards_block', awardable: @issue, inline: true .row %section.col-md-12 diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index e740fe8c84d..391193eed6c 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -35,7 +35,7 @@ = icon('thumbs-down') = downvotes - - note_count = merge_request.mr_and_commit_notes.user.nonawards.count + - note_count = merge_request.mr_and_commit_notes.user.count - if note_count > 0 %li = link_to merge_request_path(merge_request) + "#notes" do diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 2c34f9c454b..e8cda51e759 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -50,7 +50,7 @@ %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do Discussion - %span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count + %span.badge= @merge_request.mr_and_commit_notes.user.count %li.commits-tab = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits @@ -68,7 +68,7 @@ .tab-content #notes.notes.tab-pane.voting_notes .content-block.content-block-small.oneline-block - = render 'votes/votes_block', votable: @merge_request + = render 'award_emoji/awards_block', awardable: @merge_request, inline: true .row %section.col-md-12 diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index dc249155b92..8692c1cccee 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,9 +1,9 @@ -.awards.votes-block - - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| - %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}} +.awards.votes-block{data: { toggle_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) }} + - awards_sort(awardable.grouped_awards).each do |emoji, awards| + %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(awards, current_user)), data: {placement: "top", original_title: emoji_author_list(awards, current_user)}} = emoji_icon(emoji, sprite: false) %span.award-control-text.js-counter - = notes.count + = awards.count - if current_user %div.award-menu-holder.js-award-holder diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 9e8b0131f8f..3d1a41a4652 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -8,3 +8,7 @@ # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end +# +ActiveSupport::Inflector.inflections do |inflect| + inflect.uncountable %w(award_emoji) +end diff --git a/config/routes.rb b/config/routes.rb index 46a25262844..ecde83d8547 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -639,6 +639,7 @@ Rails.application.routes.draw do post :cancel_merge_when_build_succeeds get :ci_status post :toggle_subscription + post :toggle_award_emoji post :remove_wip end @@ -703,6 +704,7 @@ Rails.application.routes.draw do resources :issues, constraints: { id: /\d+/ } do member do post :toggle_subscription + post :toggle_award_emoji get :referenced_merge_requests get :related_branches end @@ -731,10 +733,7 @@ Rails.application.routes.draw do resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do member do delete :delete_attachment - end - - collection do - post :award_toggle + post :toggle_award_emoji end end diff --git a/db/migrate/20160416180807_add_award_emoji.rb b/db/migrate/20160416180807_add_award_emoji.rb new file mode 100644 index 00000000000..3177b86a133 --- /dev/null +++ b/db/migrate/20160416180807_add_award_emoji.rb @@ -0,0 +1,15 @@ +class AddAwardEmoji < ActiveRecord::Migration + def change + create_table :award_emoji do |t| + t.string :name + t.references :user + t.references :awardable, polymorphic: true + + t.timestamps + end + + add_index :award_emoji, :user_id + add_index :award_emoji, :awardable_type + add_index :award_emoji, :awardable_id + end +end diff --git a/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb b/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb new file mode 100644 index 00000000000..76f4a3aa6ae --- /dev/null +++ b/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb @@ -0,0 +1,17 @@ +class ConvertAwardNoteToEmojiAward < ActiveRecord::Migration + def change + def up + execute "INSERT INTO award_emoji (awardable_type, awardable_id, user_id, name, created_at, updated_at) (SELECT noteable_type, noteable_id, author_id, note, created_at, updated_at FROM notes WHERE is_award = true)" + end + + def down + execute <<-SQL + INSERT INTO notes (noteable_type, noteable_id, author_id, note, created_at, updated_at, is_award) + (SELECT awardable_type, awardable_id, user_id, name, created_at, updated_at, TRUE + FROM award_emoji + WHERE awardable_type IN ('Issue', 'MergeRequest') + ) + SQL + end + end +end diff --git a/db/migrate/20160416190505_remove_note_is_award.rb b/db/migrate/20160416190505_remove_note_is_award.rb new file mode 100644 index 00000000000..da16372a297 --- /dev/null +++ b/db/migrate/20160416190505_remove_note_is_award.rb @@ -0,0 +1,5 @@ +class RemoveNoteIsAward < ActiveRecord::Migration + def change + remove_column :notes, :is_award, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 42c261003bb..354d7390a5b 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: 20160412140240) do +ActiveRecord::Schema.define(version: 20160416190505) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -94,6 +94,19 @@ ActiveRecord::Schema.define(version: 20160412140240) do add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree + create_table "award_emoji", force: :cascade do |t| + t.string "name" + t.integer "user_id" + t.integer "awardable_id" + t.string "awardable_type" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "award_emoji", ["awardable_id"], name: "index_award_emoji_on_awardable_id", using: :btree + add_index "award_emoji", ["awardable_type"], name: "index_award_emoji_on_awardable_type", using: :btree + add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree + create_table "broadcast_messages", force: :cascade do |t| t.text "message", null: false t.datetime "starts_at" @@ -622,14 +635,12 @@ ActiveRecord::Schema.define(version: 20160412140240) do t.boolean "system", default: false, null: false t.text "st_diff" t.integer "updated_by_id" - t.boolean "is_award", default: false, null: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree 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", ["is_award"], name: "index_notes_on_is_award", using: :btree add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"} add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree @@ -716,37 +727,37 @@ ActiveRecord::Schema.define(version: 20160412140240) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.string "issues_tracker", default: "gitlab", null: false + t.string "issues_tracker", default: "gitlab", null: false t.string "issues_tracker_id" - t.boolean "snippets_enabled", default: true, null: false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false t.string "avatar" t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" - t.integer "commit_count", default: 0 + t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false t.string "main_language" - t.integer "pushes_since_gc", default: 0 + t.integer "pushes_since_gc", default: 0 t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 60b9f5e0ece..b3769ba9c2d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -170,6 +170,7 @@ module API expose :label_names, as: :labels expose :milestone, using: Entities::Milestone expose :assignee, :author, using: Entities::UserBasic + expose :upvotes, :downvotes expose :subscribed do |issue, options| issue.subscribed?(options[:current_user]) @@ -178,7 +179,7 @@ module API class MergeRequest < ProjectEntity expose :target_branch, :source_branch - expose :upvotes, :downvotes + expose :upvotes, :downvotes expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :label_names, as: :labels @@ -216,8 +217,8 @@ module API expose :system?, as: :system expose :noteable_id, :noteable_type # upvote? and downvote? are deprecated, always return false - expose :upvote?, as: :upvote - expose :downvote?, as: :downvote + expose(:upvote?) { |note| false } + expose(:downvote?) { |note| false } end class MRNote < Grape::Entity diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb deleted file mode 100644 index 5f8ff01b0a9..00000000000 --- a/lib/award_emoji.rb +++ /dev/null @@ -1,80 +0,0 @@ -class AwardEmoji - CATEGORIES = { - other: "Other", - objects: "Objects", - places: "Places", - travel_places: "Travel", - emoticons: "Emoticons", - objects_symbols: "Symbols", - nature: "Nature", - celebration: "Celebration", - people: "People", - activity: "Activity", - flags: "Flags", - food_drink: "Food" - }.with_indifferent_access - - CATEGORY_ALIASES = { - symbols: "objects_symbols", - foods: "food_drink", - travel: "travel_places" - }.with_indifferent_access - - def self.normilize_emoji_name(name) - aliases[name] || name - end - - def self.emoji_by_category - unless @emoji_by_category - @emoji_by_category = Hash.new { |h, key| h[key] = [] } - - emojis.each do |emoji_name, data| - data["name"] = emoji_name - - # Skip Fitzpatrick(tone) modifiers - next if data["category"] == "modifier" - - category = CATEGORY_ALIASES[data["category"]] || data["category"] - - @emoji_by_category[category] << data - end - - @emoji_by_category = @emoji_by_category.sort.to_h - end - - @emoji_by_category - end - - def self.emojis - @emojis ||= begin - json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' ) - JSON.parse(File.read(json_path)) - end - end - - def self.aliases - @aliases ||= begin - json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' ) - JSON.parse(File.read(json_path)) - end - end - - # Returns an Array of Emoji names and their asset URLs. - def self.urls - @urls ||= begin - path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') - prefix = Gitlab::Application.config.assets.prefix - digest = Gitlab::Application.config.assets.digest - - JSON.parse(File.read(path)).map do |hash| - if digest - fname = "#{hash['unicode']}-#{hash['digest']}" - else - fname = hash['unicode'] - end - - { name: hash['name'], path: "#{prefix}/#{fname}.png" } - end - end - end -end diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb new file mode 100644 index 00000000000..0ae220a86bc --- /dev/null +++ b/lib/gitlab/award_emoji.rb @@ -0,0 +1,82 @@ +module Gitlab + class AwardEmoji + CATEGORIES = { + other: "Other", + objects: "Objects", + places: "Places", + travel_places: "Travel", + emoticons: "Emoticons", + objects_symbols: "Symbols", + nature: "Nature", + celebration: "Celebration", + people: "People", + activity: "Activity", + flags: "Flags", + food_drink: "Food" + }.with_indifferent_access + + CATEGORY_ALIASES = { + symbols: "objects_symbols", + foods: "food_drink", + travel: "travel_places" + }.with_indifferent_access + + def self.normalize_emoji_name(name) + aliases[name] || name + end + + def self.emoji_by_category + unless @emoji_by_category + @emoji_by_category = Hash.new { |h, key| h[key] = [] } + + emojis.each do |emoji_name, data| + data["name"] = emoji_name + + # Skip Fitzpatrick(tone) modifiers + next if data["category"] == "modifier" + + category = CATEGORY_ALIASES[data["category"]] || data["category"] + + @emoji_by_category[category] << data + end + + @emoji_by_category = @emoji_by_category.sort.to_h + end + + @emoji_by_category + end + + def self.emojis + @emojis ||= begin + json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' ) + JSON.parse(File.read(json_path)) + end + end + + def self.aliases + @aliases ||= begin + json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' ) + JSON.parse(File.read(json_path)) + end + end + + # Returns an Array of Emoji names and their asset URLs. + def self.urls + @urls ||= begin + path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') + prefix = Gitlab::Application.config.assets.prefix + digest = Gitlab::Application.config.assets.digest + + JSON.parse(File.read(path)).map do |hash| + if digest + fname = "#{hash['unicode']}-#{hash['digest']}" + else + fname = hash['unicode'] + end + + { name: hash['name'], path: "#{prefix}/#{fname}.png" } + end + end + end + end +end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 465531b2b36..82b25702172 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -31,9 +31,9 @@ describe GroupsController do let(:issue_2) { create(:issue, project: project) } before do - create_list(:upvote_note, 3, project: project, noteable: issue_2) - create_list(:upvote_note, 2, project: project, noteable: issue_1) - create_list(:downvote_note, 2, project: project, noteable: issue_2) + create_list(:award_emoji, 3, awardable: issue_2) + create_list(:award_emoji, 2, awardable: issue_1) + create_list(:award_emoji, 2, awardable: issue_2, name: "thumbsdown") sign_in(user) end @@ -56,9 +56,9 @@ describe GroupsController do let(:merge_request_2) { create(:merge_request, :simple, source_project: project) } before do - create_list(:upvote_note, 3, project: project, noteable: merge_request_2) - create_list(:upvote_note, 2, project: project, noteable: merge_request_1) - create_list(:downvote_note, 2, project: project, noteable: merge_request_2) + create_list(:award_emoji, 3, awardable: merge_request_2) + create_list(:award_emoji, 2, awardable: merge_request_1) + create_list(:award_emoji, 2, awardable: merge_request_2, name: "thumbsdown") sign_in(user) end diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb new file mode 100644 index 00000000000..a1173834b29 --- /dev/null +++ b/spec/factories/award_emoji.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :award_emoji do + name "thumbsup" + user + awardable factory: :issue + end +end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index e5dcb159014..2bfc5effd78 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -36,8 +36,6 @@ FactoryGirl.define do factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] factory :note_on_project_snippet, traits: [:on_project_snippet] factory :system_note, traits: [:system] - factory :downvote_note, traits: [:award, :downvote] - factory :upvote_note, traits: [:award, :upvote] trait :on_commit do project @@ -69,10 +67,6 @@ FactoryGirl.define do system true end - trait :award do - is_award true - end - trait :downvote do note "thumbsdown" end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 543593cf389..2d4d9c18c9d 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -127,18 +127,15 @@ describe IssuesHelper do it { is_expected.to eq("!1, !2, or !3") } end - describe "note_active_class" do - before do - @note = create :note - @note1 = create :note - end + describe '#award_active_class' do + let!(:upvote) { create(:award_emoji) } it "returns empty string for unauthenticated user" do - expect(note_active_class(Note.all, nil)).to eq("") + expect(award_active_class(AwardEmoji.all, nil)).to eq("") end it "returns active string for author" do - expect(note_active_class(Note.all, @note.author)).to eq("active") + expect(award_active_class(AwardEmoji.all, upvote.user)).to eq("active") end end diff --git a/spec/lib/award_emoji_spec.rb b/spec/lib/gitlab/award_emoji_spec.rb similarity index 75% rename from spec/lib/award_emoji_spec.rb rename to spec/lib/gitlab/award_emoji_spec.rb index 88c22912950..4e6c04a11b9 100644 --- a/spec/lib/award_emoji_spec.rb +++ b/spec/lib/gitlab/award_emoji_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' -describe AwardEmoji do +describe Gitlab::AwardEmoji do describe '.urls' do - subject { AwardEmoji.urls } + subject { Gitlab::AwardEmoji.urls } it { is_expected.to be_an_instance_of(Array) } it { is_expected.to_not be_empty } @@ -19,7 +19,7 @@ describe AwardEmoji do describe '.emoji_by_category' do it "only contains known categories" do - undefined_categories = AwardEmoji.emoji_by_category.keys - AwardEmoji::CATEGORIES.keys + undefined_categories = Gitlab::AwardEmoji.emoji_by_category.keys - Gitlab::AwardEmoji::CATEGORIES.keys expect(undefined_categories).to be_empty end end diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb new file mode 100644 index 00000000000..fd3712b7d43 --- /dev/null +++ b/spec/models/award_emoji_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe AwardEmoji, models: true do + describe 'Associations' do + it { is_expected.to belong_to(:awardable) } + it { is_expected.to belong_to(:user) } + end + + describe 'modules' do + it { is_expected.to include_module(Participable) } + end + + describe "validations" do + it { is_expected.to validate_presence_of(:awardable) } + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:awardable) } + + # To circumvent a bug in the shoulda matchers + describe "scoped uniqueness validation" do + it "rejects duplicate award emoji" do + user = create(:user) + issue = create(:issue) + create(:award_emoji, user: user, awardable: issue) + new_award = AwardEmoji.new(user: user, awardable: issue, name: "thumbsup") + + expect(new_award).not_to be_valid + end + end + end +end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index b16ccc6e305..d5435916ea1 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -198,18 +198,4 @@ describe Issue, "Issuable" do to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' }) end end - - describe "votes" do - before do - author = create :user - project = create :empty_project - issue.notes.awards.create!(note: "thumbsup", author: author, project: project) - issue.notes.awards.create!(note: "thumbsdown", author: author, project: project) - end - - it "returns correct values" do - expect(issue.upvotes).to eq(1) - expect(issue.downvotes).to eq(1) - end - end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 6b18936edb1..bb591e9cb53 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -152,23 +152,6 @@ describe Note, models: true do end end - describe '.grouped_awards' do - before do - create :note, note: "smile", is_award: true - create :note, note: "smile", is_award: true - end - - it "returns grouped hash of notes" do - expect(Note.grouped_awards.keys.size).to eq(3) - expect(Note.grouped_awards["smile"]).to match_array(Note.all) - end - - it "returns thumbsup and thumbsdown always" do - expect(Note.grouped_awards["thumbsup"]).to match_array(Note.none) - expect(Note.grouped_awards["thumbsdown"]).to match_array(Note.none) - end - end - describe '#active?' do it 'is always true when the note has no associated diff' do note = build(:note) @@ -239,11 +222,6 @@ describe Note, models: true do note = build(:note, system: true) expect(note.editable?).to be_falsy end - - it "returns false" do - note = build(:note, is_award: true, note: "smiley") - expect(note.editable?).to be_falsy - end end describe "cross_reference_not_visible_for?" do @@ -270,23 +248,6 @@ describe Note, models: true do end end - describe "set_award!" do - let(:merge_request) { create :merge_request } - - it "converts aliases to actual name" do - note = create(:note, note: ":+1:", noteable: merge_request) - expect(note.reload.note).to eq("thumbsup") - end - - it "is not an award emoji when comment is on a diff" do - note = create(:note, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2") - note = note.reload - - expect(note.note).to eq(":blowfish:") - expect(note.is_award?).to be_falsy - end - end - describe 'clear_blank_line_code!' do it 'clears a blank line code before validation' do note = build(:note, line_code: ' ') From 4eb16290e4e95c0a9bcf3d01ecc8060d91eec021 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 25 Apr 2016 09:09:39 +0100 Subject: [PATCH 0180/1431] move frontend logic from previous MR to new MR --- app/assets/javascripts/awards_handler.coffee | 211 +++++++++++------- app/assets/javascripts/dispatcher.js.coffee | 2 + .../lib/emoji_aliases.js.coffee.erb | 9 + app/assets/javascripts/notes.js.coffee | 4 +- app/assets/stylesheets/pages/awards.scss | 13 +- app/assets/stylesheets/pages/notes.scss | 41 +++- app/finders/notes_finder.rb | 4 +- app/views/award_emoji/_awards_block.html.haml | 6 +- 8 files changed, 190 insertions(+), 100 deletions(-) create mode 100644 app/assets/javascripts/lib/emoji_aliases.js.coffee.erb diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index af4462ece38..4c0a274b793 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,63 +1,109 @@ class @AwardsHandler - constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) -> - $(".js-add-award").on "click", (event) => - event.stopPropagation() - event.preventDefault() + constructor: -> + @aliases = gl.emoji.emojiAliases() - @showEmojiMenu() + $(document) + .off "click", ".js-add-award" + .on "click", ".js-add-award", (event) => + event.stopPropagation() + event.preventDefault() + + @showEmojiMenu $(event.currentTarget) $("html").on 'click', (event) -> if !$(event.target).closest(".emoji-menu").length if $(".emoji-menu").is(":visible") + $('.js-add-award.is-active').removeClass 'is-active' $(".emoji-menu").removeClass "is-visible" - $(".awards") - .off "click" - .on "click", ".js-emoji-btn", @handleClick - - @renderFrequentlyUsedBlock() + $(document) + .off "click", ".js-emoji-btn" + .on "click", ".js-emoji-btn", (e) => @handleClick(e) handleClick: (e) -> e.preventDefault() - emoji = $(this) + $emojiBtn = $(e.currentTarget) + $addAwardBtn = $('.js-add-award.is-active') + $votesBlock = $($addAwardBtn.closest('.js-award-holder').data('target')) + + if $addAwardBtn.length is 0 + $votesBlock = $emojiBtn.closest('.js-awards-block') + else if $votesBlock.length is 0 + $votesBlock = $addAwardBtn.closest('.js-awards-block') + + $votesBlock.addClass 'js-awards-block-current' + awardUrl = $votesBlock.data 'award-url' + emoji = $emojiBtn .find(".icon") .data "emoji" - if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown" - awards_handler.addAward "thumbsdown" + if emoji is "thumbsup" and @didUserClickEmoji $emojiBtn, "thumbsdown" + @addAward awardUrl, "thumbsdown" - else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup" - awards_handler.addAward "thumbsup" + else if emoji is "thumbsdown" and @didUserClickEmoji $emojiBtn, "thumbsup" + @addAward awardUrl, "thumbsup" - awards_handler.addAward emoji + @addAward awardUrl, emoji - didUserClickEmoji: (that, emoji) -> - if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title") - $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1 + didUserClickEmoji: (emojiBtn, emoji) -> + if emojiBtn.siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title") + emojiBtn.siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1 - showEmojiMenu: -> - if $(".emoji-menu").length - if $(".emoji-menu").is ".is-visible" - $(".emoji-menu").removeClass "is-visible" + showEmojiMenu: ($addBtn) -> + $menu = $('.emoji-menu') + if $menu.length + $holder = $addBtn.closest('.js-award-holder') + + if $menu.is ".is-visible" + $addBtn.removeClass "is-active" + $menu.removeClass "is-visible" $("#emoji_search").blur() else $(".emoji-menu").addClass "is-visible" + $addBtn.addClass "is-active" + @positionMenu($menu, $addBtn) + + $menu.addClass "is-visible" $("#emoji_search").focus() else - $('.js-add-award').addClass "is-loading" - $.get @get_emojis_url, (response) => - $('.js-add-award').removeClass "is-loading" - $(".js-award-holder").append response + $addBtn.addClass "is-loading is-active" + $.get $addBtn.data('award-menu-url'), (response) => + $addBtn.removeClass "is-loading" + $('body').append response + + $menu = $(".emoji-menu") + + @positionMenu($menu, $addBtn) + + @renderFrequentlyUsedBlock() setTimeout => - $(".emoji-menu").addClass "is-visible" + $menu.addClass "is-visible" $("#emoji_search").focus() @setupSearch() , 200 - addAward: (emoji) -> + positionMenu: ($menu, $addBtn) -> + position = $addBtn.data('position') + + # The menu could potentially be off-screen or in a hidden overflow element + # So we position the element absolute in the body + css = + top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px" + + if position? and position is 'right' + css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px" + $menu.addClass "is-aligned-right" + else + css.left = "#{$addBtn.offset().left}px" + $menu.removeClass "is-aligned-right" + + $menu.css(css) + + addAward: (awardUrl, emoji) -> emoji = @normilizeEmojiName(emoji) - @postEmoji emoji, => + @postEmoji awardUrl, emoji, => @addAwardToEmojiBar(emoji) + $('.js-awards-block').removeClass 'js-awards-block-current' $(".emoji-menu").removeClass "is-visible" @@ -65,58 +111,60 @@ class @AwardsHandler @addEmojiToFrequentlyUsedList(emoji) emoji = @normilizeEmojiName(emoji) - if @exist(emoji) - if @isActive(emoji) - @decrementCounter(emoji) + $emojiBtn = @findEmojiIcon(emoji).parent() + + if $emojiBtn.length > 0 + if @isActive($emojiBtn) + @decrementCounter($emojiBtn, emoji) else - counter = @findEmojiIcon(emoji).siblings(".js-counter") - counter.text(parseInt(counter.text()) + 1) - counter.parent().addClass("active") - @addMeToAuthorList(emoji) + $counter = $emojiBtn.find('.js-counter') + $counter.text(parseInt($counter.text()) + 1) + $emojiBtn.addClass("active") + @addMeToUserList(emoji) else @createEmoji(emoji) - exist: (emoji) -> - @findEmojiIcon(emoji).length > 0 + isActive: ($emojiBtn) -> + $emojiBtn.hasClass("active") - isActive: (emoji) -> - @findEmojiIcon(emoji).parent().hasClass("active") + decrementCounter: ($emojiBtn, emoji) -> + $awardsBlock = $emojiBtn.closest('.js-awards-block') + isntNoteBody = $emojiBtn.closest('.note-body').length is 0 + counter = $('.js-counter', $emojiBtn) + counterNumber = parseInt(counter.text()) - decrementCounter: (emoji) -> - counter = @findEmojiIcon(emoji).siblings(".js-counter") - emojiIcon = counter.parent() - if parseInt(counter.text()) > 1 - counter.text(parseInt(counter.text()) - 1) - emojiIcon.removeClass("active") - @removeMeFromAuthorList(emoji) - else if emoji == "thumbsup" || emoji == "thumbsdown" - emojiIcon.tooltip("destroy") - counter.text(0) - emojiIcon.removeClass("active") - @removeMeFromAuthorList(emoji) + if counterNumber > 1 + counter.text(counterNumber - 1) + @removeMeFromUserList($emojiBtn, emoji) + else if (emoji == "thumbsup" || emoji == "thumbsdown") && isntNoteBody + $emojiBtn.tooltip("destroy") + counter.text('0') + @removeMeFromUserList($emojiBtn, emoji) else - emojiIcon.tooltip("destroy") - emojiIcon.remove() + $emojiBtn.tooltip("destroy") + $emojiBtn.remove() - removeMeFromAuthorList: (emoji) -> - award_block = @findEmojiIcon(emoji).parent() + $emojiBtn.removeClass("active") + + removeMeFromUserList: ($emojiBtn, emoji) -> + award_block = $emojiBtn authors = award_block .attr("data-original-title") .split(", ") - authors.splice(authors.indexOf("me"),1) + authors.splice(authors.indexOf("me"), 1) award_block .closest(".js-emoji-btn") .attr("data-original-title", authors.join(", ")) @resetTooltip(award_block) - addMeToAuthorList: (emoji) -> + addMeToUserList: (emoji) -> award_block = @findEmojiIcon(emoji).parent() origTitle = award_block.attr("data-original-title").trim() - authors = [] + users = [] if origTitle - authors = origTitle.split(', ') - authors.push("me") - award_block.attr("data-original-title", authors.join(", ")) + users = origTitle.split(', ') + users.push("me") + award_block.attr("data-original-title", users.join(", ")) @resetTooltip(award_block) resetTooltip: (award) -> @@ -127,24 +175,24 @@ class @AwardsHandler award.tooltip() ), 200 - createEmoji: (emoji) -> emojiCssClass = @resolveNameToCssClass(emoji) - nodes = [] - nodes.push( - "" - ) + buttonHtml = "" - emoji_node = $(nodes.join("\n")) - .insertBefore(".js-award-holder") + emoji_node = $(buttonHtml) + .insertBefore(".js-awards-block-current .js-award-holder:not(.js-award-action-btn)") .find(".emoji-icon") .data("emoji", emoji) $('.award-control').tooltip() + $currentBlock = $('.js-awards-block-current') + if $currentBlock.is('.hidden') + $currentBlock.removeClass 'hidden' + resolveNameToCssClass: (emoji) -> emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") @@ -156,17 +204,13 @@ class @AwardsHandler "emoji-#{unicodeName}" - postEmoji: (emoji, callback) -> - $.post @post_emoji_url, { note: { - note: ":#{emoji}:" - noteable_type: @noteable_type - noteable_id: @noteable_id - }},(data) -> + postEmoji: (awardUrl, emoji, callback) -> + $.post awardUrl, { name: emoji }, (data) -> if data.ok callback.call() findEmojiIcon: (emoji) -> - $(".awards > .js-emoji-btn [data-emoji='#{emoji}']") + $(".js-awards-block-current.awards > .js-emoji-btn [data-emoji='#{emoji}']") scrollToAwards: -> $('body, html').animate({ @@ -189,16 +233,15 @@ class @AwardsHandler if $.cookie('frequently_used_emojis') frequently_used_emojis = @getFrequentlyUsedEmojis() - ul = $("