From df27b3a1742048358674733d9cb3392348d5e964 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 9 Feb 2016 12:02:52 +0100 Subject: [PATCH 001/573] Issuable can be assigned to author Closes #9014 The only difference with #9014 is that I thoughed the author should also be able to assign the issue. If this is unwanted behavior Ill revert it. --- CHANGELOG | 1 + app/assets/javascripts/users_select.js.coffee | 4 +++- app/controllers/autocomplete_controller.rb | 8 +++++++- app/controllers/projects/issues_controller.rb | 4 ++-- app/helpers/selects_helper.rb | 16 +++++++++------- app/models/concerns/issuable.rb | 5 +++++ app/services/issuable_base_service.rb | 11 ++++++++--- app/services/issues/base_service.rb | 4 ++-- app/services/merge_requests/base_service.rb | 4 ++-- app/views/shared/issuable/_sidebar.html.haml | 7 +++++-- spec/controllers/autocomplete_controller_spec.rb | 13 +++++++++++++ spec/models/concerns/issuable_spec.rb | 14 ++++++++++++++ 12 files changed, 71 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 77a21e757c..e93979fd46 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -78,6 +78,7 @@ v 8.5.0 (unreleased) - Add label description (Nuttanart Pornprasitsakul) - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Add Todos + - Allow issues and merge requests to be assigned to the author(Zeger-Jan van de Weg) v 8.4.4 - Update omniauth-saml gem to 1.4.2 diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 9467011799..93c0c7adfe 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -7,11 +7,12 @@ class @UsersSelect @projectId = $(select).data('project-id') @groupId = $(select).data('group-id') @showCurrentUser = $(select).data('current-user') + @authorId = $(select).data('author-id') showNullUser = $(select).data('null-user') showAnyUser = $(select).data('any-user') showEmailUser = $(select).data('email-user') firstUser = $(select).data('first-user') - + $(select).select2 placeholder: "Search for a user" multiple: $(select).hasClass('multiselect') @@ -112,6 +113,7 @@ class @UsersSelect project_id: @projectId group_id: @groupId current_user: @showCurrentUser + author_id: @authorId dataType: "json" ).done (users) -> callback(users) diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 77c8dafc01..23f2ab45ff 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -12,8 +12,14 @@ class AutocompleteController < ApplicationController if params[:search].blank? # Include current user if available to filter by "Me" if params[:current_user] && current_user - @users = [*@users, current_user].uniq + @users = [*@users, current_user] end + + if params[:author_id] + @users = [User.find(params[:author_id]), *@users] + end + + @users.uniq! end render json: @users, only: [:name, :username, :id], methods: [:avatar_url] diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 67faa1e443..8aa85e448c 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -59,8 +59,8 @@ class Projects::IssuesController < Projects::ApplicationController end def show - @note = @project.notes.new(noteable: @issue) - @notes = @issue.notes.nonawards.with_associations.fresh + @note = @project.notes.new(noteable: @issue) + @notes = @issue.notes.nonawards.with_associations.fresh @noteable = @issue @merge_requests = @issue.referenced_merge_requests(current_user) diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 05386d790c..368762b826 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -6,12 +6,13 @@ module SelectsHelper value = opts[:selected] || '' placeholder = opts[:placeholder] || 'Search for a user' - null_user = opts[:null_user] || false - any_user = opts[:any_user] || false - email_user = opts[:email_user] || false - first_user = opts[:first_user] && current_user ? current_user.username : false - current_user = opts[:current_user] || false - project = opts[:project] || @project + null_user = opts[:null_user] || false + any_user = opts[:any_user] || false + email_user = opts[:email_user] || false + first_user = opts[:first_user] && current_user ? current_user.username : false + current_user = opts[:current_user] || false + author_id = opts[:author_id] || false + project = opts[:project] || @project html = { class: css_class, @@ -21,7 +22,8 @@ module SelectsHelper any_user: any_user, email_user: email_user, first_user: first_user, - current_user: current_user + current_user: current_user, + author_id: author_id } } diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index e5f089fb8a..f9314a241b 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -116,6 +116,11 @@ module Issuable assignee_id_changed? end + def can_assign_user?(current_user) + author == current_user || + Ability.abilities.allowed?(current_user, :"admin_#{to_ability_name}", project) + end + def open? opened? || reopened? end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index ca87dca4a7..99a28b8c63 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -33,7 +33,7 @@ class IssuableBaseService < BaseService end end - def filter_params(issuable_ability_name = :issue) + def filter_params(issuable_ability_name, issuable) params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE @@ -42,13 +42,18 @@ class IssuableBaseService < BaseService unless can?(current_user, ability, project) params.delete(:milestone_id) params.delete(:label_ids) - params.delete(:assignee_id) + + # The author of an issue can be assigned, to signal the ball being in his/her + # court. This allow him/her to reassign the issue back to the reviewer. + if issuable && !(issuable.author == current_user) + params.delete(:assignee_id) + end end end def update(issuable) change_state(issuable) - filter_params + filter_params(issuable) old_labels = issuable.labels.to_a if params.present? && issuable.update_attributes(params.merge(updated_by: current_user)) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 770f32de94..4527d1c74e 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -10,8 +10,8 @@ module Issues private - def filter_params - super(:issue) + def filter_params(issuable = nil) + super(:issue, issuable) end def execute_hooks(issue, action = 'open') diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 7b306a8a53..2afbdfae66 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -23,8 +23,8 @@ module MergeRequests private - def filter_params - super(:merge_request) + def filter_params(issueable = nil) + super(:merge_request, issueable) end end end diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index a45775f36b..1df44eaa64 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -30,7 +30,7 @@ .title %label Assignee - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + - if issuable.can_assign_user?(current_user) .pull-right = link_to 'Edit', '#', class: 'edit-link' .value @@ -43,7 +43,10 @@ .light None .selectbox - = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true) + = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", + placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', + selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, + first_user: true, author_id: issuable.author_id) .block.milestone .sidebar-collapsed-icon diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 85379a8e98..0754a2889f 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -143,4 +143,17 @@ describe AutocompleteController do it { expect(body.size).to eq 0 } end end + + context 'author of issuable included' do + before do + sign_in(user) + get(:users, author_id: non_member.id) + end + + let(:body) { JSON.parse(response.body) } + + it 'should also return the author' do + expect(body.first["username"]).to eq non_member.username + end + end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 600089802b..16fc1a6069 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -111,6 +111,20 @@ describe Issue, "Issuable" do end end + describe "#can_assign_user?" do + let(:author) { build(:user) } + let(:issue) { build(:issue, author: author)} + + it "Allows the author to change the assignee" do + expect(issue.can_assign_user?(author)).to be_truthy + end + + it "Doesn't allow others, non-team members" do + other_user = build(:user) + expect(issue.can_assign_user?(other_user)).to be_falsey + end + end + describe "votes" do before do author = create :user From 829830ae9df9e777716e03fa393a328d3ff882b0 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 9 Feb 2016 14:36:12 +0100 Subject: [PATCH 002/573] Fix loading data when no author_id is passed --- app/controllers/autocomplete_controller.rb | 2 +- app/services/merge_requests/base_service.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 23f2ab45ff..5d81a996fb 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -15,7 +15,7 @@ class AutocompleteController < ApplicationController @users = [*@users, current_user] end - if params[:author_id] + unless params[:author_id] == "false" @users = [User.find(params[:author_id]), *@users] end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 2afbdfae66..e472381ba3 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -23,8 +23,8 @@ module MergeRequests private - def filter_params(issueable = nil) - super(:merge_request, issueable) + def filter_params(issuable = nil) + super(:merge_request, issuable) end end end From 15a6633999c81387245cabf129dd2fbb04650c95 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 17 Feb 2016 12:49:38 +0100 Subject: [PATCH 003/573] Revert authors ability to assign anyone --- app/controllers/autocomplete_controller.rb | 2 +- app/models/concerns/issuable.rb | 5 ----- app/services/issuable_base_service.rb | 11 +++-------- app/services/issues/base_service.rb | 4 ++-- app/services/merge_requests/base_service.rb | 4 ++-- app/views/shared/issuable/_sidebar.html.haml | 2 +- spec/controllers/autocomplete_controller_spec.rb | 4 ++-- spec/models/concerns/issuable_spec.rb | 14 -------------- 8 files changed, 11 insertions(+), 35 deletions(-) diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 5d81a996fb..1e4fc612a3 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -15,7 +15,7 @@ class AutocompleteController < ApplicationController @users = [*@users, current_user] end - unless params[:author_id] == "false" + if params[:author_id] && params[:author_id] != "false" @users = [User.find(params[:author_id]), *@users] end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index f9314a241b..e5f089fb8a 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -116,11 +116,6 @@ module Issuable assignee_id_changed? end - def can_assign_user?(current_user) - author == current_user || - Ability.abilities.allowed?(current_user, :"admin_#{to_ability_name}", project) - end - def open? opened? || reopened? end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 99a28b8c63..ca87dca4a7 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -33,7 +33,7 @@ class IssuableBaseService < BaseService end end - def filter_params(issuable_ability_name, issuable) + def filter_params(issuable_ability_name = :issue) params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE @@ -42,18 +42,13 @@ class IssuableBaseService < BaseService unless can?(current_user, ability, project) params.delete(:milestone_id) params.delete(:label_ids) - - # The author of an issue can be assigned, to signal the ball being in his/her - # court. This allow him/her to reassign the issue back to the reviewer. - if issuable && !(issuable.author == current_user) - params.delete(:assignee_id) - end + params.delete(:assignee_id) end end def update(issuable) change_state(issuable) - filter_params(issuable) + filter_params old_labels = issuable.labels.to_a if params.present? && issuable.update_attributes(params.merge(updated_by: current_user)) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 4527d1c74e..770f32de94 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -10,8 +10,8 @@ module Issues private - def filter_params(issuable = nil) - super(:issue, issuable) + def filter_params + super(:issue) end def execute_hooks(issue, action = 'open') diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index e472381ba3..7b306a8a53 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -23,8 +23,8 @@ module MergeRequests private - def filter_params(issuable = nil) - super(:merge_request, issuable) + def filter_params + super(:merge_request) end end end diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 1df44eaa64..ef351fe809 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -30,7 +30,7 @@ .title %label Assignee - - if issuable.can_assign_user?(current_user) + - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .pull-right = link_to 'Edit', '#', class: 'edit-link' .value diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 0754a2889f..612e344c41 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -12,13 +12,13 @@ describe AutocompleteController do project.team << [user, :master] end - let(:body) { JSON.parse(response.body) } - describe 'GET #users with project ID' do before do get(:users, project_id: project.id) end + let(:body) { JSON.parse(response.body) } + it { expect(body).to be_kind_of(Array) } it { expect(body.size).to eq 1 } it { expect(body.first["username"]).to eq user.username } diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 16fc1a6069..600089802b 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -111,20 +111,6 @@ describe Issue, "Issuable" do end end - describe "#can_assign_user?" do - let(:author) { build(:user) } - let(:issue) { build(:issue, author: author)} - - it "Allows the author to change the assignee" do - expect(issue.can_assign_user?(author)).to be_truthy - end - - it "Doesn't allow others, non-team members" do - other_user = build(:user) - expect(issue.can_assign_user?(other_user)).to be_falsey - end - end - describe "votes" do before do author = create :user From 18411645505f4bf4bb877743cb4dc027d422414b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 2 Mar 2016 17:31:17 +0100 Subject: [PATCH 004/573] add initial migrations --- app/models/project.rb | 1 + ...151724_add_import_credentials_to_projects.rb | 6 ++++++ ...808_remove_wrong_import_url_from_projects.rb | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 db/migrate/20160302151724_add_import_credentials_to_projects.rb create mode 100644 db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb diff --git a/app/models/project.rb b/app/models/project.rb index 6f5d592755..36b11366f3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -91,6 +91,7 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace + attr_encrypted :import_credentials, key: Gitlab::Application.secrets.db_key_base # Relations belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' diff --git a/db/migrate/20160302151724_add_import_credentials_to_projects.rb b/db/migrate/20160302151724_add_import_credentials_to_projects.rb new file mode 100644 index 0000000000..3cfe2bbd50 --- /dev/null +++ b/db/migrate/20160302151724_add_import_credentials_to_projects.rb @@ -0,0 +1,6 @@ +class AddImportCredentialsToProjects < ActiveRecord::Migration + def change + add_column :projects, :encrypted_import_credentials, :text + add_column :projects, :encrypted_import_credentials_iv, :text + end +end diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb new file mode 100644 index 0000000000..3f1b65aff1 --- /dev/null +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -0,0 +1,17 @@ +class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration + def up + projects_with_wrong_import_url.each do |project| + project.update_columns(import_url: nil) # TODO Check really nil? + # TODO: migrate current credentials to import_credentials? + # TODO: Notify user ? + end + end + + private + + + def projects_with_dot_atom + # TODO Check live with #operations for possible false positives. Also, consider regex? But may have issues MySQL/PSQL + select_all("SELECT p.id from projects p WHERE p.import_url LIKE '%//%:%@%' or p.import_url like '#{"_"*40}@github.com%'") + end +end From cefefb2adea23c81c5e6254992da975eca71b559 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 3 Mar 2016 18:10:06 +0100 Subject: [PATCH 005/573] WIP - refactored migration and updated project_import_data with encrypted att --- app/models/project.rb | 1 - app/models/project_import_data.rb | 3 +- ...port_credentials_to_project_import_data.rb | 6 +++ ...1724_add_import_credentials_to_projects.rb | 6 --- ...8_remove_wrong_import_url_from_projects.rb | 38 +++++++++++++++---- 5 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb delete mode 100644 db/migrate/20160302151724_add_import_credentials_to_projects.rb diff --git a/app/models/project.rb b/app/models/project.rb index 36b11366f3..6f5d592755 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -91,7 +91,6 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace - attr_encrypted :import_credentials, key: Gitlab::Application.secrets.db_key_base # Relations belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index cd3319f077..2900b86d64 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -12,7 +12,8 @@ require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project - + attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base + serialize :data, JSON validates :project, presence: true diff --git a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb new file mode 100644 index 0000000000..ff3d8b466d --- /dev/null +++ b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb @@ -0,0 +1,6 @@ +class AddImportCredentialsToProjectImportData < ActiveRecord::Migration + def change + add_column :project_import_data, :encrypted_credentials, :text + add_column :project_import_data, :encrypted_credentials_iv, :text + end +end diff --git a/db/migrate/20160302151724_add_import_credentials_to_projects.rb b/db/migrate/20160302151724_add_import_credentials_to_projects.rb deleted file mode 100644 index 3cfe2bbd50..0000000000 --- a/db/migrate/20160302151724_add_import_credentials_to_projects.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddImportCredentialsToProjects < ActiveRecord::Migration - def change - add_column :projects, :encrypted_import_credentials, :text - add_column :projects, :encrypted_import_credentials_iv, :text - end -end diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 3f1b65aff1..dda7648fb8 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -1,16 +1,38 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration - def up - projects_with_wrong_import_url.each do |project| - project.update_columns(import_url: nil) # TODO Check really nil? - # TODO: migrate current credentials to import_credentials? - # TODO: Notify user ? + + class ImportUrlSanitizer + def initialize(url) + @url = url + end + + def sanitized_url + @sanitized_url ||= @url[regex_extractor, 1] + @url[regex_extractor, 3] + end + + def credentials + @credentials ||= @url[regex_extractor, 2] + end + + private + + # Regex matches 1 , 2 , + # 3 + def regex_extractor + /(.*\/\/)(.*)(\@.*)/ end end - private + def up + projects_with_wrong_import_url.each do |project| + sanitizer = ImportUrlSanitizer.new(project.import_urls) + project.update_columns(import_url: sanitizer.sanitized_url) + if project.import_data + project.import_data.update_columns(credentials: sanitizer.credentials) + end + end + end - - def projects_with_dot_atom + def projects_with_wrong_import_url # TODO Check live with #operations for possible false positives. Also, consider regex? But may have issues MySQL/PSQL select_all("SELECT p.id from projects p WHERE p.import_url LIKE '%//%:%@%' or p.import_url like '#{"_"*40}@github.com%'") end From 06b36c00d55df38cd2aaa4d5251185485c8abe5c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Mar 2016 12:21:53 +0100 Subject: [PATCH 006/573] some refactoring in the migration. Also fixed github import issue and updated spec --- ...52808_remove_wrong_import_url_from_projects.rb | 15 ++++++++++----- db/schema.rb | 4 +++- lib/gitlab/github_import/project_creator.rb | 12 ++++++++++-- .../gitlab/github_import/project_creator_spec.rb | 3 ++- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index dda7648fb8..dfa9f2d4de 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -23,11 +23,16 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def up - projects_with_wrong_import_url.each do |project| - sanitizer = ImportUrlSanitizer.new(project.import_urls) - project.update_columns(import_url: sanitizer.sanitized_url) - if project.import_data - project.import_data.update_columns(credentials: sanitizer.credentials) + projects_with_wrong_import_url.each do |project_id| + project = Project.find(project_id["id"]) + sanitizer = ImportUrlSanitizer.new(project.import_url) + + ActiveRecord::Base.transaction do + project.update_columns(import_url: sanitizer.sanitized_url) + if project.import_data + project.import_data.credentials = sanitizer.credentials + project.save! + end end end end diff --git a/db/schema.rb b/db/schema.rb index 53a941d30d..05c19fc7a2 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: 20160222153918) do +ActiveRecord::Schema.define(version: 20160302152808) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -658,6 +658,8 @@ ActiveRecord::Schema.define(version: 20160222153918) do create_table "project_import_data", force: :cascade do |t| t.integer "project_id" t.text "data" + t.text "encrypted_credentials" + t.text "encrypted_credentials_iv" end create_table "projects", force: :cascade do |t| diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 474927069a..d6cab3c2d2 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -20,13 +20,21 @@ module Gitlab visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, import_type: "github", import_source: repo.full_name, - import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"), + import_url: repo.clone_url, wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later ).execute - project.create_import_data(data: { "github_session" => session_data } ) + create_import_data(project) project end + + private + + def create_import_data(project) + project.create_import_data( + credentials: session_data.delete(:github_access_token), + data: { "github_session" => session_data }) + end end end end diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index c93a3ebdae..36abe87f52 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -26,7 +26,8 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user, access_params) project = project_creator.execute - expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") + expect(project.import_url).to eq("https://gitlab.com/asd/vim.git") + expect(project.import_data.credentials).to eq("asdffg") expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) end end From c2b33d3b71215858892debeb45d93a69d530fd8e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Mar 2016 16:23:19 +0100 Subject: [PATCH 007/573] added import url exposer to construct URL withunencrypted credentials --- app/models/project_import_data.rb | 1 + lib/gitlab/github_import/importer.rb | 3 +-- lib/gitlab/github_import/project_creator.rb | 4 ++-- lib/gitlab/github_import/wiki_formatter.rb | 4 +++- lib/gitlab/import_url_exposer.rb | 17 +++++++++++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 lib/gitlab/import_url_exposer.rb diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 2900b86d64..493c82a94f 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -13,6 +13,7 @@ require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base + serialize :credentials, JSON serialize :data, JSON diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index e2a85f2982..515fd4720d 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -7,8 +7,7 @@ module Gitlab def initialize(project) @project = project - import_data = project.import_data.try(:data) - github_session = import_data["github_session"] if import_data + github_session = project.import_data.credentials if import_data @client = Client.new(github_session["github_access_token"]) @formatter = Gitlab::ImportFormatter.new end diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index d6cab3c2d2..b5ed32e5b1 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -32,8 +32,8 @@ module Gitlab def create_import_data(project) project.create_import_data( - credentials: session_data.delete(:github_access_token), - data: { "github_session" => session_data }) + credentials: { github_access_token: session_data.delete(:github_access_token) }, + data: { github_session: session_data }) end end end diff --git a/lib/gitlab/github_import/wiki_formatter.rb b/lib/gitlab/github_import/wiki_formatter.rb index 6c592ff469..8be8292410 100644 --- a/lib/gitlab/github_import/wiki_formatter.rb +++ b/lib/gitlab/github_import/wiki_formatter.rb @@ -12,7 +12,9 @@ module Gitlab end def import_url - project.import_url.sub(/\.git\z/, ".wiki.git") + import_url = Gitlab::ImportUrlExposer.expose(import_url: project.import_url, + credentials: project.import_data.credentials) + import_url.sub(/\.git\z/, ".wiki.git") end end end diff --git a/lib/gitlab/import_url_exposer.rb b/lib/gitlab/import_url_exposer.rb new file mode 100644 index 0000000000..6b4af0bf26 --- /dev/null +++ b/lib/gitlab/import_url_exposer.rb @@ -0,0 +1,17 @@ +module Gitlab + # Exposes an import URL that includes the credentials unencrypted. + # Extracted to its own class to prevent unintended use. + module ImportUrlExposer + extend self + + def expose(import_url:, credentials: ) + import_url.sub("//", "//#{parsed_credentials(credentials)}@") + end + + private + + def parsed_credentials(credentials) + credentials.values.join(":") + end + end +end \ No newline at end of file From 7085850c50a6dd7072bd2c80f092b0c20f74d1dc Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Mar 2016 18:37:00 +0100 Subject: [PATCH 008/573] fix specs --- app/models/project_import_data.rb | 3 +-- lib/gitlab/import_url_exposer.rb | 2 +- spec/factories/project_import_data.rb | 5 +++++ spec/lib/gitlab/github_import/project_creator_spec.rb | 4 ++-- spec/lib/gitlab/github_import/wiki_formatter_spec.rb | 9 +++++++-- 5 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 spec/factories/project_import_data.rb diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 493c82a94f..23f4e97b8a 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -12,8 +12,7 @@ require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project - attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base - serialize :credentials, JSON + attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true serialize :data, JSON diff --git a/lib/gitlab/import_url_exposer.rb b/lib/gitlab/import_url_exposer.rb index 6b4af0bf26..f1919dffa8 100644 --- a/lib/gitlab/import_url_exposer.rb +++ b/lib/gitlab/import_url_exposer.rb @@ -14,4 +14,4 @@ module Gitlab credentials.values.join(":") end end -end \ No newline at end of file +end diff --git a/spec/factories/project_import_data.rb b/spec/factories/project_import_data.rb new file mode 100644 index 0000000000..18393cdda9 --- /dev/null +++ b/spec/factories/project_import_data.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :project_import_data, class: ProjectImportData do + data "test" + end +end \ No newline at end of file diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index 36abe87f52..290c855642 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do owner: OpenStruct.new(login: "john") ) end - let(:namespace){ create(:group, owner: user) } + let(:namespace) { create(:group, owner: user) } let(:token) { "asdffg" } let(:access_params) { { github_access_token: token } } @@ -27,7 +27,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do project = project_creator.execute expect(project.import_url).to eq("https://gitlab.com/asd/vim.git") - expect(project.import_data.credentials).to eq("asdffg") + expect(project.import_data.credentials).to eq(:github_access_token => "asdffg") expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) end end diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb index aed2aa39e3..46d5c2f329 100644 --- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb @@ -3,10 +3,15 @@ require 'spec_helper' describe Gitlab::GithubImport::WikiFormatter, lib: true do let(:project) do create(:project, namespace: create(:namespace, path: 'gitlabhq'), - import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git') + import_url: 'https://github.com/gitlabhq/sample.gitlabhq.git') end - subject(:wiki) { described_class.new(project)} + let!(:project_import_data) do + create(:project_import_data, credentials: { github_access_token: 'xxx' }, + project: project) + end + + subject(:wiki) { described_class.new(project) } describe '#path_with_namespace' do it 'appends .wiki to project path' do From 735563329d1f86ee4d72b37cd22eed1168935e8e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 7 Mar 2016 12:50:35 +0100 Subject: [PATCH 009/573] refactored a bunch of stuff based on MR feedback --- app/models/project_import_data.rb | 2 +- ...8_remove_wrong_import_url_from_projects.rb | 38 +++++++++++-------- lib/gitlab/github_import/importer.rb | 4 +- lib/gitlab/github_import/project_creator.rb | 3 +- lib/gitlab/import_url_exposer.rb | 14 +++---- spec/lib/gitlab/import_url_exposer_spec.rb | 13 +++++++ 6 files changed, 44 insertions(+), 30 deletions(-) create mode 100644 spec/lib/gitlab/import_url_exposer_spec.rb diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 23f4e97b8a..f3b9daa0d1 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -12,7 +12,7 @@ require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project - attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true + attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true serialize :data, JSON diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index dfa9f2d4de..881af783c6 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -2,43 +2,49 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration class ImportUrlSanitizer def initialize(url) - @url = url + @url = URI.parse(url) end def sanitized_url - @sanitized_url ||= @url[regex_extractor, 1] + @url[regex_extractor, 3] + @sanitized_url ||= safe_url end def credentials - @credentials ||= @url[regex_extractor, 2] + @credentials ||= { user: @url.user, password: @url.password } end private - # Regex matches 1 , 2 , - # 3 - def regex_extractor - /(.*\/\/)(.*)(\@.*)/ + def safe_url + safe_url = @url.dup + safe_url.password = nil + safe_url.user = nil + safe_url end + + end + + class FakeProjectImportData + extend AttrEncrypted + attr_accessor :credentials + attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true end def up - projects_with_wrong_import_url.each do |project_id| - project = Project.find(project_id["id"]) - sanitizer = ImportUrlSanitizer.new(project.import_url) + projects_with_wrong_import_url.each do |project| + sanitizer = ImportUrlSanitizer.new(project["import_url"]) ActiveRecord::Base.transaction do - project.update_columns(import_url: sanitizer.sanitized_url) - if project.import_data - project.import_data.credentials = sanitizer.credentials - project.save! - end + execute("UPDATE projects SET import_url = '#{sanitizer.sanitized_url}' WHERE id = #{project['id']}") + fake_import_data = FakeProjectImportData.new + fake_import_data.credentials = sanitizer.credentials + execute("UPDATE project_import_data SET encrypted_credentials = '#{fake_import_data.encrypted_credentials}' WHERE project_id = #{project['id']}") end end end def projects_with_wrong_import_url # TODO Check live with #operations for possible false positives. Also, consider regex? But may have issues MySQL/PSQL - select_all("SELECT p.id from projects p WHERE p.import_url LIKE '%//%:%@%' or p.import_url like '#{"_"*40}@github.com%'") + select_all("SELECT p.id, p.import_url from projects p WHERE p.import_url LIKE '%//%:%@%' or p.import_url like '#{"_"*40}@github.com%'") end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 515fd4720d..d478d3b539 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -7,8 +7,8 @@ module Gitlab def initialize(project) @project = project - github_session = project.import_data.credentials if import_data - @client = Client.new(github_session["github_access_token"]) + credentials = project.import_data.credentials if import_data + @client = Client.new(credentials["github_access_token"]) @formatter = Gitlab::ImportFormatter.new end diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index b5ed32e5b1..52aba93a51 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -32,8 +32,7 @@ module Gitlab def create_import_data(project) project.create_import_data( - credentials: { github_access_token: session_data.delete(:github_access_token) }, - data: { github_session: session_data }) + credentials: { github_access_token: session_data.delete(:github_access_token) }) end end end diff --git a/lib/gitlab/import_url_exposer.rb b/lib/gitlab/import_url_exposer.rb index f1919dffa8..bf03f5a6da 100644 --- a/lib/gitlab/import_url_exposer.rb +++ b/lib/gitlab/import_url_exposer.rb @@ -2,16 +2,12 @@ module Gitlab # Exposes an import URL that includes the credentials unencrypted. # Extracted to its own class to prevent unintended use. module ImportUrlExposer - extend self - def expose(import_url:, credentials: ) - import_url.sub("//", "//#{parsed_credentials(credentials)}@") - end - - private - - def parsed_credentials(credentials) - credentials.values.join(":") + def self.expose(import_url:, credentials: ) + uri = URI.parse(import_url) + uri.user = credentials[:user] + uri.password = credentials[:password] + uri end end end diff --git a/spec/lib/gitlab/import_url_exposer_spec.rb b/spec/lib/gitlab/import_url_exposer_spec.rb new file mode 100644 index 0000000000..878947caea --- /dev/null +++ b/spec/lib/gitlab/import_url_exposer_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'Gitlab::ImportUrlExposer' do + + describe :expose do + let(:credentials) do + Gitlab::ImportUrlExposer.expose(import_url: "https://github.com/me/project.git", credentials: {user: 'blah', password: 'password'}) + end + + it { expect(credentials).to be_a(URI) } + it { expect(credentials.to_s).to eq("https://blah:password@github.com/me/project.git") } + end +end From 383cc8404741f65bd52fbe80eec6c2dae2578fce Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 13:15:51 +0100 Subject: [PATCH 010/573] some refactoring based on feedback --- app/models/project.rb | 12 +++++ ...8_remove_wrong_import_url_from_projects.rb | 50 +++++++------------ db/schema.rb | 4 +- lib/gitlab/github_import/importer.rb | 2 +- lib/gitlab/github_import/project_creator.rb | 14 +----- lib/gitlab/github_import/wiki_formatter.rb | 4 +- lib/gitlab/import_url_sanitizer.rb | 24 +++++++++ 7 files changed, 61 insertions(+), 49 deletions(-) create mode 100644 lib/gitlab/import_url_sanitizer.rb diff --git a/app/models/project.rb b/app/models/project.rb index 412c6c6732..ab4afd4159 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -404,6 +404,18 @@ class Project < ActiveRecord::Base self.import_data.destroy if self.import_data end + def import_url=(value) + sanitizer = Gitlab::ImportUrlSanitizer.new(value) + self[:import_url] = sanitizer.sanitized_url + create_import_data(credentials: sanitizer.credentials) + end + + def import_url + if import_data + Gitlab::ImportUrlExposer.expose(import_url: self[:import_url], credentials: import_data.credentials) + end + end + def import? external_import? || forked? end diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 881af783c6..f98ec925ac 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -1,29 +1,5 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration - class ImportUrlSanitizer - def initialize(url) - @url = URI.parse(url) - end - - def sanitized_url - @sanitized_url ||= safe_url - end - - def credentials - @credentials ||= { user: @url.user, password: @url.password } - end - - private - - def safe_url - safe_url = @url.dup - safe_url.password = nil - safe_url.user = nil - safe_url - end - - end - class FakeProjectImportData extend AttrEncrypted attr_accessor :credentials @@ -31,20 +7,30 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def up - projects_with_wrong_import_url.each do |project| - sanitizer = ImportUrlSanitizer.new(project["import_url"]) + projects_with_wrong_import_url.find_in_batches do |project_batch| + project_batch.each do |project| + sanitizer = Gitlab::ImportUrlSanitizer.new(project["import_url"]) - ActiveRecord::Base.transaction do - execute("UPDATE projects SET import_url = '#{sanitizer.sanitized_url}' WHERE id = #{project['id']}") - fake_import_data = FakeProjectImportData.new - fake_import_data.credentials = sanitizer.credentials - execute("UPDATE project_import_data SET encrypted_credentials = '#{fake_import_data.encrypted_credentials}' WHERE project_id = #{project['id']}") + ActiveRecord::Base.transaction do + execute("UPDATE projects SET import_url = '#{quote(sanitizer.sanitized_url)}' WHERE id = #{project['id']}") + fake_import_data = FakeProjectImportData.new + fake_import_data.credentials = sanitizer.credentials + execute("UPDATE project_import_data SET encrypted_credentials = '#{quote(fake_import_data.encrypted_credentials)}' WHERE project_id = #{project['id']}") + end end end end + def down + + end + def projects_with_wrong_import_url # TODO Check live with #operations for possible false positives. Also, consider regex? But may have issues MySQL/PSQL - select_all("SELECT p.id, p.import_url from projects p WHERE p.import_url LIKE '%//%:%@%' or p.import_url like '#{"_"*40}@github.com%'") + select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE '#{"_"*40}@github.com%')") + end + + def quote(value) + ActiveRecord::Base.connection.quote(value) end end diff --git a/db/schema.rb b/db/schema.rb index 5b2f5aa3dd..72a3ec2fb1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -416,7 +416,7 @@ ActiveRecord::Schema.define(version: 20160316204731) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.boolean "confidential", default: false + t.boolean "confidential", default: false end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -684,6 +684,8 @@ ActiveRecord::Schema.define(version: 20160316204731) do create_table "project_import_data", force: :cascade do |t| t.integer "project_id" t.text "data" + t.text "encrypted_credentials" + t.text "encrypted_credentials_iv" end create_table "projects", force: :cascade do |t| diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index a96cfa8af9..d407be5dcf 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -8,7 +8,7 @@ module Gitlab def initialize(project) @project = project credentials = project.import_data.credentials if import_data - @client = Client.new(credentials["github_access_token"]) + @client = Client.new(credentials[:user]) @formatter = Gitlab::ImportFormatter.new end diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 52aba93a51..f4221003db 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -11,7 +11,7 @@ module Gitlab end def execute - project = ::Projects::CreateService.new( + ::Projects::CreateService.new( current_user, name: repo.name, path: repo.name, @@ -20,19 +20,9 @@ module Gitlab visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, import_type: "github", import_source: repo.full_name, - import_url: repo.clone_url, + import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"), wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later ).execute - - create_import_data(project) - project - end - - private - - def create_import_data(project) - project.create_import_data( - credentials: { github_access_token: session_data.delete(:github_access_token) }) end end end diff --git a/lib/gitlab/github_import/wiki_formatter.rb b/lib/gitlab/github_import/wiki_formatter.rb index 8be8292410..db2c49a497 100644 --- a/lib/gitlab/github_import/wiki_formatter.rb +++ b/lib/gitlab/github_import/wiki_formatter.rb @@ -12,9 +12,7 @@ module Gitlab end def import_url - import_url = Gitlab::ImportUrlExposer.expose(import_url: project.import_url, - credentials: project.import_data.credentials) - import_url.sub(/\.git\z/, ".wiki.git") + project.import_url.import_url.sub(/\.git\z/, ".wiki.git") end end end diff --git a/lib/gitlab/import_url_sanitizer.rb b/lib/gitlab/import_url_sanitizer.rb new file mode 100644 index 0000000000..dfbc4f8303 --- /dev/null +++ b/lib/gitlab/import_url_sanitizer.rb @@ -0,0 +1,24 @@ +module Gitlab + class ImportUrlSanitizer + def initialize(url) + @url = URI.parse(url) + end + + def sanitized_url + @sanitized_url ||= safe_url.to_s + end + + def credentials + @credentials ||= { user: @url.user, password: @url.password } + end + + private + + def safe_url + safe_url = @url.dup + safe_url.password = nil + safe_url.user = nil + safe_url + end + end +end \ No newline at end of file From 8d7d9c8daa61d58a17fea648771a1bb6c9341304 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 13:23:35 +0100 Subject: [PATCH 011/573] added missing column for current att_encrypted version --- ...160302151724_add_import_credentials_to_project_import_data.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb index ff3d8b466d..dd2b361398 100644 --- a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb +++ b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb @@ -2,5 +2,6 @@ class AddImportCredentialsToProjectImportData < ActiveRecord::Migration def change add_column :project_import_data, :encrypted_credentials, :text add_column :project_import_data, :encrypted_credentials_iv, :text + add_column :project_import_data, :encrypted_credentials_salt, :text end end From 030b13944534be505dc97667ce2094ed6c588f12 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 15:11:05 +0100 Subject: [PATCH 012/573] more refactoring --- app/models/project.rb | 12 ++++++---- ...8_remove_wrong_import_url_from_projects.rb | 23 ++++++++----------- db/schema.rb | 11 ++++----- lib/gitlab/github_import/importer.rb | 13 ++++++++--- lib/gitlab/github_import/wiki_formatter.rb | 2 +- ...{import_url_sanitizer.rb => import_url.rb} | 16 +++++++++++-- lib/gitlab/import_url_exposer.rb | 13 ----------- .../github_import/project_creator_spec.rb | 5 ++-- .../github_import/wiki_formatter_spec.rb | 7 +----- spec/lib/gitlab/import_url_exposer_spec.rb | 13 ----------- spec/lib/gitlab/import_url_spec.rb | 21 +++++++++++++++++ 11 files changed, 72 insertions(+), 64 deletions(-) rename lib/gitlab/{import_url_sanitizer.rb => import_url.rb} (52%) delete mode 100644 lib/gitlab/import_url_exposer.rb delete mode 100644 spec/lib/gitlab/import_url_exposer_spec.rb create mode 100644 spec/lib/gitlab/import_url_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index ab4afd4159..4e5fa8821e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -405,14 +405,17 @@ class Project < ActiveRecord::Base end def import_url=(value) - sanitizer = Gitlab::ImportUrlSanitizer.new(value) - self[:import_url] = sanitizer.sanitized_url - create_import_data(credentials: sanitizer.credentials) + import_url = Gitlab::ImportUrl.new(value) + create_import_data(credentials: import_url.credentials) + super(import_url.sanitized_url) end def import_url if import_data - Gitlab::ImportUrlExposer.expose(import_url: self[:import_url], credentials: import_data.credentials) + import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials) + import_url.full_url + else + super end end @@ -447,6 +450,7 @@ class Project < ActiveRecord::Base def safe_import_url result = URI.parse(self.import_url) result.password = '*****' unless result.password.nil? + result.user = '*****' unless result.user.nil? #tokens or other data may be saved as user result.to_s rescue self.import_url diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index f98ec925ac..fd718ef397 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -1,3 +1,6 @@ +# Loops through old importer projects that kept a token/password in the import URL +# and encrypts the credentials into a separate field in project#import_data +# #down method not supported class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration class FakeProjectImportData @@ -7,24 +10,18 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def up - projects_with_wrong_import_url.find_in_batches do |project_batch| - project_batch.each do |project| - sanitizer = Gitlab::ImportUrlSanitizer.new(project["import_url"]) + projects_with_wrong_import_url do |project| + import_url = Gitlab::ImportUrl.new(project["import_url"]) - ActiveRecord::Base.transaction do - execute("UPDATE projects SET import_url = '#{quote(sanitizer.sanitized_url)}' WHERE id = #{project['id']}") - fake_import_data = FakeProjectImportData.new - fake_import_data.credentials = sanitizer.credentials - execute("UPDATE project_import_data SET encrypted_credentials = '#{quote(fake_import_data.encrypted_credentials)}' WHERE project_id = #{project['id']}") - end + ActiveRecord::Base.transaction do + execute("UPDATE projects SET import_url = '#{quote(import_url.sanitized_url)}' WHERE id = #{project['id']}") + fake_import_data = FakeProjectImportData.new + fake_import_data.credentials = import_url.credentials + execute("UPDATE project_import_data SET encrypted_credentials = '#{quote(fake_import_data.encrypted_credentials)}' WHERE project_id = #{project['id']}") end end end - def down - - end - def projects_with_wrong_import_url # TODO Check live with #operations for possible false positives. Also, consider regex? But may have issues MySQL/PSQL select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE '#{"_"*40}@github.com%')") diff --git a/db/schema.rb b/db/schema.rb index 72a3ec2fb1..02300c028d 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: 20160316204731) do +ActiveRecord::Schema.define(version: 20160302151724) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -260,7 +260,6 @@ ActiveRecord::Schema.define(version: 20160316204731) do end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} - add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| @@ -686,6 +685,7 @@ ActiveRecord::Schema.define(version: 20160316204731) do t.text "data" t.text "encrypted_credentials" t.text "encrypted_credentials_iv" + t.text "encrypted_credentials_salt" end create_table "projects", force: :cascade do |t| @@ -725,7 +725,6 @@ ActiveRecord::Schema.define(version: 20160316204731) do 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 end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -811,6 +810,7 @@ ActiveRecord::Schema.define(version: 20160316204731) do t.string "file_name" t.string "type" t.integer "visibility_level", default: 0, null: false + t.datetime "expires_at" end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -869,7 +869,7 @@ ActiveRecord::Schema.define(version: 20160316204731) do create_table "todos", force: :cascade do |t| t.integer "user_id", null: false t.integer "project_id", null: false - t.integer "target_id" + t.integer "target_id", null: false t.string "target_type", null: false t.integer "author_id" t.integer "action", null: false @@ -877,11 +877,9 @@ ActiveRecord::Schema.define(version: 20160316204731) do t.datetime "created_at" t.datetime "updated_at" t.integer "note_id" - t.string "commit_id" end add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree - add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree add_index "todos", ["state"], name: "index_todos_on_state", using: :btree @@ -946,7 +944,6 @@ ActiveRecord::Schema.define(version: 20160316204731) do t.string "unlock_token" t.datetime "otp_grace_period_started_at" t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index d407be5dcf..0b1ed51022 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -7,9 +7,12 @@ module Gitlab def initialize(project) @project = project - credentials = project.import_data.credentials if import_data - @client = Client.new(credentials[:user]) - @formatter = Gitlab::ImportFormatter.new + if import_data_credentials + @client = Client.new(import_data_credentials[:user]) + @formatter = Gitlab::ImportFormatter.new + else + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + end end def execute @@ -18,6 +21,10 @@ module Gitlab private + def import_data_credentials + @import_data_credentials ||= project.import_data.credentials if project.import_data + end + def import_issues client.list_issues(project.import_source, state: :all, sort: :created, diff --git a/lib/gitlab/github_import/wiki_formatter.rb b/lib/gitlab/github_import/wiki_formatter.rb index db2c49a497..6c592ff469 100644 --- a/lib/gitlab/github_import/wiki_formatter.rb +++ b/lib/gitlab/github_import/wiki_formatter.rb @@ -12,7 +12,7 @@ module Gitlab end def import_url - project.import_url.import_url.sub(/\.git\z/, ".wiki.git") + project.import_url.sub(/\.git\z/, ".wiki.git") end end end diff --git a/lib/gitlab/import_url_sanitizer.rb b/lib/gitlab/import_url.rb similarity index 52% rename from lib/gitlab/import_url_sanitizer.rb rename to lib/gitlab/import_url.rb index dfbc4f8303..7358edac2e 100644 --- a/lib/gitlab/import_url_sanitizer.rb +++ b/lib/gitlab/import_url.rb @@ -1,7 +1,8 @@ module Gitlab - class ImportUrlSanitizer - def initialize(url) + class ImportUrl + def initialize(url, credentials: nil) @url = URI.parse(url) + @credentials = credentials end def sanitized_url @@ -12,8 +13,19 @@ module Gitlab @credentials ||= { user: @url.user, password: @url.password } end + def full_url + @full_url ||= generate_full_url.to_s + end + private + def generate_full_url + @full_url = @url.dup + @full_url.user = @credentials[:user] + @full_url.password = @credentials[:password] + @full_url + end + def safe_url safe_url = @url.dup safe_url.password = nil diff --git a/lib/gitlab/import_url_exposer.rb b/lib/gitlab/import_url_exposer.rb deleted file mode 100644 index bf03f5a6da..0000000000 --- a/lib/gitlab/import_url_exposer.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Gitlab - # Exposes an import URL that includes the credentials unencrypted. - # Extracted to its own class to prevent unintended use. - module ImportUrlExposer - - def self.expose(import_url:, credentials: ) - uri = URI.parse(import_url) - uri.user = credentials[:user] - uri.password = credentials[:password] - uri - end - end -end diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index 290c855642..2092c1b958 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -26,8 +26,9 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user, access_params) project = project_creator.execute - expect(project.import_url).to eq("https://gitlab.com/asd/vim.git") - expect(project.import_data.credentials).to eq(:github_access_token => "asdffg") + expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") + expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git") + expect(project.import_data.credentials).to eq(:user => "asdffg", :password => nil) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) end end diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb index 46d5c2f329..91cd370987 100644 --- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb @@ -3,12 +3,7 @@ require 'spec_helper' describe Gitlab::GithubImport::WikiFormatter, lib: true do let(:project) do create(:project, namespace: create(:namespace, path: 'gitlabhq'), - import_url: 'https://github.com/gitlabhq/sample.gitlabhq.git') - end - - let!(:project_import_data) do - create(:project_import_data, credentials: { github_access_token: 'xxx' }, - project: project) + import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git') end subject(:wiki) { described_class.new(project) } diff --git a/spec/lib/gitlab/import_url_exposer_spec.rb b/spec/lib/gitlab/import_url_exposer_spec.rb deleted file mode 100644 index 878947caea..0000000000 --- a/spec/lib/gitlab/import_url_exposer_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe 'Gitlab::ImportUrlExposer' do - - describe :expose do - let(:credentials) do - Gitlab::ImportUrlExposer.expose(import_url: "https://github.com/me/project.git", credentials: {user: 'blah', password: 'password'}) - end - - it { expect(credentials).to be_a(URI) } - it { expect(credentials.to_s).to eq("https://blah:password@github.com/me/project.git") } - end -end diff --git a/spec/lib/gitlab/import_url_spec.rb b/spec/lib/gitlab/import_url_spec.rb new file mode 100644 index 0000000000..f758cb8693 --- /dev/null +++ b/spec/lib/gitlab/import_url_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::ImportUrl do + + let(:credentials) { { user: 'blah', password: 'password' } } + let(:import_url) do + Gitlab::ImportUrl.new("https://github.com/me/project.git", credentials: credentials) + end + + describe :full_url do + it { expect(import_url.full_url).to eq("https://blah:password@github.com/me/project.git") } + end + + describe :sanitized_url do + it { expect(import_url.sanitized_url).to eq("https://github.com/me/project.git") } + end + + describe :credentials do + it { expect(import_url.credentials).to eq(credentials) } + end +end From bd8a77674f7226aafa4bd9befa8ed7482c372dc1 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 17:16:27 +0100 Subject: [PATCH 013/573] fixed few issues with the migration --- app/models/project.rb | 3 ++- app/models/project_import_data.rb | 2 +- ...8_remove_wrong_import_url_from_projects.rb | 26 ++++++++++++++++--- lib/gitlab/import_url.rb | 1 + 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 4e5fa8821e..242ad19b11 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -406,6 +406,7 @@ class Project < ActiveRecord::Base def import_url=(value) import_url = Gitlab::ImportUrl.new(value) + # deletes any existing import_data create_import_data(credentials: import_url.credentials) super(import_url.sanitized_url) end @@ -450,7 +451,7 @@ class Project < ActiveRecord::Base def safe_import_url result = URI.parse(self.import_url) result.password = '*****' unless result.password.nil? - result.user = '*****' unless result.user.nil? #tokens or other data may be saved as user + result.user = '*****' unless result.user.nil? || result.user == "git" #tokens or other data may be saved as user result.to_s rescue self.import_url diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index f3b9daa0d1..420c01f996 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -12,7 +12,7 @@ require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project - attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true + attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt serialize :data, JSON diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index fd718ef397..0f7da3103b 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -6,25 +6,43 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration class FakeProjectImportData extend AttrEncrypted attr_accessor :credentials - attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true + attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt end def up + byebug projects_with_wrong_import_url do |project| import_url = Gitlab::ImportUrl.new(project["import_url"]) ActiveRecord::Base.transaction do - execute("UPDATE projects SET import_url = '#{quote(import_url.sanitized_url)}' WHERE id = #{project['id']}") + execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}") fake_import_data = FakeProjectImportData.new fake_import_data.credentials = import_url.credentials - execute("UPDATE project_import_data SET encrypted_credentials = '#{quote(fake_import_data.encrypted_credentials)}' WHERE project_id = #{project['id']}") + project_import_data = project_import_data(project['id']) + if project_import_data + execute(update_import_data_sql(project_import_data['id'], fake_import_data)) + else + execute(insert_import_data_sql(project['id'], fake_import_data)) + end end end end + def insert_import_data_sql(project_id, fake_import_data) + %( INSERT into project_import_data (encrypted_credentials, project_id, encrypted_credentials_iv, encrypted_credentials_salt) VALUES ( #{quote(fake_import_data.encrypted_credentials)}, '#{project_id}', #{quote(fake_import_data.encrypted_credentials_iv)}, #{quote(fake_import_data.encrypted_credentials_salt)})) + end + + def update_import_data_sql(id, fake_import_data) + %( UPDATE project_import_data SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)}, encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)}, encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)} WHERE id = '#{id}') + end + def projects_with_wrong_import_url # TODO Check live with #operations for possible false positives. Also, consider regex? But may have issues MySQL/PSQL - select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE '#{"_"*40}@github.com%')") + select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE 'https___#{"_"*40}@github.com%')") + end + + def project_import_data(project_id) + select_one("SELECT id FROM project_import_data WHERE project_id = '#{project_id}'") end def quote(value) diff --git a/lib/gitlab/import_url.rb b/lib/gitlab/import_url.rb index 7358edac2e..aa43092025 100644 --- a/lib/gitlab/import_url.rb +++ b/lib/gitlab/import_url.rb @@ -20,6 +20,7 @@ module Gitlab private def generate_full_url + return @url unless @credentials @full_url = @url.dup @full_url.user = @credentials[:user] @full_url.password = @credentials[:password] From dff4050f1d3f00815c095ec2645bd935f14e51a7 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 17:29:19 +0100 Subject: [PATCH 014/573] fixed some rubocop warnings --- app/models/project_import_data.rb | 2 +- lib/gitlab/import_url.rb | 2 +- spec/factories/project_import_data.rb | 2 +- spec/lib/gitlab/github_import/project_creator_spec.rb | 2 +- spec/lib/gitlab/github_import/wiki_formatter_spec.rb | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 420c01f996..ba4334055d 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -12,7 +12,7 @@ require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project - attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt + attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, mode: :per_attribute_iv_and_salt serialize :data, JSON diff --git a/lib/gitlab/import_url.rb b/lib/gitlab/import_url.rb index aa43092025..adcbcc43d2 100644 --- a/lib/gitlab/import_url.rb +++ b/lib/gitlab/import_url.rb @@ -34,4 +34,4 @@ module Gitlab safe_url end end -end \ No newline at end of file +end diff --git a/spec/factories/project_import_data.rb b/spec/factories/project_import_data.rb index 18393cdda9..a799af9996 100644 --- a/spec/factories/project_import_data.rb +++ b/spec/factories/project_import_data.rb @@ -2,4 +2,4 @@ FactoryGirl.define do factory :project_import_data, class: ProjectImportData do data "test" end -end \ No newline at end of file +end diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index 2092c1b958..0f363b8b0a 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git") - expect(project.import_data.credentials).to eq(:user => "asdffg", :password => nil) + expect(project.import_data.credentials).to eq(user: "asdffg", password: nil) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) end end diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb index 91cd370987..1bd29b8a56 100644 --- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe Gitlab::GithubImport::WikiFormatter, lib: true do let(:project) do - create(:project, namespace: create(:namespace, path: 'gitlabhq'), + create(:project, + namespace: create(:namespace, path: 'gitlabhq'), import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git') end From 6dfb5d7cad3e3cefd766e30a4c25a9549fa2b041 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 17:34:20 +0100 Subject: [PATCH 015/573] remove byebug --- .../20160302152808_remove_wrong_import_url_from_projects.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 0f7da3103b..6b4b6726fc 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -10,7 +10,6 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def up - byebug projects_with_wrong_import_url do |project| import_url = Gitlab::ImportUrl.new(project["import_url"]) From ced56641bfc42c8380af1760fae93ba37bf2e785 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 18:09:47 +0100 Subject: [PATCH 016/573] refactored code based on feedback --- app/models/project.rb | 10 ++++++++-- lib/gitlab/import_url.rb | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 242ad19b11..a1aa1d5fdb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -406,8 +406,7 @@ class Project < ActiveRecord::Base def import_url=(value) import_url = Gitlab::ImportUrl.new(value) - # deletes any existing import_data - create_import_data(credentials: import_url.credentials) + create_or_update_import_data(import_url.credentials) super(import_url.sanitized_url) end @@ -420,6 +419,13 @@ class Project < ActiveRecord::Base end end + def create_or_update_import_data(credentials) + project_import_data = import_data || ProjectImportData.new + project_import_data.credentials = credentials + project_import_data.project_id = id + project_import_data.save + end + def import? external_import? || forked? end diff --git a/lib/gitlab/import_url.rb b/lib/gitlab/import_url.rb index adcbcc43d2..5b18d67ddc 100644 --- a/lib/gitlab/import_url.rb +++ b/lib/gitlab/import_url.rb @@ -20,10 +20,10 @@ module Gitlab private def generate_full_url - return @url unless @credentials + return @url unless credentials @full_url = @url.dup - @full_url.user = @credentials[:user] - @full_url.password = @credentials[:password] + @full_url.user = credentials[:user] + @full_url.password = credentials[:password] @full_url end From 19bfdcf4689e9c994f5a630da9cff75e5baa0e55 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 18:20:25 +0100 Subject: [PATCH 017/573] fix import data creation --- app/models/project.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index a1aa1d5fdb..2a530715a9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -420,9 +420,8 @@ class Project < ActiveRecord::Base end def create_or_update_import_data(credentials) - project_import_data = import_data || ProjectImportData.new + project_import_data = import_data || build_import_data project_import_data.credentials = credentials - project_import_data.project_id = id project_import_data.save end From 868e4918f93021430162ec097d3362f313a4bb80 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 21 Mar 2016 18:38:06 +0100 Subject: [PATCH 018/573] updated migration --- ...0302152808_remove_wrong_import_url_from_projects.rb | 3 +-- db/schema.rb | 10 +++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 6b4b6726fc..cf6b983323 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -36,8 +36,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def projects_with_wrong_import_url - # TODO Check live with #operations for possible false positives. Also, consider regex? But may have issues MySQL/PSQL - select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE 'https___#{"_"*40}@github.com%')") + select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND p.import_type = 'github' AND p.import_url LIKE '%//%:%@%'") end def project_import_data(project_id) diff --git a/db/schema.rb b/db/schema.rb index 02300c028d..71f1e1e496 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: 20160302151724) do +ActiveRecord::Schema.define(version: 20160316204731) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -260,6 +260,7 @@ ActiveRecord::Schema.define(version: 20160302151724) do end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| @@ -725,6 +726,7 @@ ActiveRecord::Schema.define(version: 20160302151724) do 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 end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -810,7 +812,6 @@ ActiveRecord::Schema.define(version: 20160302151724) do t.string "file_name" t.string "type" t.integer "visibility_level", default: 0, null: false - t.datetime "expires_at" end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -869,7 +870,7 @@ ActiveRecord::Schema.define(version: 20160302151724) do create_table "todos", force: :cascade do |t| t.integer "user_id", null: false t.integer "project_id", null: false - t.integer "target_id", null: false + t.integer "target_id" t.string "target_type", null: false t.integer "author_id" t.integer "action", null: false @@ -877,9 +878,11 @@ ActiveRecord::Schema.define(version: 20160302151724) do t.datetime "created_at" t.datetime "updated_at" t.integer "note_id" + t.string "commit_id" end add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree + add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree add_index "todos", ["state"], name: "index_todos_on_state", using: :btree @@ -944,6 +947,7 @@ ActiveRecord::Schema.define(version: 20160302151724) do t.string "unlock_token" t.datetime "otp_grace_period_started_at" t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree From 1b8d995492baca8984bde950e0449dad6342befc Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 22 Mar 2016 11:32:15 +0100 Subject: [PATCH 019/573] refactoring migration to add bitbucket stuff --- ...8_remove_wrong_import_url_from_projects.rb | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index cf6b983323..8ba68e61c7 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -10,23 +10,52 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def up + process_projects_with_wrong_url + process_bitbucket_projects + end + + def process_projects_with_wrong_url projects_with_wrong_import_url do |project| import_url = Gitlab::ImportUrl.new(project["import_url"]) ActiveRecord::Base.transaction do - execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}") - fake_import_data = FakeProjectImportData.new - fake_import_data.credentials = import_url.credentials - project_import_data = project_import_data(project['id']) - if project_import_data - execute(update_import_data_sql(project_import_data['id'], fake_import_data)) - else - execute(insert_import_data_sql(project['id'], fake_import_data)) - end + update_import_url(import_url, project) + update_import_data(import_url, project) end end end + def process_bitbucket_projects + bitbucket_projects_with_wrong_import_url do |bitbucket_data| + data = bitbucket_data['data'] + data_hash = YAML::load(data) + if data_hash && data_hash['bb_session'] + update_import_data_for_bitbucket(data_hash, bitbucket_data['id']) + end + end + end + + def update_import_data(import_url, project) + fake_import_data = FakeProjectImportData.new + fake_import_data.credentials = import_url.credentials + project_import_data = project_import_data(project['id']) + if project_import_data + execute(update_import_data_sql(project_import_data['id'], fake_import_data)) + else + execute(insert_import_data_sql(project['id'], fake_import_data)) + end + end + + def update_import_data_for_bitbucket(data_hash, import_data_id) + fake_import_data = FakeProjectImportData.new + fake_import_data.credentials = data_hash + execute(update_import_data_sql(import_data_id, fake_import_data)) + end + + def update_import_url(import_url, project) + execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}") + end + def insert_import_data_sql(project_id, fake_import_data) %( INSERT into project_import_data (encrypted_credentials, project_id, encrypted_credentials_iv, encrypted_credentials_salt) VALUES ( #{quote(fake_import_data.encrypted_credentials)}, '#{project_id}', #{quote(fake_import_data.encrypted_credentials_iv)}, #{quote(fake_import_data.encrypted_credentials_salt)})) end @@ -35,8 +64,14 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration %( UPDATE project_import_data SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)}, encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)}, encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)} WHERE id = '#{id}') end + #Github projects with token, and any user:password@ based URL def projects_with_wrong_import_url - select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND p.import_type = 'github' AND p.import_url LIKE '%//%:%@%'") + select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE 'https___#{"_"*40}@github.com%')") + end + + # All bitbucket imports + def bitbucket_projects_with_wrong_import_url + select_all("SELECT p.id, p.import_urlselect_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = 'bitbucket' "), i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = 'bitbucket' ") end def project_import_data(project_id) From 23146fca1878c9f6e90deed6856d3da2c731d513 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 22 Mar 2016 12:25:28 +0100 Subject: [PATCH 020/573] update bitbucket importer --- ...808_remove_wrong_import_url_from_projects.rb | 2 +- lib/gitlab/bitbucket_import/importer.rb | 17 ++++++++++++----- lib/gitlab/github_import/importer.rb | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 8ba68e61c7..4ca245035d 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -71,7 +71,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration # All bitbucket imports def bitbucket_projects_with_wrong_import_url - select_all("SELECT p.id, p.import_urlselect_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = 'bitbucket' "), i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = 'bitbucket' ") + select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = 'bitbucket' ") end def project_import_data(project_id) diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 46e51a4bf6..36110962e0 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -5,11 +5,14 @@ module Gitlab def initialize(project) @project = project - import_data = project.import_data.try(:data) - bb_session = import_data["bb_session"] if import_data - @client = Client.new(bb_session["bitbucket_access_token"], - bb_session["bitbucket_access_token_secret"]) - @formatter = Gitlab::ImportFormatter.new + if import_data_credentials && import_data_credentials['bb_session'] + token = import_data_credentials['bb_session']['bitbucket_access_token'] + token_secret = import_data_credentials['bb_session']['bitbucket_access_token_secret'] + @client = Client.new(token, token_secret) + @formatter = Gitlab::ImportFormatter.new + else + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + end end def execute @@ -24,6 +27,10 @@ module Gitlab private + def import_data_credentials + @import_data_credentials ||= project.import_data.credentials if project.import_data + end + def gl_user_id(project, bitbucket_id) if bitbucket_id user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 0b1ed51022..a5d3ab5fcf 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -8,7 +8,7 @@ module Gitlab def initialize(project) @project = project if import_data_credentials - @client = Client.new(import_data_credentials[:user]) + @client = Client.new(import_data_credentials['user']) @formatter = Gitlab::ImportFormatter.new else raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" From 3b78885eac025b5cf90e1a370b7bbbbd88cc9727 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 22 Mar 2016 12:26:50 +0100 Subject: [PATCH 021/573] encrypt credentials in project_creator for bitbucket by default --- lib/gitlab/bitbucket_import/project_creator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index 03aac1a025..c9ccecef0c 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -23,7 +23,7 @@ module Gitlab import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", ).execute - project.create_import_data(data: { "bb_session" => session_data } ) + project.create_import_data(credentials: { "bb_session" => session_data } ) project end end From b372968e936a8b02f8d7ff73a1e1cc25b332406c Mon Sep 17 00:00:00 2001 From: Baldinof Date: Tue, 22 Mar 2016 10:56:44 +0100 Subject: [PATCH 022/573] Add number sign on external issue reference text --- app/models/external_issue.rb | 8 ++++++++ spec/models/external_issue_spec.rb | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index 2ca79df0a2..92f1f17494 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -34,7 +34,15 @@ class ExternalIssue %r{(?\b([A-Z][A-Z0-9_]+-)\d+)} end + def self.reference_prefix + '#' + end + def to_reference(_from_project = nil) id end + + def reference_link_text(from_project = nil) + "#{self.class.reference_prefix}#{id}" + end end diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index 9b144dd1ec..b3d0c4efe9 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -36,4 +36,10 @@ describe ExternalIssue, models: true do expect(issue.title).to eq "External Issue #{issue}" end end + + describe '#reference_link_text' do + it 'returns a String reference to the object' do + expect(issue.reference_link_text).to eq '#EXT-1234' + end + end end From 8aafe685837d12b623f70eec86cae6e7cef9a849 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 22 Mar 2016 17:53:53 +0100 Subject: [PATCH 023/573] first round of fixes and spec fixes --- app/models/project.rb | 5 ++-- lib/gitlab/bitbucket_import/importer.rb | 19 +------------ lib/gitlab/bitbucket_import/importer_init.rb | 27 +++++++++++++++++++ lib/gitlab/bitbucket_import/key_deleter.rb | 13 ++------- .../bitbucket_import/project_creator.rb | 4 +-- .../gitlab/bitbucket_import/importer_spec.rb | 8 +++--- 6 files changed, 39 insertions(+), 37 deletions(-) create mode 100644 lib/gitlab/bitbucket_import/importer_init.rb diff --git a/app/models/project.rb b/app/models/project.rb index c4287d314e..b464356326 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -407,7 +407,7 @@ class Project < ActiveRecord::Base end def import_url - if import_data + if import_data && super import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials) import_url.full_url else @@ -417,7 +417,8 @@ class Project < ActiveRecord::Base def create_or_update_import_data(credentials) project_import_data = import_data || build_import_data - project_import_data.credentials = credentials + project_import_data.credentials ||= {} + project_import_data.credentials.merge!(credentials) project_import_data.save end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 36110962e0..f80410641a 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -1,19 +1,6 @@ module Gitlab module BitbucketImport - class Importer - attr_reader :project, :client - - def initialize(project) - @project = project - if import_data_credentials && import_data_credentials['bb_session'] - token = import_data_credentials['bb_session']['bitbucket_access_token'] - token_secret = import_data_credentials['bb_session']['bitbucket_access_token_secret'] - @client = Client.new(token, token_secret) - @formatter = Gitlab::ImportFormatter.new - else - raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" - end - end + class Importer < ImporterInit def execute import_issues if has_issues? @@ -27,10 +14,6 @@ module Gitlab private - def import_data_credentials - @import_data_credentials ||= project.import_data.credentials if project.import_data - end - def gl_user_id(project, bitbucket_id) if bitbucket_id user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) diff --git a/lib/gitlab/bitbucket_import/importer_init.rb b/lib/gitlab/bitbucket_import/importer_init.rb new file mode 100644 index 0000000000..6d81811d36 --- /dev/null +++ b/lib/gitlab/bitbucket_import/importer_init.rb @@ -0,0 +1,27 @@ +module Gitlab + module BitbucketImport + class ImporterInit + attr_reader :project, :client + + def initialize(project) + @project = project + if import_data_credentials && import_data_credentials['bb_session'] + token = import_data_credentials['bb_session']['bitbucket_access_token'] + token_secret = import_data_credentials['bb_session']['bitbucket_access_token_secret'] + @client = Client.new(token, token_secret) + @formatter = Gitlab::ImportFormatter.new + else + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + end + end + + private + + def import_data_credentials + @import_data_credentials ||= project.import_data.credentials if project.import_data + end + end + end +end + + diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb index f4dd393ad2..312ed58150 100644 --- a/lib/gitlab/bitbucket_import/key_deleter.rb +++ b/lib/gitlab/bitbucket_import/key_deleter.rb @@ -1,16 +1,7 @@ module Gitlab module BitbucketImport - class KeyDeleter - attr_reader :project, :current_user, :client - - def initialize(project) - @project = project - @current_user = project.creator - import_data = project.import_data.try(:data) - bb_session = import_data["bb_session"] if import_data - @client = Client.new(bb_session["bitbucket_access_token"], - bb_session["bitbucket_access_token_secret"]) - end + class KeyDeleter < ImporterInit + attr_reader :current_user def execute return false unless BitbucketImport.public_key.present? diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index c9ccecef0c..0e6f66de32 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -22,8 +22,8 @@ module Gitlab import_source: "#{repo["owner"]}/#{repo["slug"]}", import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", ).execute - - project.create_import_data(credentials: { "bb_session" => session_data } ) + import_data = project.import_data + import_data.credentials.merge!("bb_session" => session_data) project end end diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index c413132abe..1a833f255a 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -34,9 +34,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do let(:project_identifier) { 'namespace/repo' } let(:data) do { - bb_session: { - bitbucket_access_token: "123456", - bitbucket_access_token_secret: "secret" + 'bb_session' => { + 'bitbucket_access_token' => "123456", + 'bitbucket_access_token_secret' => "secret" } } end @@ -44,7 +44,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do create( :project, import_source: project_identifier, - import_data: ProjectImportData.new(data: data) + import_data: ProjectImportData.new(credentials: data) ) end let(:importer) { Gitlab::BitbucketImport::Importer.new(project) } From c136edbbe343cbb54f135928db5d901c1cb65c4f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 22 Mar 2016 18:03:54 +0100 Subject: [PATCH 024/573] fix gitlab import and spec --- lib/gitlab/gitlab_import/importer.rb | 11 +++++++---- lib/gitlab/gitlab_import/project_creator.rb | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index 850b73244c..afc06d01eb 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -5,10 +5,13 @@ module Gitlab def initialize(project) @project = project - import_data = project.import_data.try(:data) - gitlab_session = import_data["gitlab_session"] if import_data - @client = Client.new(gitlab_session["gitlab_access_token"]) - @formatter = Gitlab::ImportFormatter.new + credentials = import_data.credentials + if credentials && credentials["password"] + @client = Client.new(credentials["password"]) + @formatter = Gitlab::ImportFormatter.new + else + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + end end def execute diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb index 7baaadb813..77c33db4b5 100644 --- a/lib/gitlab/gitlab_import/project_creator.rb +++ b/lib/gitlab/gitlab_import/project_creator.rb @@ -23,7 +23,6 @@ module Gitlab import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@") ).execute - project.create_import_data(data: { "gitlab_session" => session_data } ) project end end From 46346caf5ba5af7ad9af6913e479f301a3ff2d2d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 22 Mar 2016 18:14:13 +0100 Subject: [PATCH 025/573] fix rubocop warning --- lib/gitlab/bitbucket_import/importer_init.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/gitlab/bitbucket_import/importer_init.rb b/lib/gitlab/bitbucket_import/importer_init.rb index 6d81811d36..08b710003e 100644 --- a/lib/gitlab/bitbucket_import/importer_init.rb +++ b/lib/gitlab/bitbucket_import/importer_init.rb @@ -23,5 +23,3 @@ module Gitlab end end end - - From 8da04f6b64884c642e8e4b630a30e6400e47b09e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 23 Mar 2016 11:55:43 +0100 Subject: [PATCH 026/573] refactored migration a bit and fixed a few problems. Also added some logging --- ...8_remove_wrong_import_url_from_projects.rb | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 4ca245035d..3d97a66c0f 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -10,31 +10,41 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def up - process_projects_with_wrong_url - process_bitbucket_projects + say("Encrypting and migrating project import credentials...") + + say("Projects and Github projects with a wrong URL") + in_transaction { process_projects_with_wrong_url } + + say("Migrating bitbucket credentials...") + in_transaction { process_bitbucket_projects } end def process_projects_with_wrong_url - projects_with_wrong_import_url do |project| + projects_with_wrong_import_url.each do |project| import_url = Gitlab::ImportUrl.new(project["import_url"]) - ActiveRecord::Base.transaction do - update_import_url(import_url, project) - update_import_data(import_url, project) - end + update_import_url(import_url, project) + update_import_data(import_url, project) end end def process_bitbucket_projects - bitbucket_projects_with_wrong_import_url do |bitbucket_data| - data = bitbucket_data['data'] - data_hash = YAML::load(data) - if data_hash && data_hash['bb_session'] + bitbucket_projects_with_wrong_import_url.each do |bitbucket_data| + data_hash = YAML::load(bitbucket_data['data']) if bitbucket_data['data'] + if defined?(data_hash) && data_hash && data_hash['bb_session'] update_import_data_for_bitbucket(data_hash, bitbucket_data['id']) end end end + def in_transaction + say_with_time("Processing new transaction...") do + ActiveRecord::Base.transaction do + yield + end + end + end + def update_import_data(import_url, project) fake_import_data = FakeProjectImportData.new fake_import_data.credentials = import_url.credentials @@ -60,8 +70,8 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration %( INSERT into project_import_data (encrypted_credentials, project_id, encrypted_credentials_iv, encrypted_credentials_salt) VALUES ( #{quote(fake_import_data.encrypted_credentials)}, '#{project_id}', #{quote(fake_import_data.encrypted_credentials_iv)}, #{quote(fake_import_data.encrypted_credentials_salt)})) end - def update_import_data_sql(id, fake_import_data) - %( UPDATE project_import_data SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)}, encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)}, encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)} WHERE id = '#{id}') + def update_import_data_sql(id, fake_import_data, data = 'NULL') + %( UPDATE project_import_data SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)}, encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)}, encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)}, data = #{data} WHERE id = '#{id}') end #Github projects with token, and any user:password@ based URL From 6967871fc567cbadd63f26f1ef87c4008cc6387b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 23 Mar 2016 12:52:50 +0100 Subject: [PATCH 027/573] fogbugz importer, also refactored migration again to make it easier to add new importers --- ...8_remove_wrong_import_url_from_projects.rb | 31 ++++++++++++------- lib/gitlab/fogbugz_import/importer.rb | 13 ++++++-- lib/gitlab/fogbugz_import/project_creator.rb | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 3d97a66c0f..f3a4fc26be 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -12,11 +12,14 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration def up say("Encrypting and migrating project import credentials...") - say("Projects and Github projects with a wrong URL") + say("Projects and Github projects with a wrong URL. Also, migrating Gitlab projects credentials.") in_transaction { process_projects_with_wrong_url } say("Migrating bitbucket credentials...") - in_transaction { process_bitbucket_projects } + in_transaction { process_project(import_type: 'bitbucket') } + + say("Migrating fogbugz credentials...") + in_transaction { process_project(import_type: 'fogbugz') } end def process_projects_with_wrong_url @@ -28,12 +31,16 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end end - def process_bitbucket_projects - bitbucket_projects_with_wrong_import_url.each do |bitbucket_data| - data_hash = YAML::load(bitbucket_data['data']) if bitbucket_data['data'] - if defined?(data_hash) && data_hash && data_hash['bb_session'] - update_import_data_for_bitbucket(data_hash, bitbucket_data['id']) - end + def process_project(import_type: ) + unencrypted_import_data(import_type: import_type).each do |data| + replace_data_credentials(data) + end + end + + def replace_data_credentials(data) + data_hash = YAML::load(data['data']) if data['data'] + if defined?(data_hash) && data_hash + update_with_encrypted_data(data_hash, data['id']) end end @@ -56,7 +63,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end end - def update_import_data_for_bitbucket(data_hash, import_data_id) + def update_with_encrypted_data(data_hash, import_data_id) fake_import_data = FakeProjectImportData.new fake_import_data.credentials = data_hash execute(update_import_data_sql(import_data_id, fake_import_data)) @@ -79,9 +86,9 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE 'https___#{"_"*40}@github.com%')") end - # All bitbucket imports - def bitbucket_projects_with_wrong_import_url - select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = 'bitbucket' ") + # All imports with data for import_type + def unencrypted_import_data(import_type: ) + select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = '#{import_type}' ") end def project_import_data(project_id) diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index db580b5e57..c33b3541dd 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -8,9 +8,12 @@ module Gitlab import_data = project.import_data.try(:data) repo_data = import_data['repo'] if import_data - @repo = FogbugzImport::Repository.new(repo_data) - - @known_labels = Set.new + if import_data_credentials && import_data_credentials['repo'] + @repo = FogbugzImport::Repository.new(repo_data) + @known_labels = Set.new + else + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + end end def execute @@ -30,6 +33,10 @@ module Gitlab private + def import_data_credentials + @import_data_credentials ||= project.import_data.credentials if project.import_data + end + def user_map @user_map ||= begin user_map = Hash.new diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index e0163499e3..73d6272720 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -25,7 +25,7 @@ module Gitlab ).execute project.create_import_data( - data: { + credentials: { 'repo' => repo.raw_data, 'user_map' => user_map, 'fb_session' => fb_session From cc4d04f97f891479c4d033196c6868e19528c51c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 23 Mar 2016 17:57:10 +0100 Subject: [PATCH 028/573] added rest of importers, fixed specs and some issues with the migration --- ...8_remove_wrong_import_url_from_projects.rb | 10 +++++++--- db/schema.rb | 4 ++-- lib/gitlab/google_code_import/importer.rb | 20 +++++++++++-------- .../google_code_import/project_creator.rb | 2 +- .../google_code_import/importer_spec.rb | 2 +- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index f3a4fc26be..a55ab6a42b 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -12,7 +12,8 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration def up say("Encrypting and migrating project import credentials...") - say("Projects and Github projects with a wrong URL. Also, migrating Gitlab projects credentials.") + # This should cover Github, Gitlab, Bitbucket user:password, token@domain, and other similar URLs. + say("Projects and Github projects with a wrong URL. It also migrates Gitlab project credentials.") in_transaction { process_projects_with_wrong_url } say("Migrating bitbucket credentials...") @@ -20,6 +21,9 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration say("Migrating fogbugz credentials...") in_transaction { process_project(import_type: 'fogbugz') } + + say("Migrating google code credentials...") + in_transaction { process_project(import_type: 'google_code') } end def process_projects_with_wrong_url @@ -39,7 +43,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration def replace_data_credentials(data) data_hash = YAML::load(data['data']) if data['data'] - if defined?(data_hash) && data_hash + if defined?(data_hash) && !data_hash.blank? update_with_encrypted_data(data_hash, data['id']) end end @@ -83,7 +87,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration #Github projects with token, and any user:password@ based URL def projects_with_wrong_import_url - select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND (p.import_url LIKE '%//%:%@%' OR p.import_url LIKE 'https___#{"_"*40}@github.com%')") + select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND p.import_url LIKE '%//%@%'") end # All imports with data for import_type diff --git a/db/schema.rb b/db/schema.rb index 53d12aa35d..75509c35b0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -417,9 +417,9 @@ ActiveRecord::Schema.define(version: 20160320204112) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.integer "moved_to_id" - t.boolean "confidential", default: false + t.boolean "confidential", default: false t.datetime "deleted_at" + t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 62da327931..6b0715d149 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -6,12 +6,13 @@ module Gitlab def initialize(project) @project = project - import_data = project.import_data.try(:data) - repo_data = import_data["repo"] if import_data - @repo = GoogleCodeImport::Repository.new(repo_data) - - @closed_statuses = [] - @known_labels = Set.new + if import_data_credentials && import_data_credentials['repo'] + @repo = GoogleCodeImport::Repository.new(import_data_credentials['repo']) + @closed_statuses = [] + @known_labels = Set.new + else + raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" + end end def execute @@ -28,6 +29,10 @@ module Gitlab private + def import_data_credentials + @import_data_credentials ||= project.import_data.credentials if project.import_data + end + def user_map @user_map ||= begin user_map = Hash.new do |hash, user| @@ -35,8 +40,7 @@ module Gitlab Client.mask_email(user).sub("...", "\\.\\.\\.") end - import_data = project.import_data.try(:data) - stored_user_map = import_data["user_map"] if import_data + stored_user_map = import_data_credentials["user_map"] user_map.update(stored_user_map) if stored_user_map user_map diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb index 87821c2346..acd3a832d5 100644 --- a/lib/gitlab/google_code_import/project_creator.rb +++ b/lib/gitlab/google_code_import/project_creator.rb @@ -25,7 +25,7 @@ module Gitlab ).execute project.create_import_data( - data: { + credentials: { "repo" => repo.raw_data, "user_map" => user_map } diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 647631271e..6ecf3d7182 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do subject { described_class.new(project) } before do - project.create_import_data(data: import_data) + project.create_import_data(credentials: import_data) end describe "#execute" do From 459ad34493c57b40fd431b18750fef85884d51e1 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 28 Mar 2016 16:35:03 +0200 Subject: [PATCH 029/573] refactored code based on feedback plus fixed a couple of other issues --- ...8_remove_wrong_import_url_from_projects.rb | 66 ++++++++++++------- lib/gitlab/bitbucket_import/client.rb | 11 ++++ lib/gitlab/bitbucket_import/importer.rb | 9 ++- lib/gitlab/bitbucket_import/importer_init.rb | 25 ------- lib/gitlab/bitbucket_import/key_deleter.rb | 10 ++- lib/gitlab/google_code_import/importer.rb | 20 +++--- .../google_code_import/project_creator.rb | 2 +- .../google_code_import/importer_spec.rb | 2 +- 8 files changed, 81 insertions(+), 64 deletions(-) delete mode 100644 lib/gitlab/bitbucket_import/importer_init.rb diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index a55ab6a42b..fb5d8591c0 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -16,14 +16,12 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration say("Projects and Github projects with a wrong URL. It also migrates Gitlab project credentials.") in_transaction { process_projects_with_wrong_url } - say("Migrating bitbucket credentials...") - in_transaction { process_project(import_type: 'bitbucket') } + say("Migrating bitbucket credentials...")# TODO remove last param + in_transaction { process_project(import_type: 'bitbucket', unencrypted_data: ['repo', 'user_map']) } say("Migrating fogbugz credentials...") - in_transaction { process_project(import_type: 'fogbugz') } + in_transaction { process_project(import_type: 'fogbugz', unencrypted_data: ['repo', 'user_map']) } - say("Migrating google code credentials...") - in_transaction { process_project(import_type: 'google_code') } end def process_projects_with_wrong_url @@ -35,19 +33,29 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end end - def process_project(import_type: ) + def process_project(import_type: , unencrypted_data: []) unencrypted_import_data(import_type: import_type).each do |data| - replace_data_credentials(data) + replace_data_credentials(data, unencrypted_data) end end - def replace_data_credentials(data) - data_hash = YAML::load(data['data']) if data['data'] + def replace_data_credentials(data, unencrypted_data) + data_hash = JSON.load(data['data']) if data['data'] if defined?(data_hash) && !data_hash.blank? - update_with_encrypted_data(data_hash, data['id']) + unencrypted_data_hash = encrypted_data_hash(data_hash, unencrypted_data) + update_with_encrypted_data(data_hash, data['id'], unencrypted_data_hash) end end + def encrypted_data_hash(data_hash, unencrypted_data) + return 'NULL' if unencrypted_data.empty? + new_data_hash = {} + unencrypted_data.each do |key| + new_data_hash[key] = data_hash.delete(key) if data_hash[key] + end + quote(new_data_hash.to_json) + end + def in_transaction say_with_time("Processing new transaction...") do ActiveRecord::Base.transaction do @@ -59,18 +67,18 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration def update_import_data(import_url, project) fake_import_data = FakeProjectImportData.new fake_import_data.credentials = import_url.credentials - project_import_data = project_import_data(project['id']) - if project_import_data - execute(update_import_data_sql(project_import_data['id'], fake_import_data)) + import_data_id = project['import_data_id'] + if import_data_id + execute(update_import_data_sql(import_data_id, fake_import_data)) else execute(insert_import_data_sql(project['id'], fake_import_data)) end end - def update_with_encrypted_data(data_hash, import_data_id) + def update_with_encrypted_data(data_hash, import_data_id, data_array = nil) fake_import_data = FakeProjectImportData.new fake_import_data.credentials = data_hash - execute(update_import_data_sql(import_data_id, fake_import_data)) + execute(update_import_data_sql(import_data_id, fake_import_data, data_array)) end def update_import_url(import_url, project) @@ -78,16 +86,34 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def insert_import_data_sql(project_id, fake_import_data) - %( INSERT into project_import_data (encrypted_credentials, project_id, encrypted_credentials_iv, encrypted_credentials_salt) VALUES ( #{quote(fake_import_data.encrypted_credentials)}, '#{project_id}', #{quote(fake_import_data.encrypted_credentials_iv)}, #{quote(fake_import_data.encrypted_credentials_salt)})) + %( + INSERT INTO project_import_data + (encrypted_credentials, + project_id, + encrypted_credentials_iv, + encrypted_credentials_salt) + VALUES ( #{quote(fake_import_data.encrypted_credentials)}, + '#{project_id}', + #{quote(fake_import_data.encrypted_credentials_iv)}, + #{quote(fake_import_data.encrypted_credentials_salt)}) + ).squish end def update_import_data_sql(id, fake_import_data, data = 'NULL') - %( UPDATE project_import_data SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)}, encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)}, encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)}, data = #{data} WHERE id = '#{id}') + %( + UPDATE project_import_data + SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)}, + encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)}, + encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)}, + data = #{data} + WHERE id = '#{id}' + ).squish end #Github projects with token, and any user:password@ based URL + #TODO: may need to add import_type != list def projects_with_wrong_import_url - select_all("SELECT p.id, p.import_url FROM projects p WHERE p.import_url IS NOT NULL AND p.import_url LIKE '%//%@%'") + select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.id WHERE p.import_url IS NOT NULL AND p.import_url LIKE '%//%@%'") end # All imports with data for import_type @@ -95,10 +121,6 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = '#{import_type}' ") end - def project_import_data(project_id) - select_one("SELECT id FROM project_import_data WHERE project_id = '#{project_id}'") - end - def quote(value) ActiveRecord::Base.connection.quote(value) end diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index d88a6eaac6..1d1bd5e321 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -5,6 +5,17 @@ module Gitlab attr_reader :consumer, :api + def self.from_project(project) + credentials = project.import_data.credentials if project.import_data + if defined?(credentials) && credentials['bb_session'] + token = credentials['bb_session']['bitbucket_access_token'] + token_secret = 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}" + end + end + def initialize(access_token = nil, access_token_secret = nil) @consumer = ::OAuth::Consumer.new( config.app_id, diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index f80410641a..7beaecd1cf 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -1,6 +1,13 @@ module Gitlab module BitbucketImport - class Importer < ImporterInit + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.from_project(@project) + @formatter = Gitlab::ImportFormatter.new + end def execute import_issues if has_issues? diff --git a/lib/gitlab/bitbucket_import/importer_init.rb b/lib/gitlab/bitbucket_import/importer_init.rb deleted file mode 100644 index 08b710003e..0000000000 --- a/lib/gitlab/bitbucket_import/importer_init.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Gitlab - module BitbucketImport - class ImporterInit - attr_reader :project, :client - - def initialize(project) - @project = project - if import_data_credentials && import_data_credentials['bb_session'] - token = import_data_credentials['bb_session']['bitbucket_access_token'] - token_secret = import_data_credentials['bb_session']['bitbucket_access_token_secret'] - @client = Client.new(token, token_secret) - @formatter = Gitlab::ImportFormatter.new - else - raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" - end - end - - private - - def import_data_credentials - @import_data_credentials ||= project.import_data.credentials if project.import_data - end - end - end -end diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb index 312ed58150..e03c3155b3 100644 --- a/lib/gitlab/bitbucket_import/key_deleter.rb +++ b/lib/gitlab/bitbucket_import/key_deleter.rb @@ -1,7 +1,13 @@ module Gitlab module BitbucketImport - class KeyDeleter < ImporterInit - attr_reader :current_user + class KeyDeleter + attr_reader :project, :current_user, :client + + def initialize(project) + @project = project + @current_user = project.creator + @client = Client.from_project(@project) + end def execute return false unless BitbucketImport.public_key.present? diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 6b0715d149..62da327931 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -6,13 +6,12 @@ module Gitlab def initialize(project) @project = project - if import_data_credentials && import_data_credentials['repo'] - @repo = GoogleCodeImport::Repository.new(import_data_credentials['repo']) - @closed_statuses = [] - @known_labels = Set.new - else - raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" - end + import_data = project.import_data.try(:data) + repo_data = import_data["repo"] if import_data + @repo = GoogleCodeImport::Repository.new(repo_data) + + @closed_statuses = [] + @known_labels = Set.new end def execute @@ -29,10 +28,6 @@ module Gitlab private - def import_data_credentials - @import_data_credentials ||= project.import_data.credentials if project.import_data - end - def user_map @user_map ||= begin user_map = Hash.new do |hash, user| @@ -40,7 +35,8 @@ module Gitlab Client.mask_email(user).sub("...", "\\.\\.\\.") end - stored_user_map = import_data_credentials["user_map"] + import_data = project.import_data.try(:data) + stored_user_map = import_data["user_map"] if import_data user_map.update(stored_user_map) if stored_user_map user_map diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb index acd3a832d5..87821c2346 100644 --- a/lib/gitlab/google_code_import/project_creator.rb +++ b/lib/gitlab/google_code_import/project_creator.rb @@ -25,7 +25,7 @@ module Gitlab ).execute project.create_import_data( - credentials: { + data: { "repo" => repo.raw_data, "user_map" => user_map } diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 6ecf3d7182..647631271e 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do subject { described_class.new(project) } before do - project.create_import_data(credentials: import_data) + project.create_import_data(data: import_data) end describe "#execute" do From 251240c026cf6153be0b8fc5b78dcc0b33deefe2 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 28 Mar 2016 17:01:44 -0500 Subject: [PATCH 030/573] Refresh page according remaining todos --- app/assets/javascripts/todos.js.coffee | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index b6b4bd90e6..e9652ee411 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,4 +1,6 @@ class @Todos + PER_PAGE = 20 + constructor: (@name) -> @clearListeners() @initBtnListeners() @@ -24,6 +26,7 @@ class @Todos dataType: 'json' data: '_method': 'delete' success: (data) => + @redirectIfNeeded data.count @clearDone $this.closest('li') @updateBadges data @@ -54,3 +57,43 @@ class @Todos updateBadges: (data) -> $('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-done .badge').text data.done_count + + getRenderedPages: -> + $('.gl-pagination .page').length + + getCurrentPage: -> + parseInt($.trim($('.gl-pagination .page.active').text())) + + redirectIfNeeded: (total) -> + currPages = @getRenderedPages() + currPage = @getCurrentPage() + + newPages = Math.ceil(total / PER_PAGE) + url = location.href # Includes query strings + + # Refresh if no remaining Todos + if !total + location.reload() + return + + # Do nothing if no pagination + return if !currPages + + # If new total of pages if different than we have now + if newPages isnt currPages + # Redirect to previous page if there´s one available + if currPages > 1 and currPage is currPages + url = @updateQueryStringParameter(url, 'page', currPages - 1) + + location.replace url + + updateQueryStringParameter: (uri, key, value) -> + separator = if uri.indexOf('?') isnt -1 then "&" else "?" + + # Matches key and value + regex = new RegExp("([?&])" + key + "=.*?(&|#|$)", "i") + + if uri.match(regex) + return uri.replace(regex, '$1' + key + "=" + value + '$2') + + uri + separator + key + "=" + value From 5c307cf190201a7e29bf88537b4b4d9821c89294 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 28 Mar 2016 17:05:04 -0500 Subject: [PATCH 031/573] typo --- app/assets/javascripts/todos.js.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index e9652ee411..ee75e4a0b8 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -79,21 +79,21 @@ class @Todos # Do nothing if no pagination return if !currPages - # If new total of pages if different than we have now + # If new total of pages is different than we have now if newPages isnt currPages - # Redirect to previous page if there´s one available + # Redirect to previous page if there's one available if currPages > 1 and currPage is currPages url = @updateQueryStringParameter(url, 'page', currPages - 1) location.replace url updateQueryStringParameter: (uri, key, value) -> - separator = if uri.indexOf('?') isnt -1 then "&" else "?" + separator = if uri.indexOf('?') isnt -1 then '&' else '?' # Matches key and value - regex = new RegExp("([?&])" + key + "=.*?(&|#|$)", "i") + regex = new RegExp('([?&])' + key + '=.*?(&|#|$)', 'i') if uri.match(regex) - return uri.replace(regex, '$1' + key + "=" + value + '$2') + return uri.replace(regex, '$1' + key + '=' + value + '$2') - uri + separator + key + "=" + value + uri + separator + key + '=' + value From 28df200c66f88348e0c995fc8023b6dd78b9bf9b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 29 Mar 2016 15:23:32 +0200 Subject: [PATCH 032/573] fixed failing specs --- lib/gitlab/import_url.rb | 6 +++++- spec/factories/project_import_data.rb | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/import_url.rb b/lib/gitlab/import_url.rb index 5b18d67ddc..3cfbc17b89 100644 --- a/lib/gitlab/import_url.rb +++ b/lib/gitlab/import_url.rb @@ -20,7 +20,7 @@ module Gitlab private def generate_full_url - return @url unless credentials + return @url unless valid_credentials? @full_url = @url.dup @full_url.user = credentials[:user] @full_url.password = credentials[:password] @@ -33,5 +33,9 @@ module Gitlab safe_url.user = nil safe_url end + + def valid_credentials? + credentials && credentials.is_a?(Hash) && credentials.any? + end end end diff --git a/spec/factories/project_import_data.rb b/spec/factories/project_import_data.rb index a799af9996..9e08d5a22e 100644 --- a/spec/factories/project_import_data.rb +++ b/spec/factories/project_import_data.rb @@ -1,5 +1,6 @@ FactoryGirl.define do factory :project_import_data, class: ProjectImportData do data "test" + project end end From c93570d8fb439a8a60a204bcc41e5e8a720730e4 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 29 Mar 2016 16:10:35 +0200 Subject: [PATCH 033/573] fixing a few issues after testing imports --- lib/gitlab/bitbucket_import/project_creator.rb | 3 +++ lib/gitlab/fogbugz_import/project_creator.rb | 11 ++++------- lib/gitlab/google_code_import/project_creator.rb | 9 +++------ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index 0e6f66de32..7dc3e01753 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -22,8 +22,11 @@ module Gitlab import_source: "#{repo["owner"]}/#{repo["slug"]}", import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", ).execute + import_data = project.import_data import_data.credentials.merge!("bb_session" => session_data) + import_data.save + project end end diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index 73d6272720..15914aea3b 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -24,13 +24,10 @@ module Gitlab import_url: Project::UNKNOWN_IMPORT_URL ).execute - project.create_import_data( - credentials: { - 'repo' => repo.raw_data, - 'user_map' => user_map, - 'fb_session' => fb_session - } - ) + import_data = project.import_data + import_data.credentials.merge!('fb_session' => fb_session) + import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } + import_data.save project end diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb index 87821c2346..49d6013af2 100644 --- a/lib/gitlab/google_code_import/project_creator.rb +++ b/lib/gitlab/google_code_import/project_creator.rb @@ -24,12 +24,9 @@ module Gitlab import_url: repo.import_url ).execute - project.create_import_data( - data: { - "repo" => repo.raw_data, - "user_map" => user_map - } - ) + import_data = project.import_data + import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } + import_data.save project end From 9e5a11f4b679f4a11aab31ca82c347ba6b025b68 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 29 Mar 2016 17:27:56 +0200 Subject: [PATCH 034/573] Target right release --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1adfa49ad1..b0ddf6c3a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.7.0 (unreleased) - Add links to CI setup documentation from project settings and builds pages - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) + - Allow issues and merge requests to be assigned to the author v 8.6.2 (unreleased) - Comments on confidential issues don't show up in activity feed to non-members @@ -233,7 +234,6 @@ v 8.5.0 - Add label description (Nuttanart Pornprasitsakul) - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Add Todos - - Allow issues and merge requests to be assigned to the author(Zeger-Jan van de Weg) v 8.4.5 - No CE-specific changes From 075b56aae2e045e930580985234276edb353747f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 29 Mar 2016 18:43:23 +0200 Subject: [PATCH 035/573] more fixes after doing more manual testing on importing --- lib/gitlab/bitbucket_import/project_creator.rb | 3 ++- lib/gitlab/fogbugz_import/project_creator.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index 7dc3e01753..cc7f201714 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -24,7 +24,8 @@ module Gitlab ).execute import_data = project.import_data - import_data.credentials.merge!("bb_session" => session_data) + # merge! with a bang doesn't work here + import_data.credentials = import_data.credentials.merge("bb_session" => session_data) import_data.save project diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index 15914aea3b..939cb96c10 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -25,7 +25,8 @@ module Gitlab ).execute import_data = project.import_data - import_data.credentials.merge!('fb_session' => fb_session) + # merge! with a bang doesn't work here + import_data.credentials = import_data.credentials.merge('fb_session' => fb_session) import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } import_data.save From f3134c2a7045accd70f6877874c43c3ead134d35 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 30 Mar 2016 17:11:51 -0500 Subject: [PATCH 036/573] Expose todos_per_page variable --- app/assets/javascripts/todos.js.coffee | 5 ++--- app/controllers/dashboard/todos_controller.rb | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 29dad4ed6f..ce44a16e22 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,7 +1,6 @@ class @Todos - PER_PAGE = 20 - constructor: (@name) -> + @todos_per_page = gon.todos_per_page || 20 @clearListeners() @initBtnListeners() @@ -70,7 +69,7 @@ class @Todos currPages = @getRenderedPages() currPage = @getCurrentPage() - newPages = Math.ceil(total / PER_PAGE) + newPages = Math.ceil(total / @todos_per_page) url = location.href # Includes query strings # Refresh if no remaining Todos diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 5abf97342c..d91311aa51 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController before_action :find_todos, only: [:index, :destroy, :destroy_all] def index + gon.todos_per_page = Todo.default_per_page @todos = @todos.page(params[:page]) end From 6d12d79d29e035c7238aa7112db1429711e61a65 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 1 Apr 2016 12:04:41 +0200 Subject: [PATCH 037/573] fix fogbugz import --- app/models/project_import_data.rb | 4 ++++ lib/gitlab/fogbugz_import/importer.rb | 14 ++++++-------- lib/gitlab/fogbugz_import/project_creator.rb | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index ba4334055d..fa6055ff64 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -17,4 +17,8 @@ class ProjectImportData < ActiveRecord::Base serialize :data, JSON validates :project, presence: true + + def stringified_credentials + JSON[credentials.to_json] + end end diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index c33b3541dd..c88a44573a 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -8,7 +8,7 @@ module Gitlab import_data = project.import_data.try(:data) repo_data = import_data['repo'] if import_data - if import_data_credentials && import_data_credentials['repo'] + if defined?(repo_data) @repo = FogbugzImport::Repository.new(repo_data) @known_labels = Set.new else @@ -18,10 +18,8 @@ module Gitlab def execute return true unless repo.valid? - - data = project.import_data.try(:data) - - client = Gitlab::FogbugzImport::Client.new(token: data['fb_session']['token'], uri: data['fb_session']['uri']) + Rails.logger.error import_data_credentials.inspect + client = Gitlab::FogbugzImport::Client.new(token: import_data_credentials['fb_session']['token'], uri: import_data_credentials['fb_session']['uri']) @cases = client.cases(@repo.id.to_i) @categories = client.categories @@ -34,7 +32,7 @@ module Gitlab private def import_data_credentials - @import_data_credentials ||= project.import_data.credentials if project.import_data + @import_data_credentials ||= project.import_data.stringified_credentials if project.import_data end def user_map @@ -244,8 +242,8 @@ module Gitlab def build_attachment_url(rel_url) data = project.import_data.try(:data) - uri = data['fb_session']['uri'] - token = data['fb_session']['token'] + uri = import_data_credentials['fb_session']['uri'] + token = import_data_credentials['fb_session']['token'] "#{uri}/#{rel_url}&token=#{token}" end diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index 939cb96c10..0a87b406c5 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -25,9 +25,10 @@ module Gitlab ).execute import_data = project.import_data + import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } + # merge! with a bang doesn't work here import_data.credentials = import_data.credentials.merge('fb_session' => fb_session) - import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } import_data.save project From 6aeb753ba81c857c23d29f9d4c5e22aaeed737dc Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 1 Apr 2016 15:51:54 +0200 Subject: [PATCH 038/573] fix github import issues --- app/models/project.rb | 2 +- lib/gitlab/github_import/importer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 52f70256be..941e444a4f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -418,7 +418,7 @@ class Project < ActiveRecord::Base def create_or_update_import_data(credentials) project_import_data = import_data || build_import_data project_import_data.credentials ||= {} - project_import_data.credentials.merge!(credentials) + project_import_data.credentials = project_import_data.credentials.merge(credentials) project_import_data.save end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index a5d3ab5fcf..0b1ed51022 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -8,7 +8,7 @@ module Gitlab def initialize(project) @project = project if import_data_credentials - @client = Client.new(import_data_credentials['user']) + @client = Client.new(import_data_credentials[:user]) @formatter = Gitlab::ImportFormatter.new else raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" From 255cd31652f1afda5cd1c075526bbe3ee56a708e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 1 Apr 2016 17:27:07 +0200 Subject: [PATCH 039/573] fixes after more import testing --- lib/gitlab/bitbucket_import/client.rb | 2 +- lib/gitlab/gitlab_import/importer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index 1d1bd5e321..acd0f298b3 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -6,7 +6,7 @@ module Gitlab attr_reader :consumer, :api def self.from_project(project) - credentials = project.import_data.credentials if project.import_data + credentials = project.import_data.stringified_credentials if project.import_data if defined?(credentials) && credentials['bb_session'] token = credentials['bb_session']['bitbucket_access_token'] token_secret = credentials['bb_session']['bitbucket_access_token_secret'] diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index afc06d01eb..19dc79462c 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -5,7 +5,7 @@ module Gitlab def initialize(project) @project = project - credentials = import_data.credentials + credentials = import_data.stringified_credentials if credentials && credentials["password"] @client = Client.new(credentials["password"]) @formatter = Gitlab::ImportFormatter.new From 43ee65e173cbb4f846c118c531f635dc0f8602ac Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 1 Apr 2016 17:50:50 +0200 Subject: [PATCH 040/573] remove useless var --- lib/gitlab/fogbugz_import/importer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index c88a44573a..fffeb66ce2 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -241,7 +241,6 @@ module Gitlab end def build_attachment_url(rel_url) - data = project.import_data.try(:data) uri = import_data_credentials['fb_session']['uri'] token = import_data_credentials['fb_session']['token'] "#{uri}/#{rel_url}&token=#{token}" From 8c0aba9458a25266bb52bbc2101a83ed05967722 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 1 Apr 2016 12:55:45 -0500 Subject: [PATCH 041/573] Get pagination options form the view --- app/assets/javascripts/todos.js.coffee | 27 ++++++++++++------- app/controllers/dashboard/todos_controller.rb | 1 - app/views/dashboard/todos/index.html.haml | 1 + 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index ce44a16e22..f39184777a 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,6 +1,11 @@ class @Todos - constructor: (@name) -> - @todos_per_page = gon.todos_per_page || 20 + constructor: (opts = {}) -> + { + @el = $('.js-todos-options') + } = opts + + @perPage = @el.data('perPage') + @clearListeners() @initBtnListeners() @@ -59,26 +64,30 @@ class @Todos $('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-done .badge').text data.done_count - getRenderedPages: -> - $('.gl-pagination .page').length + getTotalPages: -> + @el.data('totalPages') getCurrentPage: -> - parseInt($.trim($('.gl-pagination .page.active').text())) + @el.data('currentPage') + + getTodosPerPage: -> + @el.data('perPage') + redirectIfNeeded: (total) -> - currPages = @getRenderedPages() + currPages = @getTotalPages() currPage = @getCurrentPage() - newPages = Math.ceil(total / @todos_per_page) + newPages = Math.ceil(total / @getTodosPerPage()) url = location.href # Includes query strings # Refresh if no remaining Todos - if !total + if not total location.reload() return # Do nothing if no pagination - return if !currPages + return if not currPages # If new total of pages is different than we have now if newPages isnt currPages diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index d91311aa51..5abf97342c 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -2,7 +2,6 @@ class Dashboard::TodosController < Dashboard::ApplicationController before_action :find_todos, only: [:index, :destroy, :destroy_all] def index - gon.todos_per_page = Todo.default_per_page @todos = @todos.page(params[:page]) end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index f9ec3a8915..49ab8aad1d 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -45,6 +45,7 @@ .prepend-top-default - if @todos.any? + .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } - @todos.group_by(&:project).each do |group| .panel.panel-default.panel-small.js-todos-list - project = group[0] From ef85c510fa24a0deec6f680f9226dccd937a7bbe Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 4 Apr 2016 09:57:52 +0200 Subject: [PATCH 042/573] corrected a couple of based on MR review --- .../20160302152808_remove_wrong_import_url_from_projects.rb | 4 ++-- lib/gitlab/fogbugz_import/importer.rb | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index fb5d8591c0..60f86c74df 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -16,8 +16,8 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration say("Projects and Github projects with a wrong URL. It also migrates Gitlab project credentials.") in_transaction { process_projects_with_wrong_url } - say("Migrating bitbucket credentials...")# TODO remove last param - in_transaction { process_project(import_type: 'bitbucket', unencrypted_data: ['repo', 'user_map']) } + say("Migrating bitbucket credentials...") + in_transaction { process_project(import_type: 'bitbucket') } say("Migrating fogbugz credentials...") in_transaction { process_project(import_type: 'fogbugz', unencrypted_data: ['repo', 'user_map']) } diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index fffeb66ce2..5c1c1c4865 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -8,7 +8,7 @@ module Gitlab import_data = project.import_data.try(:data) repo_data = import_data['repo'] if import_data - if defined?(repo_data) + if repo_data @repo = FogbugzImport::Repository.new(repo_data) @known_labels = Set.new else @@ -18,7 +18,6 @@ module Gitlab def execute return true unless repo.valid? - Rails.logger.error import_data_credentials.inspect client = Gitlab::FogbugzImport::Client.new(token: import_data_credentials['fb_session']['token'], uri: import_data_credentials['fb_session']['uri']) @cases = client.cases(@repo.id.to_i) From 3cf512362ef6e459c4a10f53818db799b9b1a248 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Apr 2016 15:39:08 +0200 Subject: [PATCH 043/573] Add notice about GitLab Runner to requirements docs This is related to !14589, and problems that may stem from running GitLab Runner on same machine user installed GitLab web app on. --- doc/install/requirements.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 03cb08dd1f..03433a72fa 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -79,6 +79,16 @@ With less memory GitLab will give strange errors during the reconfigure run and 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 + +We strongly advise against installing GitLab Runner on the same machine you plan to install GitLab on. Depending on how you decide to configure GitLab Runner and what tools you use to exercise your application in the CI environment, GitLab Runner can consume significant amount of available memory. + +Memory consumption calculations, that are available above, will not be valid if you decide to run GitLab Runner and GitLab web application on the same machine. + +It is also not safe to install everything on a single machine, because of the security reasons - especially when you plan to use shell executor with GitLab Runner. + +We recommend using a separate machine for each GitLab Runner, if you plan to use CI features. + ## Unicorn Workers It's possible to increase the amount of unicorn workers and this will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests. From bf9526739b5c90790907c1d8b9410dd339e3d395 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 4 Apr 2016 17:23:43 +0200 Subject: [PATCH 044/573] Rebase repo check MR --- app/controllers/admin/projects_controller.rb | 24 +++++++++- app/mailers/repo_check_mailer.rb | 16 +++++++ app/views/admin/logs/show.html.haml | 3 +- app/views/admin/projects/index.html.haml | 15 +++++- app/views/admin/projects/show.html.haml | 32 +++++++++++++ app/views/repo_check_mailer/notify.html.haml | 5 ++ app/views/repo_check_mailer/notify.text.haml | 3 ++ app/workers/admin_email_worker.rb | 12 +++++ app/workers/repo_check_worker.rb | 46 +++++++++++++++++++ app/workers/single_repo_check_worker.rb | 34 ++++++++++++++ config/gitlab.yml.example | 7 +++ config/initializers/1_settings.rb | 7 ++- config/routes.rb | 5 ++ .../20160315135439_project_add_repo_check.rb | 6 +++ db/schema.rb | 2 + doc/administration/repo_checks.md | 39 ++++++++++++++++ lib/gitlab/repo_check_logger.rb | 7 +++ spec/features/admin/admin_projects_spec.rb | 37 ++++++++++++++- spec/mailers/repo_check_mailer_spec.rb | 21 +++++++++ spec/workers/repo_check_worker_spec.rb | 31 +++++++++++++ 20 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 app/mailers/repo_check_mailer.rb create mode 100644 app/views/repo_check_mailer/notify.html.haml create mode 100644 app/views/repo_check_mailer/notify.text.haml create mode 100644 app/workers/admin_email_worker.rb create mode 100644 app/workers/repo_check_worker.rb create mode 100644 app/workers/single_repo_check_worker.rb create mode 100644 db/migrate/20160315135439_project_add_repo_check.rb create mode 100644 doc/administration/repo_checks.md create mode 100644 lib/gitlab/repo_check_logger.rb create mode 100644 spec/mailers/repo_check_mailer_spec.rb create mode 100644 spec/workers/repo_check_worker_spec.rb diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 4089091d56..b8c276fb1b 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,5 +1,5 @@ class Admin::ProjectsController < Admin::ApplicationController - before_action :project, only: [:show, :transfer] + before_action :project, only: [:show, :transfer, :repo_check] before_action :group, only: [:show, :transfer] def index @@ -8,6 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.with_push if params[:with_push].present? @projects = @projects.abandoned if params[:abandoned].present? + @projects = @projects.where(last_repo_check_failed: true) if params[:last_repo_check_failed].present? @projects = @projects.non_archived unless params[:with_archived].present? @projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.sort(@sort = params[:sort]) @@ -30,6 +31,27 @@ class Admin::ProjectsController < Admin::ApplicationController redirect_to admin_namespace_project_path(@project.namespace, @project) end + def repo_check + SingleRepoCheckWorker.perform_async(@project.id) + + redirect_to( + admin_namespace_project_path(@project.namespace, @project), + notice: 'Repo check was triggered' + ) + end + + def clear_repo_check_states + Project.update_all( + last_repo_check_failed: false, + last_repo_check_at: nil + ) + + redirect_to( + admin_namespaces_projects_path, + notice: 'All project repo check states were cleared' + ) + end + protected def project diff --git a/app/mailers/repo_check_mailer.rb b/app/mailers/repo_check_mailer.rb new file mode 100644 index 0000000000..d98533f120 --- /dev/null +++ b/app/mailers/repo_check_mailer.rb @@ -0,0 +1,16 @@ +class RepoCheckMailer < BaseMailer + include ActionView::Helpers::TextHelper + + def notify(failed_count) + if failed_count == 1 + @message = "One project failed its last repository check" + else + @message = "#{failed_count} projects failed their last repository check" + end + + mail( + to: User.admins.pluck(:email), + subject: @message + ) + end +end diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index af9fdeb073..abcc93f404 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,6 +1,7 @@ - page_title "Logs" - loggers = [Gitlab::GitLogger, Gitlab::AppLogger, - Gitlab::ProductionLogger, Gitlab::SidekiqLogger] + Gitlab::ProductionLogger, Gitlab::SidekiqLogger, + Gitlab::RepoCheckLogger] %ul.nav-links.log-tabs - loggers.each do |klass| %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index d39c0f4403..ed360f2f01 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -3,7 +3,7 @@ .row.prepend-top-default %aside.col-md-3 - .admin-filter + .panel.admin-filter = form_tag admin_namespaces_projects_path, method: :get, class: '' do .form-group = label_tag :name, 'Name:' @@ -38,11 +38,22 @@ %span.descr = visibility_level_icon(level) = label - %hr + %fieldset + %strong Problems + .checkbox + = label_tag :last_repo_check_failed do + = check_box_tag :last_repo_check_failed, 1, params[:last_repo_check_failed] + %span Last repo check failed + = hidden_field_tag :sort, params[:sort] = button_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" + .panel.panel-default.repo-check-states + .panel-heading + Repo check states + .panel-body + = link_to 'Clear all', clear_repo_check_states_admin_namespace_projects_path(0), data: { confirm: 'This will clear repo check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove" %section.col-md-9 .panel.panel-default .panel-heading diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index c638c32a65..a7a3f6349e 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -5,6 +5,14 @@ %i.fa.fa-pencil-square-o Edit %hr +- if @project.last_repo_check_failed? + .row + .col-md-12 + .panel + .panel-heading.alert.alert-danger + Last repo check failed. See + = link_to 'repocheck.log', admin_logs_path + for error messages. .row .col-md-6 .panel.panel-default @@ -95,6 +103,30 @@ .col-sm-offset-2.col-sm-10 = f.submit 'Transfer', class: 'btn btn-primary' + .panel.panel-default.repo-check + .panel-heading + Repo check + .panel-body + = form_for @project, url: repo_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f| + .form-group + - if @project.last_repo_check_at.nil? + This repository has never been checked. + - else + This repository was last checked + = @project.last_repo_check_at.to_s(:medium) + '.' + The check + - if @project.last_repo_check_failed? + = succeed '.' do + %strong.cred failed + See + = link_to 'repocheck.log', admin_logs_path + for error messages. + - else + passed. + + .form-group + = f.submit 'Trigger repo check', class: 'btn btn-primary' + .col-md-6 - if @group .panel.panel-default diff --git a/app/views/repo_check_mailer/notify.html.haml b/app/views/repo_check_mailer/notify.html.haml new file mode 100644 index 0000000000..ef0016f9d3 --- /dev/null +++ b/app/views/repo_check_mailer/notify.html.haml @@ -0,0 +1,5 @@ +%p + #{@message}. + +%p + = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repo_check_failed: 1) diff --git a/app/views/repo_check_mailer/notify.text.haml b/app/views/repo_check_mailer/notify.text.haml new file mode 100644 index 0000000000..bdf8c2ad67 --- /dev/null +++ b/app/views/repo_check_mailer/notify.text.haml @@ -0,0 +1,3 @@ +#{@message}. +\ +View details: #{admin_namespaces_projects_url(last_repo_check_failed: 1)} diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb new file mode 100644 index 0000000000..fcccb9ea66 --- /dev/null +++ b/app/workers/admin_email_worker.rb @@ -0,0 +1,12 @@ +class AdminEmailWorker + include Sidekiq::Worker + + sidekiq_options retry: false # this job auto-repeats via sidekiq-cron + + def perform + repo_check_failed_count = Project.where(last_repo_check_failed: true).count + return if repo_check_failed_count.zero? + + RepoCheckMailer.notify(repo_check_failed_count).deliver_now + end +end diff --git a/app/workers/repo_check_worker.rb b/app/workers/repo_check_worker.rb new file mode 100644 index 0000000000..9be795309e --- /dev/null +++ b/app/workers/repo_check_worker.rb @@ -0,0 +1,46 @@ +class RepoCheckWorker + include Sidekiq::Worker + + RUN_TIME = 3600 + + sidekiq_options retry: false + + def perform + start = Time.now + + # This loop will break after a little more than one hour ('a little + # more' because `git fsck` may take a few minutes), or if it runs out of + # projects to check. By default sidekiq-cron will start a new + # RepoCheckWorker each hour so that as long as there are repositories to + # check, only one (or two) will be checked at a time. + project_ids.each do |project_id| + break if Time.now - start >= RUN_TIME + + next if !try_obtain_lease(project_id) + + SingleRepoCheckWorker.new.perform(project_id) + end + end + + private + + # In an ideal world we would use Project.where(...).find_each. + # Unfortunately, calling 'find_each' drops the 'where', so we must build + # an array of IDs instead. + def project_ids + limit = 10_000 + never_checked_projects = Project.where('last_repo_check_at IS NULL').limit(limit). + pluck(:id) + old_check_projects = Project.where('last_repo_check_at < ?', 1.week.ago). + reorder('last_repo_check_at ASC').limit(limit).pluck(:id) + never_checked_projects + old_check_projects + end + + def try_obtain_lease(id) + lease = Gitlab::ExclusiveLease.new( + "project_repo_check:#{id}", + timeout: RUN_TIME + ) + lease.try_obtain + end +end diff --git a/app/workers/single_repo_check_worker.rb b/app/workers/single_repo_check_worker.rb new file mode 100644 index 0000000000..f8b245247c --- /dev/null +++ b/app/workers/single_repo_check_worker.rb @@ -0,0 +1,34 @@ +class SingleRepoCheckWorker + include Sidekiq::Worker + + sidekiq_options retry: false + + def perform(project_id) + project = Project.find(project_id) + update(project, success: check(project)) + end + + private + + def check(project) + [project.repository.path_to_repo, project.wiki.wiki.path].all? do |path| + git_fsck(path) + end + end + + def git_fsck(path) + cmd = %W(nice git --git-dir=#{path} fsck) + output, status = Gitlab::Popen.popen(cmd) + return true if status.zero? + + Gitlab::RepoCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") + false + end + + def update(project, success:) + project.update_columns( + last_repo_check_failed: !success, + last_repo_check_at: Time.now, + ) + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index fb1c3476f6..cf20fc9c63 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -155,6 +155,13 @@ production: &base # Flag stuck CI builds as failed stuck_ci_builds_worker: cron: "0 0 * * *" + # Periodically run 'git fsck' on all repositories. If started more than + # once per hour you will have concurrent 'git fsck' jobs. + repo_check_worker: + cron: "20 * * * *" + # Send admin emails once a day + admin_email_worker: + cron: "0 0 * * *" # diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 2b98901527..8240978ef0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -239,7 +239,12 @@ Settings['cron_jobs'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker' - +Settings.cron_jobs['repo_check_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['repo_check_worker']['cron'] ||= '20 * * * *' +Settings.cron_jobs['repo_check_worker']['job_class'] = 'RepoCheckWorker' +Settings.cron_jobs['admin_email_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * *' +Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker' # # GitLab Shell diff --git a/config/routes.rb b/config/routes.rb index 6bf22fb445..fad8600b77 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -264,6 +264,11 @@ Rails.application.routes.draw do member do put :transfer + post :repo_check + end + + collection do + put :clear_repo_check_states end resources :runner_projects diff --git a/db/migrate/20160315135439_project_add_repo_check.rb b/db/migrate/20160315135439_project_add_repo_check.rb new file mode 100644 index 0000000000..ba7ddc9ecb --- /dev/null +++ b/db/migrate/20160315135439_project_add_repo_check.rb @@ -0,0 +1,6 @@ +class ProjectAddRepoCheck < ActiveRecord::Migration + def change + add_column :projects, :last_repo_check_failed, :boolean, default: false + add_column :projects, :last_repo_check_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index e63e22ce86..d2c183f968 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -732,6 +732,8 @@ ActiveRecord::Schema.define(version: 20160331133914) do t.boolean "public_builds", default: true, null: false t.string "main_language" t.integer "pushes_since_gc", default: 0 + t.boolean "last_repo_check_failed", default: false + t.datetime "last_repo_check_at" end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree diff --git a/doc/administration/repo_checks.md b/doc/administration/repo_checks.md new file mode 100644 index 0000000000..9c2c01594e --- /dev/null +++ b/doc/administration/repo_checks.md @@ -0,0 +1,39 @@ +# Repo checks + +_**Note:** This feature was [introduced][ce-3232] in GitLab 8.7_ + +--- + +Git has a built-in mechanism [git fsck][git-fsck] to verify the +integrity of all data commited to a repository. GitLab administrators can +trigger such a check for a project via the admin panel. The checks run +asynchronously so it may take a few minutes before the check result is +visible on the project admin page. If the checks failed you can see their +output on the admin log page under 'repocheck.log'. + +## Periodical checks + +GitLab periodically runs a repo check on all project repositories and +wiki repositories in order to detect data corruption problems. A +project will be checked no more than once per week. If any projects +fail their repo checks all GitLab administrators will receive an email +notification of the situation. This notification is sent out no more +than once a day. + + +## What to do if a check failed + +If the repo check fails for some repository you should look up the error +in repocheck.log (in the admin panel or on disk; see +`/var/log/gitlab/gitlab-rails` for Omnibus installations or +`/home/git/gitlab/log` for installations from source). Once you have +resolved the issue use the admin panel to trigger a new repo check on +the project. This will clear the 'check failed' state. + +If for some reason the periodical repo check caused a lot of false +alarms you can choose to clear ALL repo check states from the admin +project index page. + +--- +[ce-3232]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3232 "Auto git fsck" +[git-fsck]: https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html "git fsck documentation" \ No newline at end of file diff --git a/lib/gitlab/repo_check_logger.rb b/lib/gitlab/repo_check_logger.rb new file mode 100644 index 0000000000..9409f68722 --- /dev/null +++ b/lib/gitlab/repo_check_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class RepoCheckLogger < Gitlab::Logger + def self.file_name_noext + 'repocheck' + end + end +end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 101d955d69..e3991d48ed 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' +require 'rails_helper' -describe "Admin::Projects", feature: true do +describe "Admin Projects", feature: true do before do @project = create(:project) login_as :admin @@ -31,4 +32,38 @@ describe "Admin::Projects", feature: true do expect(page).to have_content(@project.name) end end + + feature 'repo checks' do + scenario 'trigger repo check' do + visit_admin_project_page + + page.within('.repo-check') do + click_button 'Trigger repo check' + end + + expect(page).to have_content('Repo check was triggered') + end + + scenario 'see failed repo check' do + @project.update_column(:last_repo_check_failed, true) + visit_admin_project_page + + expect(page).to have_content('Last repo check failed') + end + + scenario 'clear repo checks', js: true do + @project.update_column(:last_repo_check_failed, true) + visit admin_namespaces_projects_path + + page.within('.repo-check-states') do + click_link 'Clear all' # pop-up should be auto confirmed + end + + expect(@project.reload.last_repo_check_failed).to eq(false) + end + end + + def visit_admin_project_page + visit admin_namespace_project_path(@project.namespace, @project) + end end diff --git a/spec/mailers/repo_check_mailer_spec.rb b/spec/mailers/repo_check_mailer_spec.rb new file mode 100644 index 0000000000..d49a6ae0c0 --- /dev/null +++ b/spec/mailers/repo_check_mailer_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe RepoCheckMailer do + include EmailSpec::Matchers + + describe '.notify' do + it 'emails all admins' do + admins = 3.times.map { create(:admin) } + + mail = described_class.notify(1) + + expect(mail).to deliver_to admins.map(&:email) + end + + it 'mentions the number of failed checks' do + mail = described_class.notify(3) + + expect(mail).to have_subject '3 projects failed their last repository check' + end + end +end diff --git a/spec/workers/repo_check_worker_spec.rb b/spec/workers/repo_check_worker_spec.rb new file mode 100644 index 0000000000..7ef3eba9ac --- /dev/null +++ b/spec/workers/repo_check_worker_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe RepoCheckWorker do + subject { RepoCheckWorker.new } + + it 'prefers projects that have never been checked' do + projects = 3.times.map { create(:project) } + projects[0].update_column(:last_repo_check_at, 1.month.ago) + projects[2].update_column(:last_repo_check_at, 3.weeks.ago) + + expect(subject.perform).to eq(projects.values_at(1, 0, 2).map(&:id)) + end + + it 'sorts projects by last_repo_check_at' do + projects = 3.times.map { create(:project) } + projects[0].update_column(:last_repo_check_at, 2.weeks.ago) + projects[1].update_column(:last_repo_check_at, 1.month.ago) + projects[2].update_column(:last_repo_check_at, 3.weeks.ago) + + expect(subject.perform).to eq(projects.values_at(1, 2, 0).map(&:id)) + end + + it 'excludes projects that were checked recently' do + projects = 3.times.map { create(:project) } + projects[0].update_column(:last_repo_check_at, 2.days.ago) + projects[1].update_column(:last_repo_check_at, 1.month.ago) + projects[2].update_column(:last_repo_check_at, 3.days.ago) + + expect(subject.perform).to eq([projects[1].id]) + end +end From 288d8e669883abe995fe27ad577189da42ef78fb Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 4 Apr 2016 18:03:55 +0200 Subject: [PATCH 045/573] fixes based on MR review --- ...8_remove_wrong_import_url_from_projects.rb | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 60f86c74df..708dde8eca 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -17,10 +17,10 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration in_transaction { process_projects_with_wrong_url } say("Migrating bitbucket credentials...") - in_transaction { process_project(import_type: 'bitbucket') } + in_transaction { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) } say("Migrating fogbugz credentials...") - in_transaction { process_project(import_type: 'fogbugz', unencrypted_data: ['repo', 'user_map']) } + in_transaction { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) } end @@ -33,27 +33,26 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end end - def process_project(import_type: , unencrypted_data: []) + def process_project(import_type: , credentials_keys: []) unencrypted_import_data(import_type: import_type).each do |data| - replace_data_credentials(data, unencrypted_data) + replace_data_credentials(data, credentials_keys) end end - def replace_data_credentials(data, unencrypted_data) + def replace_data_credentials(data, credentials_keys) data_hash = JSON.load(data['data']) if data['data'] - if defined?(data_hash) && !data_hash.blank? - unencrypted_data_hash = encrypted_data_hash(data_hash, unencrypted_data) - update_with_encrypted_data(data_hash, data['id'], unencrypted_data_hash) + unless data_hash.blank? + encrypted_data_hash = encrypt_data(data_hash, credentials_keys) + unencrypted_data = data_hash.empty? ? ' NULL ' : quote(data_hash.to_json) + update_with_encrypted_data(encrypted_data_hash, data['id'], unencrypted_data) end end - def encrypted_data_hash(data_hash, unencrypted_data) - return 'NULL' if unencrypted_data.empty? + def encrypt_data(data_hash, credentials_keys) new_data_hash = {} - unencrypted_data.each do |key| + credentials_keys.each do |key| new_data_hash[key] = data_hash.delete(key) if data_hash[key] end - quote(new_data_hash.to_json) end def in_transaction @@ -75,10 +74,10 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end end - def update_with_encrypted_data(data_hash, import_data_id, data_array = nil) + def update_with_encrypted_data(data_hash, import_data_id, unencrypted_data = ' NULL ') fake_import_data = FakeProjectImportData.new fake_import_data.credentials = data_hash - execute(update_import_data_sql(import_data_id, fake_import_data, data_array)) + execute(update_import_data_sql(import_data_id, fake_import_data, unencrypted_data)) end def update_import_url(import_url, project) From 172d37a2385424eb5f4c157fa691fd47b9f5a850 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 4 Apr 2016 19:45:53 +0200 Subject: [PATCH 046/573] fix wording --- ...0302152808_remove_wrong_import_url_from_projects.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 708dde8eca..0a7ba65acc 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -12,14 +12,14 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration def up say("Encrypting and migrating project import credentials...") - # This should cover Github, Gitlab, Bitbucket user:password, token@domain, and other similar URLs. - say("Projects and Github projects with a wrong URL. It also migrates Gitlab project credentials.") + # This should cover GitHub, GitLab, Bitbucket user:password, token@domain, and other similar URLs. + say("Projects and GitHub projects with a wrong URL. It also migrates GitLab project credentials.") in_transaction { process_projects_with_wrong_url } - say("Migrating bitbucket credentials...") + say("Migrating Bitbucket credentials...") in_transaction { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) } - say("Migrating fogbugz credentials...") + say("Migrating FogBugz credentials...") in_transaction { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) } end @@ -109,7 +109,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration ).squish end - #Github projects with token, and any user:password@ based URL + #GitHub projects with token, and any user:password@ based URL #TODO: may need to add import_type != list def projects_with_wrong_import_url select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.id WHERE p.import_url IS NOT NULL AND p.import_url LIKE '%//%@%'") From 4835e680a4624ab8de3316b367b8375bb5a270a0 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 4 Apr 2016 19:55:13 +0200 Subject: [PATCH 047/573] fix migration issue --- .../20160302152808_remove_wrong_import_url_from_projects.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 0a7ba65acc..8fef93233e 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -53,6 +53,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration credentials_keys.each do |key| new_data_hash[key] = data_hash.delete(key) if data_hash[key] end + new_data_hash end def in_transaction From 5e51fce4dcd62997f372aed44badc844f98851e9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 5 Apr 2016 15:41:15 +0200 Subject: [PATCH 048/573] some refactoring to symbolise keys across importers and left a TODO --- app/models/project_import_data.rb | 8 ++++++-- ...0160302152808_remove_wrong_import_url_from_projects.rb | 4 ++-- db/schema.rb | 5 ++--- lib/gitlab/bitbucket_import/client.rb | 8 ++++---- lib/gitlab/bitbucket_import/project_creator.rb | 2 +- lib/gitlab/fogbugz_import/importer.rb | 8 ++++---- lib/gitlab/fogbugz_import/project_creator.rb | 2 +- lib/gitlab/gitlab_import/importer.rb | 6 +++--- 8 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index fa6055ff64..225cbda15b 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -18,7 +18,11 @@ class ProjectImportData < ActiveRecord::Base validates :project, presence: true - def stringified_credentials - JSON[credentials.to_json] + # TODO: This doesnt play well with attr_encrypted. Perhaps consider extending Marshall and specify a different Marshaller + before_validation :symbolize_credentials + + def symbolize_credentials + return if credentials.blank? + credentials.deep_symbolize_keys! end end diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 8fef93233e..93e040fce2 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -51,9 +51,9 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration def encrypt_data(data_hash, credentials_keys) new_data_hash = {} credentials_keys.each do |key| - new_data_hash[key] = data_hash.delete(key) if data_hash[key] + new_data_hash[key.to_sym] = data_hash.delete(key) if data_hash[key] end - new_data_hash + new_data_hash.deep_symbolize_keys end def in_transaction diff --git a/db/schema.rb b/db/schema.rb index df4c65a062..5d87dbfe41 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: 20160331133914) do +ActiveRecord::Schema.define(version: 20160331223143) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -44,7 +44,6 @@ ActiveRecord::Schema.define(version: 20160331133914) do t.datetime "updated_at" t.string "home_page_url" t.integer "default_branch_protection", default: 2 - t.boolean "twitter_sharing_enabled", default: true t.text "restricted_visibility_levels" t.boolean "version_check_enabled", default: true t.integer "max_attachment_size", default: 10, null: false @@ -417,9 +416,9 @@ ActiveRecord::Schema.define(version: 20160331133914) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.integer "moved_to_id" t.boolean "confidential", default: false t.datetime "deleted_at" + t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index acd0f298b3..49f86ab5ed 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -6,10 +6,10 @@ module Gitlab attr_reader :consumer, :api def self.from_project(project) - credentials = project.import_data.stringified_credentials if project.import_data - if defined?(credentials) && credentials['bb_session'] - token = credentials['bb_session']['bitbucket_access_token'] - token_secret = credentials['bb_session']['bitbucket_access_token_secret'] + credentials = project.import_data if project.import_data + if credentials && credentials[:bb_session] + token = credentials[:bb_session][:bitbucket_access_token] + token_secret = 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}" diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index cc7f201714..109010cb96 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -25,7 +25,7 @@ module Gitlab import_data = project.import_data # merge! with a bang doesn't work here - import_data.credentials = import_data.credentials.merge("bb_session" => session_data) + import_data.credentials = import_data.credentials.merge(bb_session: session_data) import_data.save project diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 5c1c1c4865..249c5b48b1 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -18,7 +18,7 @@ module Gitlab def execute return true unless repo.valid? - client = Gitlab::FogbugzImport::Client.new(token: import_data_credentials['fb_session']['token'], uri: import_data_credentials['fb_session']['uri']) + client = Gitlab::FogbugzImport::Client.new(token: import_data_credentials[:fb_session][:token], uri: import_data_credentials[:fb_session][:uri]) @cases = client.cases(@repo.id.to_i) @categories = client.categories @@ -31,7 +31,7 @@ module Gitlab private def import_data_credentials - @import_data_credentials ||= project.import_data.stringified_credentials if project.import_data + @import_data_credentials ||= project.import_data if project.import_data end def user_map @@ -240,8 +240,8 @@ module Gitlab end def build_attachment_url(rel_url) - uri = import_data_credentials['fb_session']['uri'] - token = import_data_credentials['fb_session']['token'] + uri = import_data_credentials[:fb_session][:uri] + token = import_data_credentials[:fb_session][:token] "#{uri}/#{rel_url}&token=#{token}" end diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index 0a87b406c5..e9fac8968e 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -28,7 +28,7 @@ module Gitlab import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } # merge! with a bang doesn't work here - import_data.credentials = import_data.credentials.merge('fb_session' => fb_session) + import_data.credentials = import_data.credentials.merge(fb_session: fb_session) import_data.save project diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index 19dc79462c..96717b42ba 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -5,9 +5,9 @@ module Gitlab def initialize(project) @project = project - credentials = import_data.stringified_credentials - if credentials && credentials["password"] - @client = Client.new(credentials["password"]) + credentials = import_data + if credentials && credentials[:password] + @client = Client.new(credentials[:password]) @formatter = Gitlab::ImportFormatter.new else raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" From 77a24965cafba42b596039f9058e2e11a7ebc0dd Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 5 Apr 2016 17:08:45 +0200 Subject: [PATCH 049/573] Add author id to the right selector --- app/assets/javascripts/users_select.js.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 33d3357a1f..a7e934936e 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -12,6 +12,7 @@ class @UsersSelect showNullUser = $dropdown.data('null-user') showAnyUser = $dropdown.data('any-user') firstUser = $dropdown.data('first-user') + @authorId = $dropdown.data('author-id') selectedId = $dropdown.data('selected') defaultLabel = $dropdown.data('default-label') issueURL = $dropdown.data('issueUpdate') @@ -212,7 +213,7 @@ class @UsersSelect showAnyUser = $(select).data('any-user') showEmailUser = $(select).data('email-user') firstUser = $(select).data('first-user') - + $(select).select2 placeholder: "Search for a user" multiple: $(select).hasClass('multiselect') From b97654393e326095c7d95ccc1eb9f583a3b23da9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 6 Apr 2016 10:36:30 +0200 Subject: [PATCH 050/573] fix some issues with credentials --- app/models/project_import_data.rb | 4 ++-- lib/gitlab/bitbucket_import/client.rb | 12 ++++++------ lib/gitlab/fogbugz_import/importer.rb | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 225cbda15b..e984832685 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -22,7 +22,7 @@ class ProjectImportData < ActiveRecord::Base before_validation :symbolize_credentials def symbolize_credentials - return if credentials.blank? - credentials.deep_symbolize_keys! + # bang doesn't work here + self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank? end end diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index 49f86ab5ed..9bb507b5ed 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -6,10 +6,10 @@ module Gitlab attr_reader :consumer, :api def self.from_project(project) - credentials = project.import_data if project.import_data - if credentials && credentials[:bb_session] - token = credentials[:bb_session][:bitbucket_access_token] - token_secret = credentials[:bb_session][:bitbucket_access_token_secret] + import_data_credentials = project.import_data.credentials if project.import_data + if import_data_credentials && import_data_credentials[:bb_session] + token = import_data_credentials[:bb_session][:bitbucket_access_token] + 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}" @@ -65,7 +65,7 @@ module Gitlab def issues(project_identifier) all_issues = [] offset = 0 - per_page = 50 # Maximum number allowed by Bitbucket + per_page = 50 # Maximum number allowed by Bitbucket index = 0 begin @@ -131,7 +131,7 @@ module Gitlab end def config - Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"} + Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket" } end def bitbucket_options diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 249c5b48b1..42f9b6eab8 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -31,7 +31,7 @@ module Gitlab private def import_data_credentials - @import_data_credentials ||= project.import_data if project.import_data + @import_data_credentials ||= project.import_data.credentials if project.import_data end def user_map From 05be0c306ea8663461ee73023f162aeaf77a4325 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 6 Apr 2016 11:13:19 +0200 Subject: [PATCH 051/573] removed TODO [ci skip] --- app/models/project_import_data.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index e984832685..a099431200 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -18,7 +18,6 @@ class ProjectImportData < ActiveRecord::Base validates :project, presence: true - # TODO: This doesnt play well with attr_encrypted. Perhaps consider extending Marshall and specify a different Marshaller before_validation :symbolize_credentials def symbolize_credentials From e3558ed67e7e829fe5148c3fb2fe80ed045fe1b4 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 12:26:29 +0200 Subject: [PATCH 052/573] Document how to disable repo checks --- doc/administration/repo_checks.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/administration/repo_checks.md b/doc/administration/repo_checks.md index 9c2c01594e..81087f3ac1 100644 --- a/doc/administration/repo_checks.md +++ b/doc/administration/repo_checks.md @@ -20,6 +20,22 @@ fail their repo checks all GitLab administrators will receive an email notification of the situation. This notification is sent out no more than once a day. +## Disabling periodic checks + +You can disable the periodic checks by giving them an empty cron +schedule in gitlab.yml. + +``` +# For omnibus installations, in /etc/gitlab/gitlab.rb: +gitlab_rails['cron_jobs_repo_check_worker_cron'] = '' +``` + +``` +# For installations from source, in config/gitlab.yml: + cron_jobs: + repo_check_worker: + cron: "" +``` ## What to do if a check failed From 5cf56e56470e695b10db02dff70d0f0b50060518 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 6 Apr 2016 13:47:05 +0200 Subject: [PATCH 053/573] Rename almost all the things --- app/controllers/admin/projects_controller.rb | 18 +++++------ ...k_mailer.rb => repository_check_mailer.rb} | 2 +- app/views/admin/logs/show.html.haml | 2 +- app/views/admin/projects/index.html.haml | 12 +++---- app/views/admin/projects/show.html.haml | 18 +++++------ app/views/repo_check_mailer/notify.text.haml | 3 -- .../notify.html.haml | 2 +- .../repository_check_mailer/notify.text.haml | 3 ++ app/workers/admin_email_worker.rb | 6 ++-- ...k_worker.rb => repository_check_worker.rb} | 14 ++++----- ...r.rb => single_repository_check_worker.rb} | 8 ++--- config/gitlab.yml.example | 2 +- config/initializers/1_settings.rb | 6 ++-- config/routes.rb | 4 +-- .../20160315135439_project_add_repo_check.rb | 6 ---- ...0315135439_project_add_repository_check.rb | 6 ++++ db/schema.rb | 4 +-- .../{repo_checks.md => repository_checks.md} | 18 +++++------ ...k_logger.rb => repository_check_logger.rb} | 2 +- spec/features/admin/admin_projects_spec.rb | 24 +++++++------- ...pec.rb => repository_check_mailer_spec.rb} | 2 +- spec/workers/repo_check_worker_spec.rb | 31 ------------------- spec/workers/repository_check_worker_spec.rb | 31 +++++++++++++++++++ 23 files changed, 112 insertions(+), 112 deletions(-) rename app/mailers/{repo_check_mailer.rb => repository_check_mailer.rb} (89%) delete mode 100644 app/views/repo_check_mailer/notify.text.haml rename app/views/{repo_check_mailer => repository_check_mailer}/notify.html.haml (56%) create mode 100644 app/views/repository_check_mailer/notify.text.haml rename app/workers/{repo_check_worker.rb => repository_check_worker.rb} (67%) rename app/workers/{single_repo_check_worker.rb => single_repository_check_worker.rb} (72%) delete mode 100644 db/migrate/20160315135439_project_add_repo_check.rb create mode 100644 db/migrate/20160315135439_project_add_repository_check.rb rename doc/administration/{repo_checks.md => repository_checks.md} (72%) rename lib/gitlab/{repo_check_logger.rb => repository_check_logger.rb} (62%) rename spec/mailers/{repo_check_mailer_spec.rb => repository_check_mailer_spec.rb} (92%) delete mode 100644 spec/workers/repo_check_worker_spec.rb create mode 100644 spec/workers/repository_check_worker_spec.rb diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index b8c276fb1b..01257a6861 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,5 +1,5 @@ class Admin::ProjectsController < Admin::ApplicationController - before_action :project, only: [:show, :transfer, :repo_check] + before_action :project, only: [:show, :transfer, :repository_check] before_action :group, only: [:show, :transfer] def index @@ -8,7 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.with_push if params[:with_push].present? @projects = @projects.abandoned if params[:abandoned].present? - @projects = @projects.where(last_repo_check_failed: true) if params[:last_repo_check_failed].present? + @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present? @projects = @projects.non_archived unless params[:with_archived].present? @projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.sort(@sort = params[:sort]) @@ -31,24 +31,24 @@ class Admin::ProjectsController < Admin::ApplicationController redirect_to admin_namespace_project_path(@project.namespace, @project) end - def repo_check - SingleRepoCheckWorker.perform_async(@project.id) + def repository_check + SingleRepositoryCheckWorker.perform_async(@project.id) redirect_to( admin_namespace_project_path(@project.namespace, @project), - notice: 'Repo check was triggered' + notice: 'Repository check was triggered' ) end - def clear_repo_check_states + def clear_repository_check_states Project.update_all( - last_repo_check_failed: false, - last_repo_check_at: nil + last_repository_check_failed: false, + last_repository_check_at: nil ) redirect_to( admin_namespaces_projects_path, - notice: 'All project repo check states were cleared' + notice: 'All project states were cleared' ) end diff --git a/app/mailers/repo_check_mailer.rb b/app/mailers/repository_check_mailer.rb similarity index 89% rename from app/mailers/repo_check_mailer.rb rename to app/mailers/repository_check_mailer.rb index d98533f120..994054c876 100644 --- a/app/mailers/repo_check_mailer.rb +++ b/app/mailers/repository_check_mailer.rb @@ -1,4 +1,4 @@ -class RepoCheckMailer < BaseMailer +class RepositoryCheckMailer < BaseMailer include ActionView::Helpers::TextHelper def notify(failed_count) diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index abcc93f404..4b475a4d8f 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,7 +1,7 @@ - page_title "Logs" - loggers = [Gitlab::GitLogger, Gitlab::AppLogger, Gitlab::ProductionLogger, Gitlab::SidekiqLogger, - Gitlab::RepoCheckLogger] + Gitlab::RepositoryCheckLogger] %ul.nav-links.log-tabs - loggers.each do |klass| %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index ed360f2f01..c2bf065984 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -41,19 +41,19 @@ %fieldset %strong Problems .checkbox - = label_tag :last_repo_check_failed do - = check_box_tag :last_repo_check_failed, 1, params[:last_repo_check_failed] - %span Last repo check failed + = label_tag :last_repository_check_failed do + = check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed] + %span Last repository check failed = hidden_field_tag :sort, params[:sort] = button_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" - .panel.panel-default.repo-check-states + .panel.panel-default.repository-check-states .panel-heading - Repo check states + Repository check states .panel-body - = link_to 'Clear all', clear_repo_check_states_admin_namespace_projects_path(0), data: { confirm: 'This will clear repo check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove" + = link_to 'Clear all', clear_repository_check_states_admin_namespace_projects_path(0), data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove" %section.col-md-9 .panel.panel-default .panel-heading diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index a7a3f6349e..5bef8e3ad5 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -5,12 +5,12 @@ %i.fa.fa-pencil-square-o Edit %hr -- if @project.last_repo_check_failed? +- if @project.last_repository_check_failed? .row .col-md-12 .panel .panel-heading.alert.alert-danger - Last repo check failed. See + Last repository check failed. See = link_to 'repocheck.log', admin_logs_path for error messages. .row @@ -103,19 +103,19 @@ .col-sm-offset-2.col-sm-10 = f.submit 'Transfer', class: 'btn btn-primary' - .panel.panel-default.repo-check + .panel.panel-default.repository-check .panel-heading - Repo check + Repository check .panel-body - = form_for @project, url: repo_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f| + = form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f| .form-group - - if @project.last_repo_check_at.nil? + - if @project.last_repository_check_at.nil? This repository has never been checked. - else This repository was last checked - = @project.last_repo_check_at.to_s(:medium) + '.' + = @project.last_repository_check_at.to_s(:medium) + '.' The check - - if @project.last_repo_check_failed? + - if @project.last_repository_check_failed? = succeed '.' do %strong.cred failed See @@ -125,7 +125,7 @@ passed. .form-group - = f.submit 'Trigger repo check', class: 'btn btn-primary' + = f.submit 'Trigger repository check', class: 'btn btn-primary' .col-md-6 - if @group diff --git a/app/views/repo_check_mailer/notify.text.haml b/app/views/repo_check_mailer/notify.text.haml deleted file mode 100644 index bdf8c2ad67..0000000000 --- a/app/views/repo_check_mailer/notify.text.haml +++ /dev/null @@ -1,3 +0,0 @@ -#{@message}. -\ -View details: #{admin_namespaces_projects_url(last_repo_check_failed: 1)} diff --git a/app/views/repo_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml similarity index 56% rename from app/views/repo_check_mailer/notify.html.haml rename to app/views/repository_check_mailer/notify.html.haml index ef0016f9d3..df16f50357 100644 --- a/app/views/repo_check_mailer/notify.html.haml +++ b/app/views/repository_check_mailer/notify.html.haml @@ -2,4 +2,4 @@ #{@message}. %p - = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repo_check_failed: 1) + = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1) diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml new file mode 100644 index 0000000000..02f3f80288 --- /dev/null +++ b/app/views/repository_check_mailer/notify.text.haml @@ -0,0 +1,3 @@ +#{@message}. +\ +View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)} diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb index fcccb9ea66..667fff031d 100644 --- a/app/workers/admin_email_worker.rb +++ b/app/workers/admin_email_worker.rb @@ -4,9 +4,9 @@ class AdminEmailWorker sidekiq_options retry: false # this job auto-repeats via sidekiq-cron def perform - repo_check_failed_count = Project.where(last_repo_check_failed: true).count - return if repo_check_failed_count.zero? + repository_check_failed_count = Project.where(last_repository_check_failed: true).count + return if repository_check_failed_count.zero? - RepoCheckMailer.notify(repo_check_failed_count).deliver_now + RepositoryCheckMailer.notify(repository_check_failed_count).deliver_now end end diff --git a/app/workers/repo_check_worker.rb b/app/workers/repository_check_worker.rb similarity index 67% rename from app/workers/repo_check_worker.rb rename to app/workers/repository_check_worker.rb index 9be795309e..2d75c8bafd 100644 --- a/app/workers/repo_check_worker.rb +++ b/app/workers/repository_check_worker.rb @@ -1,4 +1,4 @@ -class RepoCheckWorker +class RepositoryCheckWorker include Sidekiq::Worker RUN_TIME = 3600 @@ -11,14 +11,14 @@ class RepoCheckWorker # This loop will break after a little more than one hour ('a little # more' because `git fsck` may take a few minutes), or if it runs out of # projects to check. By default sidekiq-cron will start a new - # RepoCheckWorker each hour so that as long as there are repositories to + # RepositoryCheckWorker each hour so that as long as there are repositories to # check, only one (or two) will be checked at a time. project_ids.each do |project_id| break if Time.now - start >= RUN_TIME next if !try_obtain_lease(project_id) - SingleRepoCheckWorker.new.perform(project_id) + SingleRepositoryCheckWorker.new.perform(project_id) end end @@ -29,16 +29,16 @@ class RepoCheckWorker # an array of IDs instead. def project_ids limit = 10_000 - never_checked_projects = Project.where('last_repo_check_at IS NULL').limit(limit). + never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit). pluck(:id) - old_check_projects = Project.where('last_repo_check_at < ?', 1.week.ago). - reorder('last_repo_check_at ASC').limit(limit).pluck(:id) + old_check_projects = Project.where('last_repository_check_at < ?', 1.week.ago). + reorder('last_repository_check_at ASC').limit(limit).pluck(:id) never_checked_projects + old_check_projects end def try_obtain_lease(id) lease = Gitlab::ExclusiveLease.new( - "project_repo_check:#{id}", + "project_repository_check:#{id}", timeout: RUN_TIME ) lease.try_obtain diff --git a/app/workers/single_repo_check_worker.rb b/app/workers/single_repository_check_worker.rb similarity index 72% rename from app/workers/single_repo_check_worker.rb rename to app/workers/single_repository_check_worker.rb index f8b245247c..d9eed9bd70 100644 --- a/app/workers/single_repo_check_worker.rb +++ b/app/workers/single_repository_check_worker.rb @@ -1,4 +1,4 @@ -class SingleRepoCheckWorker +class SingleRepositoryCheckWorker include Sidekiq::Worker sidekiq_options retry: false @@ -21,14 +21,14 @@ class SingleRepoCheckWorker output, status = Gitlab::Popen.popen(cmd) return true if status.zero? - Gitlab::RepoCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") + Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") false end def update(project, success:) project.update_columns( - last_repo_check_failed: !success, - last_repo_check_at: Time.now, + last_repository_check_failed: !success, + last_repository_check_at: Time.now, ) end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index cf20fc9c63..4fbef653bc 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -157,7 +157,7 @@ production: &base cron: "0 0 * * *" # Periodically run 'git fsck' on all repositories. If started more than # once per hour you will have concurrent 'git fsck' jobs. - repo_check_worker: + repository_check_worker: cron: "20 * * * *" # Send admin emails once a day admin_email_worker: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8240978ef0..23771553c4 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -239,9 +239,9 @@ Settings['cron_jobs'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker' -Settings.cron_jobs['repo_check_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['repo_check_worker']['cron'] ||= '20 * * * *' -Settings.cron_jobs['repo_check_worker']['job_class'] = 'RepoCheckWorker' +Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['repository_check_worker']['cron'] ||= '20 * * * *' +Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheckWorker' Settings.cron_jobs['admin_email_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker' diff --git a/config/routes.rb b/config/routes.rb index fad8600b77..c0ed99b196 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -264,11 +264,11 @@ Rails.application.routes.draw do member do put :transfer - post :repo_check + post :repository_check end collection do - put :clear_repo_check_states + put :clear_repository_check_states end resources :runner_projects diff --git a/db/migrate/20160315135439_project_add_repo_check.rb b/db/migrate/20160315135439_project_add_repo_check.rb deleted file mode 100644 index ba7ddc9ecb..0000000000 --- a/db/migrate/20160315135439_project_add_repo_check.rb +++ /dev/null @@ -1,6 +0,0 @@ -class ProjectAddRepoCheck < ActiveRecord::Migration - def change - add_column :projects, :last_repo_check_failed, :boolean, default: false - add_column :projects, :last_repo_check_at, :datetime - end -end diff --git a/db/migrate/20160315135439_project_add_repository_check.rb b/db/migrate/20160315135439_project_add_repository_check.rb new file mode 100644 index 0000000000..5a0859a30b --- /dev/null +++ b/db/migrate/20160315135439_project_add_repository_check.rb @@ -0,0 +1,6 @@ +class ProjectAddRepositoryCheck < ActiveRecord::Migration + def change + add_column :projects, :last_repository_check_failed, :boolean, default: false + add_column :projects, :last_repository_check_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index d2c183f968..5350995688 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -732,8 +732,8 @@ ActiveRecord::Schema.define(version: 20160331133914) do t.boolean "public_builds", default: true, null: false t.string "main_language" t.integer "pushes_since_gc", default: 0 - t.boolean "last_repo_check_failed", default: false - t.datetime "last_repo_check_at" + t.boolean "last_repository_check_failed", default: false + t.datetime "last_repository_check_at" end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree diff --git a/doc/administration/repo_checks.md b/doc/administration/repository_checks.md similarity index 72% rename from doc/administration/repo_checks.md rename to doc/administration/repository_checks.md index 81087f3ac1..77f28209f2 100644 --- a/doc/administration/repo_checks.md +++ b/doc/administration/repository_checks.md @@ -1,4 +1,4 @@ -# Repo checks +# Repository checks _**Note:** This feature was [introduced][ce-3232] in GitLab 8.7_ @@ -13,10 +13,10 @@ output on the admin log page under 'repocheck.log'. ## Periodical checks -GitLab periodically runs a repo check on all project repositories and +GitLab periodically runs a repository check on all project repositories and wiki repositories in order to detect data corruption problems. A project will be checked no more than once per week. If any projects -fail their repo checks all GitLab administrators will receive an email +fail their repository checks all GitLab administrators will receive an email notification of the situation. This notification is sent out no more than once a day. @@ -27,27 +27,27 @@ schedule in gitlab.yml. ``` # For omnibus installations, in /etc/gitlab/gitlab.rb: -gitlab_rails['cron_jobs_repo_check_worker_cron'] = '' +gitlab_rails['cron_jobs_repository_check_worker_cron'] = '' ``` ``` # For installations from source, in config/gitlab.yml: cron_jobs: - repo_check_worker: + repository_check_worker: cron: "" ``` ## What to do if a check failed -If the repo check fails for some repository you should look up the error +If the repository check fails for some repository you should look up the error in repocheck.log (in the admin panel or on disk; see `/var/log/gitlab/gitlab-rails` for Omnibus installations or `/home/git/gitlab/log` for installations from source). Once you have -resolved the issue use the admin panel to trigger a new repo check on +resolved the issue use the admin panel to trigger a new repository check on the project. This will clear the 'check failed' state. -If for some reason the periodical repo check caused a lot of false -alarms you can choose to clear ALL repo check states from the admin +If for some reason the periodical repository check caused a lot of false +alarms you can choose to clear ALL repository check states from the admin project index page. --- diff --git a/lib/gitlab/repo_check_logger.rb b/lib/gitlab/repository_check_logger.rb similarity index 62% rename from lib/gitlab/repo_check_logger.rb rename to lib/gitlab/repository_check_logger.rb index 9409f68722..485b596ca5 100644 --- a/lib/gitlab/repo_check_logger.rb +++ b/lib/gitlab/repository_check_logger.rb @@ -1,5 +1,5 @@ module Gitlab - class RepoCheckLogger < Gitlab::Logger + class RepositoryCheckLogger < Gitlab::Logger def self.file_name_noext 'repocheck' end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index e3991d48ed..95a230a72c 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -33,33 +33,33 @@ describe "Admin Projects", feature: true do end end - feature 'repo checks' do - scenario 'trigger repo check' do + feature 'repository checks' do + scenario 'trigger repository check' do visit_admin_project_page - page.within('.repo-check') do - click_button 'Trigger repo check' + page.within('.repository-check') do + click_button 'Trigger repository check' end - expect(page).to have_content('Repo check was triggered') + expect(page).to have_content('Repository check was triggered') end - scenario 'see failed repo check' do - @project.update_column(:last_repo_check_failed, true) + scenario 'see failed repository check' do + @project.update_column(:last_repository_check_failed, true) visit_admin_project_page - expect(page).to have_content('Last repo check failed') + expect(page).to have_content('Last repository check failed') end - scenario 'clear repo checks', js: true do - @project.update_column(:last_repo_check_failed, true) + scenario 'clear repository checks', js: true do + @project.update_column(:last_repository_check_failed, true) visit admin_namespaces_projects_path - page.within('.repo-check-states') do + page.within('.repository-check-states') do click_link 'Clear all' # pop-up should be auto confirmed end - expect(@project.reload.last_repo_check_failed).to eq(false) + expect(@project.reload.last_repository_check_failed).to eq(false) end end diff --git a/spec/mailers/repo_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb similarity index 92% rename from spec/mailers/repo_check_mailer_spec.rb rename to spec/mailers/repository_check_mailer_spec.rb index d49a6ae0c0..6ae9a93aaa 100644 --- a/spec/mailers/repo_check_mailer_spec.rb +++ b/spec/mailers/repository_check_mailer_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe RepoCheckMailer do +describe RepositoryCheckMailer do include EmailSpec::Matchers describe '.notify' do diff --git a/spec/workers/repo_check_worker_spec.rb b/spec/workers/repo_check_worker_spec.rb deleted file mode 100644 index 7ef3eba9ac..0000000000 --- a/spec/workers/repo_check_worker_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -describe RepoCheckWorker do - subject { RepoCheckWorker.new } - - it 'prefers projects that have never been checked' do - projects = 3.times.map { create(:project) } - projects[0].update_column(:last_repo_check_at, 1.month.ago) - projects[2].update_column(:last_repo_check_at, 3.weeks.ago) - - expect(subject.perform).to eq(projects.values_at(1, 0, 2).map(&:id)) - end - - it 'sorts projects by last_repo_check_at' do - projects = 3.times.map { create(:project) } - projects[0].update_column(:last_repo_check_at, 2.weeks.ago) - projects[1].update_column(:last_repo_check_at, 1.month.ago) - projects[2].update_column(:last_repo_check_at, 3.weeks.ago) - - expect(subject.perform).to eq(projects.values_at(1, 2, 0).map(&:id)) - end - - it 'excludes projects that were checked recently' do - projects = 3.times.map { create(:project) } - projects[0].update_column(:last_repo_check_at, 2.days.ago) - projects[1].update_column(:last_repo_check_at, 1.month.ago) - projects[2].update_column(:last_repo_check_at, 3.days.ago) - - expect(subject.perform).to eq([projects[1].id]) - end -end diff --git a/spec/workers/repository_check_worker_spec.rb b/spec/workers/repository_check_worker_spec.rb new file mode 100644 index 0000000000..d1849321f5 --- /dev/null +++ b/spec/workers/repository_check_worker_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe RepositoryCheckWorker do + subject { RepositoryCheckWorker.new } + + it 'prefers projects that have never been checked' do + projects = 3.times.map { create(:project) } + projects[0].update_column(:last_repository_check_at, 1.month.ago) + projects[2].update_column(:last_repository_check_at, 3.weeks.ago) + + expect(subject.perform).to eq(projects.values_at(1, 0, 2).map(&:id)) + end + + it 'sorts projects by last_repository_check_at' do + projects = 3.times.map { create(:project) } + projects[0].update_column(:last_repository_check_at, 2.weeks.ago) + projects[1].update_column(:last_repository_check_at, 1.month.ago) + projects[2].update_column(:last_repository_check_at, 3.weeks.ago) + + expect(subject.perform).to eq(projects.values_at(1, 2, 0).map(&:id)) + end + + it 'excludes projects that were checked recently' do + projects = 3.times.map { create(:project) } + projects[0].update_column(:last_repository_check_at, 2.days.ago) + projects[1].update_column(:last_repository_check_at, 1.month.ago) + projects[2].update_column(:last_repository_check_at, 3.days.ago) + + expect(subject.perform).to eq([projects[1].id]) + end +end From 15044e7d857138b31199b796f02a81f0c29c643f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Apr 2016 15:00:20 +0200 Subject: [PATCH 054/573] refactored a few things based on MR feedback --- app/models/project.rb | 13 +++++++++++++ db/schema.rb | 8 ++++---- lib/gitlab/bitbucket_import/project_creator.rb | 5 +---- lib/gitlab/fogbugz_import/importer.rb | 10 +++++----- lib/gitlab/fogbugz_import/project_creator.rb | 7 +------ lib/gitlab/google_code_import/project_creator.rb | 4 +--- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 7b1188420e..17b971b9d3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -424,6 +424,19 @@ class Project < ActiveRecord::Base project_import_data.save end + def create_or_update_import_data(credentials) + project_import_data = import_data || build_import_data + project_import_data.credentials ||= {} + project_import_data.credentials = project_import_data.credentials.merge(credentials) + project_import_data.save + end + + def update_import_data(data: nil, credentials: nil) + import_data.data = data if data + import_data.credentials = import_data.credentials.merge(credentials) if credentials + import_data.save + end + def import? external_import? || forked? end diff --git a/db/schema.rb b/db/schema.rb index 5d87dbfe41..53d12aa35d 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: 20160331223143) do +ActiveRecord::Schema.define(version: 20160320204112) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -44,6 +44,7 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.datetime "updated_at" t.string "home_page_url" t.integer "default_branch_protection", default: 2 + t.boolean "twitter_sharing_enabled", default: true t.text "restricted_visibility_levels" t.boolean "version_check_enabled", default: true t.integer "max_attachment_size", default: 10, null: false @@ -416,9 +417,9 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.boolean "confidential", default: false - t.datetime "deleted_at" t.integer "moved_to_id" + t.boolean "confidential", default: false + t.datetime "deleted_at" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -747,7 +748,6 @@ ActiveRecord::Schema.define(version: 20160331223143) do add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} - add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index 109010cb96..65b62b2b81 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -23,10 +23,7 @@ module Gitlab import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", ).execute - import_data = project.import_data - # merge! with a bang doesn't work here - import_data.credentials = import_data.credentials.merge(bb_session: session_data) - import_data.save + project.update_import_data(credentials: { bb_session: session_data }) project end diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 42f9b6eab8..501d5a9554 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -18,7 +18,7 @@ module Gitlab def execute return true unless repo.valid? - client = Gitlab::FogbugzImport::Client.new(token: import_data_credentials[:fb_session][:token], uri: import_data_credentials[:fb_session][:uri]) + client = Gitlab::FogbugzImport::Client.new(token: fb_session[:token], uri: fb_session[:uri]) @cases = client.cases(@repo.id.to_i) @categories = client.categories @@ -30,8 +30,8 @@ module Gitlab private - def import_data_credentials - @import_data_credentials ||= project.import_data.credentials if project.import_data + def fb_session + @import_data_credentials ||= project.import_data.credentials[:fb_session] if project.import_data && project.import_data.credentials end def user_map @@ -240,8 +240,8 @@ module Gitlab end def build_attachment_url(rel_url) - uri = import_data_credentials[:fb_session][:uri] - token = import_data_credentials[:fb_session][:token] + uri = fb_session[:uri] + token = fb_session[:token] "#{uri}/#{rel_url}&token=#{token}" end diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index e9fac8968e..c000b30046 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -24,12 +24,7 @@ module Gitlab import_url: Project::UNKNOWN_IMPORT_URL ).execute - import_data = project.import_data - import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } - - # merge! with a bang doesn't work here - import_data.credentials = import_data.credentials.merge(fb_session: fb_session) - import_data.save + project.update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session }) project end diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb index 49d6013af2..d2e20afbb1 100644 --- a/lib/gitlab/google_code_import/project_creator.rb +++ b/lib/gitlab/google_code_import/project_creator.rb @@ -24,9 +24,7 @@ module Gitlab import_url: repo.import_url ).execute - import_data = project.import_data - import_data.data = { 'repo' => repo.raw_data, 'user_map' => user_map } - import_data.save + project.update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }) project end From a1a1d1f7de71f46787f12f1efa23346a2a6b1c29 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Apr 2016 15:08:38 +0200 Subject: [PATCH 055/573] refactored create_or_update_import_data --- app/models/project.rb | 23 ++++++------------- .../bitbucket_import/project_creator.rb | 2 +- lib/gitlab/fogbugz_import/project_creator.rb | 2 +- .../google_code_import/project_creator.rb | 2 +- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 17b971b9d3..273b04c632 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -404,7 +404,7 @@ class Project < ActiveRecord::Base def import_url=(value) import_url = Gitlab::ImportUrl.new(value) - create_or_update_import_data(import_url.credentials) + create_or_update_import_data(credentials: import_url.credentials) super(import_url.sanitized_url) end @@ -417,26 +417,17 @@ class Project < ActiveRecord::Base end end - def create_or_update_import_data(credentials) + def create_or_update_import_data(data: nil, credentials: nil) project_import_data = import_data || build_import_data - project_import_data.credentials ||= {} - project_import_data.credentials = project_import_data.credentials.merge(credentials) - project_import_data.save - end + project_import_data.data = data if data + if credentials + project_import_data.credentials ||= {} + project_import_data.credentials = project_import_data.credentials.merge(credentials) + end - def create_or_update_import_data(credentials) - project_import_data = import_data || build_import_data - project_import_data.credentials ||= {} - project_import_data.credentials = project_import_data.credentials.merge(credentials) project_import_data.save end - def update_import_data(data: nil, credentials: nil) - import_data.data = data if data - import_data.credentials = import_data.credentials.merge(credentials) if credentials - import_data.save - end - def import? external_import? || forked? end diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index 65b62b2b81..941f818b84 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -23,7 +23,7 @@ module Gitlab import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", ).execute - project.update_import_data(credentials: { bb_session: session_data }) + project.create_or_update_import_data(credentials: { bb_session: session_data }) project end diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index c000b30046..3840765db8 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -24,7 +24,7 @@ module Gitlab import_url: Project::UNKNOWN_IMPORT_URL ).execute - project.update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session }) + project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session }) project end diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb index d2e20afbb1..0abb7a64c1 100644 --- a/lib/gitlab/google_code_import/project_creator.rb +++ b/lib/gitlab/google_code_import/project_creator.rb @@ -24,7 +24,7 @@ module Gitlab import_url: repo.import_url ).execute - project.update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }) + project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }) project end From 0ceee903f14d1824aa56452b83ae4a8e090cbc14 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Apr 2016 15:49:46 +0200 Subject: [PATCH 056/573] fix schema file [ci skip] --- db/schema.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 53d12aa35d..df4c65a062 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: 20160320204112) do +ActiveRecord::Schema.define(version: 20160331133914) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -418,7 +418,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do t.integer "iid" t.integer "updated_by_id" t.integer "moved_to_id" - t.boolean "confidential", default: false + t.boolean "confidential", default: false t.datetime "deleted_at" end @@ -748,6 +748,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} + add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree From 350a9aa984100a4d91454200fb4ac308108e5cef Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Apr 2016 16:53:15 +0200 Subject: [PATCH 057/573] fix create_or_update_import_data --- app/models/project.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 273b04c632..6304699386 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -419,7 +419,10 @@ class Project < ActiveRecord::Base def create_or_update_import_data(data: nil, credentials: nil) project_import_data = import_data || build_import_data - project_import_data.data = data if data + if data + project_import_data.data ||= {} + project_import_data.data = project_import_data.data.merge(data) + end if credentials project_import_data.credentials ||= {} project_import_data.credentials = project_import_data.credentials.merge(credentials) From 45c93b52e497c6c3fc10272623fc7f38f11e9079 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 7 Apr 2016 21:22:58 -0500 Subject: [PATCH 058/573] Todos spec --- spec/features/todos/todos_spec.rb | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 spec/features/todos/todos_spec.rb diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb new file mode 100644 index 0000000000..214daf88fc --- /dev/null +++ b/spec/features/todos/todos_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe 'Dashboard Todos', feature: true do + let(:user){ create(:user) } + let(:author){ create(:user) } + let(:project){ create(:project) } + let(:issue){ create(:issue) } + let(:todos_per_page){ Todo.default_per_page } + let(:todos_total){ todos_per_page + 1 } + + describe 'GET /dashboard/todos' do + context 'User do not have todos' do + before do + login_as(user) + visit dashboard_todos_path + end + it 'shows "All done" message' do + expect(page).to have_content "You're all done!" + end + end + + context 'User has a todo', js: true do + before do + create(:todo, :mentioned, user: user, project: project, target: issue, author: author) + login_as(user) + visit dashboard_todos_path + end + + it 'todo is present' do + expect(page).to have_selector('.todos-list .todo', count: 1) + end + + describe 'deleting the todo' do + before do + first('.done-todo').click + end + + it 'is removed from the list' do + expect(page).not_to have_selector('.todos-list .todo') + end + + it 'shows "All done" message' do + expect(page).to have_content("You're all done!") + end + end + end + + context 'User has multiple pages of Todos' do + let(:todo_total_pages){ (todos_total.to_f/todos_per_page).ceil } + + before do + todos_total.times do + create(:todo, :mentioned, user: user, project: project, target: issue, author: author) + end + + login_as(user) + visit dashboard_todos_path + end + + it 'is paginated' do + expect(page).to have_selector('.gl-pagination') + end + + it 'is has the right number of pages' do + expect(page).to have_selector('.gl-pagination .page', count: todo_total_pages) + end + + describe 'deleting last todo from last page', js: true do + it 'redirects to the previous page' do + page.within('.gl-pagination') do + click_link todo_total_pages.to_s + end + first('.done-todo').click + expect(page).to have_content(Todo.last.body) + end + end + end + end +end From 1266f41e28ae7a8da9f85f0ede70291f34ed7988 Mon Sep 17 00:00:00 2001 From: Christoph Junghans Date: Sat, 9 Apr 2016 11:59:12 -0600 Subject: [PATCH 059/573] Add test coverage parsing example for gcvor --- app/views/projects/_builds_settings.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml index 9ae6964aaa..b6074373e2 100644 --- a/app/views/projects/_builds_settings.html.haml +++ b/app/views/projects/_builds_settings.html.haml @@ -52,6 +52,9 @@ %li phpunit --coverage-text --colors=never (PHP) - %code ^\s*Lines:\s*\d+.\d+\% + %li + gcovr (C/C++) - + %code ^TOTAL.*\s+(\d+\%)$ .form-group .col-sm-offset-2.col-sm-10 From 5ffa8f057095fb2fe12a60ffa0dd3a611d2f1aeb Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 9 Apr 2016 18:40:15 -0400 Subject: [PATCH 060/573] Escape the query argument provided to `git grep` by `search_files` Closes #14963. --- app/models/repository.rb | 2 +- spec/models/repository_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 8dead3a588..090cccd2c7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -795,7 +795,7 @@ class Repository def search_files(query, ref) offset = 2 - args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref}) + args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref}) Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4e49c413f2..bce30aafc4 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -94,6 +94,12 @@ describe Repository, models: true do it { is_expected.to be_an Array } + it 'regex-escapes the query string' do + results = repository.search_files("test\\", 'master') + + expect(results.first).not_to start_with('fatal:') + end + describe 'result' do subject { results.first } From a153a1d6fb0aa41de08e07ecfe7e73ccef4dacc2 Mon Sep 17 00:00:00 2001 From: connorshea Date: Sun, 10 Apr 2016 14:21:02 -0600 Subject: [PATCH 061/573] Upgrade jQuery Rails from 4.0.5 to 4.1.1 Upgrades jQuery to 1.12.1 and jquery-ujs to 1.2.1. Changelogs: https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md https://blog.jquery.com/2016/01/08/jquery-2-2-and-1-12-released/ https://github.com/rails/jquery-ujs/releases --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 298cfd260b..d8e35c847b 100644 --- a/Gemfile +++ b/Gemfile @@ -214,7 +214,7 @@ gem 'font-awesome-rails', '~> 4.2' gem 'gitlab_emoji', '~> 0.3.0' gem 'gon', '~> 6.0.1' gem 'jquery-atwho-rails', '~> 1.3.2' -gem 'jquery-rails', '~> 4.0.0' +gem 'jquery-rails', '~> 4.1.0' gem 'jquery-scrollto-rails', '~> 1.4.3' gem 'jquery-ui-rails', '~> 5.0.0' gem 'raphael-rails', '~> 2.1.2' diff --git a/Gemfile.lock b/Gemfile.lock index 28b71ac7bc..4c3d7c1f69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -431,8 +431,8 @@ GEM json ipaddress (0.8.2) jquery-atwho-rails (1.3.2) - jquery-rails (4.0.5) - rails-dom-testing (~> 1.0) + jquery-rails (4.1.1) + rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-scrollto-rails (1.4.3) @@ -951,7 +951,7 @@ DEPENDENCIES httparty (~> 0.13.3) influxdb (~> 0.2) jquery-atwho-rails (~> 1.3.2) - jquery-rails (~> 4.0.0) + jquery-rails (~> 4.1.0) jquery-scrollto-rails (~> 1.4.3) jquery-turbolinks (~> 2.1.0) jquery-ui-rails (~> 5.0.0) From be834c4de93a7716035f5373210ea3922c26da72 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 11 Apr 2016 11:13:51 +0200 Subject: [PATCH 062/573] changed a few things based on feedback --- app/models/project_import_data.rb | 8 +++++-- ...port_credentials_to_project_import_data.rb | 4 ++-- ...8_remove_wrong_import_url_from_projects.rb | 21 ++++++++----------- spec/factories/project_import_data.rb | 6 ------ 4 files changed, 17 insertions(+), 22 deletions(-) delete mode 100644 spec/factories/project_import_data.rb diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index a099431200..79efb40305 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -12,7 +12,11 @@ require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project - attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, mode: :per_attribute_iv_and_salt + attr_encrypted :credentials, + key: Gitlab::Application.secrets.db_key_base, + marshal: true, + encode: true, + mode: :per_attribute_iv_and_salt serialize :data, JSON @@ -21,7 +25,7 @@ class ProjectImportData < ActiveRecord::Base before_validation :symbolize_credentials def symbolize_credentials - # bang doesn't work here + # bang doesn't work here - attr_encrypted makes it not to work self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank? end end diff --git a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb index dd2b361398..ffcd64266e 100644 --- a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb +++ b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb @@ -1,7 +1,7 @@ class AddImportCredentialsToProjectImportData < ActiveRecord::Migration def change add_column :project_import_data, :encrypted_credentials, :text - add_column :project_import_data, :encrypted_credentials_iv, :text - add_column :project_import_data, :encrypted_credentials_salt, :text + add_column :project_import_data, :encrypted_credentials_iv, :string + add_column :project_import_data, :encrypted_credentials_salt, :string end end diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index 93e040fce2..dd2842b835 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -3,7 +3,7 @@ # #down method not supported class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration - class FakeProjectImportData + class ProjectImportDataFake extend AttrEncrypted attr_accessor :credentials attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt @@ -13,14 +13,11 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration say("Encrypting and migrating project import credentials...") # This should cover GitHub, GitLab, Bitbucket user:password, token@domain, and other similar URLs. - say("Projects and GitHub projects with a wrong URL. It also migrates GitLab project credentials.") - in_transaction { process_projects_with_wrong_url } + in_transaction(message: "Projects including GitHub and GitLab projects with an unsecured URL.") { process_projects_with_wrong_url } - say("Migrating Bitbucket credentials...") - in_transaction { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) } + in_transaction(message: "Migrating Bitbucket credentials...") { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) } - say("Migrating FogBugz credentials...") - in_transaction { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) } + in_transaction(message: "Migrating FogBugz credentials...") { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) } end @@ -33,7 +30,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end end - def process_project(import_type: , credentials_keys: []) + def process_project(import_type:, credentials_keys: []) unencrypted_import_data(import_type: import_type).each do |data| replace_data_credentials(data, credentials_keys) end @@ -56,8 +53,8 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration new_data_hash.deep_symbolize_keys end - def in_transaction - say_with_time("Processing new transaction...") do + def in_transaction(message:) + say_with_time(message) do ActiveRecord::Base.transaction do yield end @@ -65,7 +62,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def update_import_data(import_url, project) - fake_import_data = FakeProjectImportData.new + fake_import_data = ProjectImportDataFake.new fake_import_data.credentials = import_url.credentials import_data_id = project['import_data_id'] if import_data_id @@ -76,7 +73,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end def update_with_encrypted_data(data_hash, import_data_id, unencrypted_data = ' NULL ') - fake_import_data = FakeProjectImportData.new + fake_import_data = ProjectImportDataFake.new fake_import_data.credentials = data_hash execute(update_import_data_sql(import_data_id, fake_import_data, unencrypted_data)) end diff --git a/spec/factories/project_import_data.rb b/spec/factories/project_import_data.rb deleted file mode 100644 index 9e08d5a22e..0000000000 --- a/spec/factories/project_import_data.rb +++ /dev/null @@ -1,6 +0,0 @@ -FactoryGirl.define do - factory :project_import_data, class: ProjectImportData do - data "test" - project - end -end From fb63173df2bf81c155ed311d2c8fc5889a5faf1d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 11 Apr 2016 11:28:35 +0200 Subject: [PATCH 063/573] typo --- .../20160302152808_remove_wrong_import_url_from_projects.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index dd2842b835..b97b79f920 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -110,7 +110,7 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration #GitHub projects with token, and any user:password@ based URL #TODO: may need to add import_type != list def projects_with_wrong_import_url - select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.id WHERE p.import_url IS NOT NULL AND p.import_url LIKE '%//%@%'") + select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_url LIKE '%//%@%'") end # All imports with data for import_type From 3122e5cc847b224d5531f23a9c2fe9bc2043b01f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 11 Apr 2016 12:35:26 +0300 Subject: [PATCH 064/573] Add link to Runner security doc [ci skip] --- doc/install/requirements.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 03433a72fa..eb9fe5e1b1 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -81,13 +81,23 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro ## Gitlab Runner -We strongly advise against installing GitLab Runner on the same machine you plan to install GitLab on. Depending on how you decide to configure GitLab Runner and what tools you use to exercise your application in the CI environment, GitLab Runner can consume significant amount of available memory. +We strongly advise against installing GitLab Runner on the same machine you plan +to install GitLab on. Depending on how you decide to configure GitLab Runner and +what tools you use to exercise your application in the CI environment, GitLab +Runner can consume significant amount of available memory. -Memory consumption calculations, that are available above, will not be valid if you decide to run GitLab Runner and GitLab web application on the same machine. +Memory consumption calculations, that are available above, will not be valid if +you decide to run GitLab Runner and the GitLab Rails application on the same +machine. -It is also not safe to install everything on a single machine, because of the security reasons - especially when you plan to use shell executor with GitLab Runner. +It is also not safe to install everything on a single machine, because of the +[security reasons] - especially when you plan to use shell executor with GitLab +Runner. -We recommend using a separate machine for each GitLab Runner, if you plan to use CI features. +We recommend using a separate machine for each GitLab Runner, if you plan to +use the CI features. + +[security reasons]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md ## Unicorn Workers From d86190eead0075d1b0ba7e73ff2efa8a610b5398 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 11 Apr 2016 12:41:27 +0300 Subject: [PATCH 065/573] Add a note about installing Runners in ci/runners/README.md --- doc/ci/runners/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 295d953db1..36e90e2141 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -7,6 +7,10 @@ through the coordinator API of GitLab CI. A runner can be specific to a certain project or serve any project in GitLab CI. A runner that serves all projects is called a shared runner. +Ideally, GitLab Runner should not be installed on the same machine as GitLab. +Read the [requirements documentation](../../install/requirements.md#gitlab-runner) +for more information. + ## Shared vs. Specific Runners A runner that is specific only runs for the specified project. A shared runner @@ -140,7 +144,7 @@ to it. This means that if you have shared runners setup for a project and someone forks that project, the shared runners will also serve jobs of this project. -# Attack vectors in runners +## Attack vectors in Runners Mentioned briefly earlier, but the following things of runners can be exploited. We're always looking for contributions that can mitigate these [Security Considerations](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md). From 667d44c25ccefb511fc0d206eaa5990117032236 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 11 Apr 2016 12:46:19 +0200 Subject: [PATCH 066/573] Fix high CPU usage when PostReceive receives refs/merge-requests/ --- CHANGELOG | 1 + app/workers/post_receive.rb | 2 +- spec/workers/post_receive_spec.rb | 43 ++++++++++++++++++++++++++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3561c541df..6a196dd9dc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.7.0 (unreleased) - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Better errors handling when creating milestones inside groups + - Fix high CPU usage when PostReceive receives refs/merge-requests/ - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu) diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 3cc232ef1a..9e1215b21a 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -40,7 +40,7 @@ class PostReceive if Gitlab::Git.tag_ref?(ref) GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref) - else + elsif Gitlab::Git.branch_ref?(ref) GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute end end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 0265dbe9c6..94ff345790 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -4,6 +4,9 @@ describe PostReceive do let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" } let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") } let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) } + let(:project) { create(:project) } + let(:key) { create(:key, user: project.owner) } + let(:key_id) { key.shell_id } context "as a resque worker" do it "reponds to #perform" do @@ -11,11 +14,43 @@ describe PostReceive do end end - context "webhook" do - let(:project) { create(:project) } - let(:key) { create(:key, user: project.owner) } - let(:key_id) { key.shell_id } + describe "#process_project_changes" do + before do + allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner) + end + context "branches" do + let(:changes) { "123456 789012 refs/heads/tést" } + + it "should call GitTagPushService" do + expect_any_instance_of(GitPushService).to receive(:execute).and_return(true) + expect_any_instance_of(GitTagPushService).not_to receive(:execute) + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + end + + context "tags" do + let(:changes) { "123456 789012 refs/tags/tag" } + + it "should call GitTagPushService" do + expect_any_instance_of(GitPushService).not_to receive(:execute) + expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true) + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + end + + context "merge-requests" do + let(:changes) { "123456 789012 refs/merge-requests/123" } + + it "should not call any of the services" do + expect_any_instance_of(GitPushService).not_to receive(:execute) + expect_any_instance_of(GitTagPushService).not_to receive(:execute) + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + end + end + + context "webhook" do it "fetches the correct project" do expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project) PostReceive.new.perform(pwd(project), key_id, base64_changes) From de4d98fd120fd43bd744abc116c62708577b5673 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 11 Apr 2016 15:19:11 +0100 Subject: [PATCH 067/573] fix bug causing comment form in issue to submit twice when CTRL+Enter is pressed twice --- app/assets/javascripts/behaviors/quick_submit.js.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee index 6e29d37426..3cb96bacaa 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js.coffee +++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee @@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> e.preventDefault() $form = $(e.target).closest('form') - $form.find('input[type=submit], button[type=submit]').disable() + $submit_button = $form.find('input[type=submit], button[type=submit]') + + return if $submit_button.attr('disabled') + + $submit_button.disable() $form.submit() # If the user tabs to a submit button on a `js-quick-submit` form, display a From 979dedba8a68f33b8e2078f7e2980bf048a8a25a Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 11 Apr 2016 16:24:49 +0100 Subject: [PATCH 068/573] make milestone labels in labels tab similar to that of the labels page --- app/assets/stylesheets/pages/labels.scss | 25 +++++++++++++------ .../shared/milestones/_labels_tab.html.haml | 13 +++++----- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 3e0a3140be..da20fa2880 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -79,19 +79,30 @@ color: $white-light; } +@mixin labels-mobile { + @media (max-width: $screen-xs-min) { + display: block; + width: 100%; + margin-left: 0; + padding: 10px 0; + } +} + + .manage-labels-list { - .prepend-left-10 { + .prepend-left-10, .prepend-description-left { display: inline-block; width: 40%; vertical-align: middle; - @media (max-width: $screen-xs-min) { - display: block; - width: 100%; - margin-left: 0; - padding: 10px 0; - } + @include labels-mobile; + } + + .prepend-description-left { + width: 57%; + + @include labels-mobile; } .pull-info-right { diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml index 868b235700..b15e8ea73f 100644 --- a/app/views/shared/milestones/_labels_tab.html.haml +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -4,15 +4,16 @@ %li %span.label-row - = link_to milestones_label_path(options) do - - render_colored_label(label, tooltip: false) - %span.prepend-left-10 + %span.label-name + = link_to milestones_label_path(options) do + - render_colored_label(label, tooltip: false) + %span.prepend-description-left = markdown(label.description, pipeline: :single_line) - .pull-right - %strong.issues-count + .pull-info-right + %span.append-right-20 = link_to milestones_label_path(options.merge(state: 'opened')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue' - %strong.issues-count + %span.append-right-20 = link_to milestones_label_path(options.merge(state: 'closed')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue' From 450a39ededbf93d0bfcec1d4774c3562b87fc190 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 17:04:01 +0100 Subject: [PATCH 069/573] Fixed alignment on issuable new form Fixes #13802 --- app/assets/stylesheets/pages/issuable.scss | 6 ++++++ app/views/shared/issuable/_form.html.haml | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 88c1b614c7..999b9a2e79 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -316,3 +316,9 @@ color: #8c8c8c; } } + +.issuable-form-padding-top { + @media (min-width: $screen-sm-min) { + padding-top: 7px; + } +} diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 757a3812de..1c89a2ee7f 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -70,13 +70,13 @@ - if can? current_user, :admin_milestone, issuable.project = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank .form-group + - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: 'control-label' - .col-sm-10 - - if issuable.project.labels.any? + .col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) } + - if has_labels = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" } - else - .prepend-top-10 %span.light No labels yet.   - if can? current_user, :admin_label, issuable.project From 3cfbd2b1601842feea0a7739098a90e2d1dccec4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 18:27:34 +0100 Subject: [PATCH 070/573] Added loading icon to import buttons See #14488 for more details --- .../javascripts/importer_status.js.coffee | 37 +++++++++++++------ app/assets/stylesheets/pages/import.scss | 21 +++++++++++ app/views/import/bitbucket/status.html.haml | 20 +++++++--- app/views/import/fogbugz/status.html.haml | 15 ++++++-- app/views/import/github/status.html.haml | 15 ++++++-- app/views/import/gitlab/status.html.haml | 15 ++++++-- app/views/import/gitorious/status.html.haml | 15 ++++++-- app/views/import/google_code/status.html.haml | 19 +++++++--- 8 files changed, 119 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index be8d225e73..c51c4a1a18 100644 --- a/app/assets/javascripts/importer_status.js.coffee +++ b/app/assets/javascripts/importer_status.js.coffee @@ -4,18 +4,33 @@ class @ImporterStatus this.setAutoUpdate() initStatusPage: -> - $(".js-add-to-import").click (event) => - new_namespace = null - tr = $(event.currentTarget).closest("tr") - id = tr.attr("id").replace("repo_", "") - if tr.find(".import-target input").length > 0 - new_namespace = tr.find(".import-target input").prop("value") - tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) - $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' + $(".js-add-to-import") + .off 'click' + .on 'click', (event) => + new_namespace = null + $btn = $(event.currentTarget) + $tr = $btn.closest("tr") + id = $tr.attr("id").replace("repo_", "") + if $tr.find(".import-target input").length > 0 + new_namespace = $tr.find(".import-target input").prop("value") + $tr.find(".import-target").empty().append(new_namespace + "/" + $tr.find(".import-target").data("project_name")) - $(".js-import-all").click (event) => - $(".js-add-to-import").each -> - $(this).click() + $btn + .disable() + .addClass 'is-loading' + + $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' + + $(".js-import-all") + .off 'click' + .on 'click', (event) => + $btn = $(event.currentTarget) + $btn + .disable() + .addClass 'is-loading' + + $(".js-add-to-import").each -> + $(this).click() setAutoUpdate: -> setInterval (=> diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss index 6a99cd9cb9..84cc35239f 100644 --- a/app/assets/stylesheets/pages/import.scss +++ b/app/assets/stylesheets/pages/import.scss @@ -16,3 +16,24 @@ i.icon-gitorious-big { width: 18px; height: 18px; } + +.import-jobs-from-col, +.import-jobs-to-col { + width: 40%; +} + +.import-jobs-status-col { + width: 20%; +} + +.btn-import { + .loading-icon { + display: none; + } + + &.is-loading { + .loading-icon { + display: inline-block; + } + } +} diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index aec2e836c9..134d88481d 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -10,13 +10,19 @@ %hr %p - if @incompatible_repos.any? - = button_tag 'Import all compatible projects', class: "btn btn-success js-import-all" + = button_tag class: "btn btn-import btn-success js-import-all" do + = icon('spinner spin', class: 'loading-icon') + Import all compatible projects - else - = button_tag 'Import all projects', class: "btn btn-success js-import-all" + = button_tag class: "btn btn-success js-import-all" do + = icon('spinner spin', class: 'loading-icon') + Import all projects - -.table-holder +.table-responsive %table.table.import-jobs + %colgroup.import-jobs-from-col + %colgroup.import-jobs-to-col + %colgroup.import-jobs-status-col %thead %tr %th From Bitbucket @@ -28,7 +34,7 @@ %td = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span @@ -47,7 +53,9 @@ %td.import-target = "#{repo["owner"]}/#{repo["slug"]}" %td.import-actions.job-status - = button_tag "Import", class: "btn js-add-to-import" + = button_tag class: "btn btn-import js-add-to-import" do + = icon('spinner spin', class: 'loading-icon') + Import - @incompatible_repos.each do |repo| %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} %td diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index 6ee16c8be4..f5966fac65 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -13,10 +13,15 @@ how FogBugz email addresses and usernames are imported into GitLab. %hr %p - = button_tag 'Import all projects', class: 'btn btn-success js-import-all' + = button_tag class: 'btn btn-import btn-success js-import-all' do + = icon('spinner spin', class: 'loading-icon') + Import all projects -.table-holder +.table-responsive %table.table.import-jobs + %colgroup.import-jobs-from-col + %colgroup.import-jobs-to-col + %colgroup.import-jobs-status-col %thead %tr %th From FogBugz @@ -28,7 +33,7 @@ %td = project.import_source %td - %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span @@ -47,7 +52,9 @@ %td.import-target = "#{current_user.username}/#{repo.name}" %td.import-actions.job-status - = button_tag "Import", class: "btn js-add-to-import" + = button_tag class: "btn btn-import js-add-to-import" do + = icon('spinner spin', class: 'loading-icon') + Import :javascript new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}"); diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 1416ee5bd5..65432951ec 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -8,10 +8,15 @@ Select projects you want to import. %hr %p - = button_tag 'Import all projects', class: "btn btn-success js-import-all" + = button_tag class: "btn btn-import btn-success js-import-all" do + = icon('spinner spin', class: 'loading-icon') + Import all projects -.table-holder +.table-responsive %table.table.import-jobs + %colgroup.import-jobs-from-col + %colgroup.import-jobs-to-col + %colgroup.import-jobs-status-col %thead %tr %th From GitHub @@ -23,7 +28,7 @@ %td = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span @@ -42,7 +47,9 @@ %td.import-target = repo.full_name %td.import-actions.job-status - = button_tag "Import", class: "btn js-add-to-import" + = button_tag class: "btn btn-import js-add-to-import" do + = icon('spinner spin', class: 'loading-icon') + Import :javascript new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}"); diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 911a55eb85..f20011f568 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -8,10 +8,15 @@ Select projects you want to import. %hr %p - = button_tag 'Import all projects', class: "btn btn-success js-import-all" + = button_tag class: "btn btn-import btn-success js-import-all" do + = icon('spinner spin', class: 'loading-icon') + Import all projects -.table-holder +.table-responsive %table.table.import-jobs + %colgroup.import-jobs-from-col + %colgroup.import-jobs-to-col + %colgroup.import-jobs-status-col %thead %tr %th From GitLab.com @@ -23,7 +28,7 @@ %td = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span @@ -42,7 +47,9 @@ %td.import-target = repo["path_with_namespace"] %td.import-actions.job-status - = button_tag "Import", class: "btn js-add-to-import" + = button_tag class: "btn js-add-to-import" do + = icon('spinner spin', class: 'loading-icon') + Import :javascript new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}"); diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index 6b0fa1edf8..725910ed21 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -8,10 +8,15 @@ Select projects you want to import. %hr %p - = button_tag 'Import all projects', class: "btn btn-success js-import-all" + = button_tag class: "btn btn-import btn-success js-import-all" do + = icon('spinner spin', class: 'loading-icon') + Import all projects -.table-holder +.table-responsive %table.table.import-jobs + %colgroup.import-jobs-from-col + %colgroup.import-jobs-to-col + %colgroup.import-jobs-status-col %thead %tr %th From Gitorious.org @@ -23,7 +28,7 @@ %td = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span @@ -42,7 +47,9 @@ %td.import-target = repo.full_name %td.import-actions.job-status - = button_tag "Import", class: "btn js-add-to-import" + = button_tag class: "btn btn-import js-add-to-import" do + = icon('spinner spin', class: 'loading-icon') + Import :javascript new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}"); diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 175ef6921c..2cb0931838 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -14,12 +14,19 @@ %hr %p - if @incompatible_repos.any? - = button_tag 'Import all compatible projects', class: "btn btn-success js-import-all" + = button_tag class: "btn btn-import btn-success js-import-all" do + = icon('spinner spin', class: 'loading-icon') + Import all compatible projects - else - = button_tag 'Import all projects', class: "btn btn-success js-import-all" + = button_tag class: "btn btn-import btn-success js-import-all" do + = icon('spinner spin', class: 'loading-icon') + Import all projects -.table-holder +.table-responsive %table.table.import-jobs + %colgroup.import-jobs-from-col + %colgroup.import-jobs-to-col + %colgroup.import-jobs-status-col %thead %tr %th From Google Code @@ -31,7 +38,7 @@ %td = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank" %td - %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status - if project.import_status == 'finished' %span @@ -50,7 +57,9 @@ %td.import-target = "#{current_user.username}/#{repo.name}" %td.import-actions.job-status - = button_tag "Import", class: "btn js-add-to-import" + = button_tag class: "btn btn-import js-add-to-import" do + = icon('spinner spin', class: 'loading-icon') + Import - @incompatible_repos.each do |repo| %tr{id: "repo_#{repo.id}"} %td From 153afd0cd4e2f5cad7d13cb489fbaa5854ff6b0c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 16:10:00 -0500 Subject: [PATCH 071/573] Reutilize existing method to update param value --- app/assets/javascripts/todos.js.coffee | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index f16b735ece..37b4d2f66c 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -93,21 +93,12 @@ class @Todos if newPages isnt currPages # Redirect to previous page if there's one available if currPages > 1 and currPage is currPages - url = @updateQueryStringParameter(url, 'page', currPages - 1) + pageParams = + page: currPages - 1 + url = gl.utils.mergeUrlParams(pageParams, url) location.replace url - updateQueryStringParameter: (uri, key, value) -> - separator = if uri.indexOf('?') isnt -1 then '&' else '?' - - # Matches key and value - regex = new RegExp('([?&])' + key + '=.*?(&|#|$)', 'i') - - if uri.match(regex) - return uri.replace(regex, '$1' + key + '=' + value + '$2') - - uri + separator + key + '=' + value - goToTodoUrl: (e)-> todoLink = $(this).data('url') if e.metaKey From 72e2c1db19fe9a3f45e5df89c03e8077e064ec8b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 11 Apr 2016 17:20:38 -0500 Subject: [PATCH 072/573] Update delete button --- app/views/shared/issuable/_form.html.haml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 757a3812de..4b4078cb8a 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -128,8 +128,6 @@ - else .pull-right - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) - = link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, - method: :delete, class: 'btn btn-grouped' do - = icon('trash-o') - Delete + = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, + method: :delete, class: 'btn btn-danger btn-grouped' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' From 158bba238f06f43f2988552f02b32feb2bc245e6 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 17:42:51 -0500 Subject: [PATCH 073/573] Set tooltips for new added labels --- app/assets/javascripts/labels_select.js.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index d1fe116397..cf0d4f9aae 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -34,7 +34,7 @@ class @LabelsSelect labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> issues?label_name=<%= label.title %>"> - + <%= label.title %> @@ -165,6 +165,8 @@ class @LabelsSelect .html(template) $sidebarCollapsedValue.text(labelCount) + $('.has-tooltip', $value).tooltip(container: 'body') + $value .find('a') .each((i) -> From d88d6e7619c3f976361df638e765472b499f1986 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 18:04:42 -0500 Subject: [PATCH 074/573] Hide top search form on the search page --- app/views/layouts/header/_default.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 0f3b811937..17502148dc 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -8,7 +8,7 @@ .navbar-collapse.collapse %ul.nav.navbar-nav %li.hidden-sm.hidden-xs - = render 'layouts/search' + = render 'layouts/search' unless current_controller?(:search) %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('search') From 377b59da3029786f2265058972ad0bc6aefd8b53 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 12 Apr 2016 09:59:01 +0530 Subject: [PATCH 075/573] Sanitize branch names for confidential issues. - When creating new branches for confidential issues, prefer a branch name like `issue-15` to `some-sensitive-issue-title-15`. - The behaviour for non-confidential issues stays the same. --- app/models/issue.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/issue.rb b/app/models/issue.rb index e064b0f8b9..68e0113380 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -151,7 +151,11 @@ class Issue < ActiveRecord::Base end def to_branch_name - "#{title.parameterize}-#{iid}" + if self.confidential? + "issue-#{iid}" + else + "#{title.parameterize}-#{iid}" + end end def can_be_worked_on?(current_user) From 5d88de092f37497d9c08878954b099c425376bda Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 12 Apr 2016 11:43:15 +0530 Subject: [PATCH 076/573] Refactor `Issue#related_branches` - Previously, the controller held the logic to calculate related branches, which was: ` - ` - This logic belongs in the `related_branches` method, not in the controller. This commit makes this change. - This means that `Issue#related_branches` now needs to take a `User`. When we find the branches that have a merge request referenced in the current issue, this is limited to merge requests that the current user has access to. - This is not directly related to #14566, but is a related refactoring. --- app/controllers/projects/issues_controller.rb | 2 +- app/models/issue.rb | 13 ++++++++++--- spec/models/issue_spec.rb | 3 ++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6d649e72f8..a384203698 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -66,7 +66,7 @@ class Projects::IssuesController < Projects::ApplicationController @notes = @issue.notes.nonawards.with_associations.fresh @noteable = @issue @merge_requests = @issue.referenced_merge_requests(current_user) - @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch) + @related_branches = @issue.related_branches(current_user) respond_to do |format| format.html diff --git a/app/models/issue.rb b/app/models/issue.rb index 68e0113380..252545d114 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -104,10 +104,17 @@ class Issue < ActiveRecord::Base end end - def related_branches - project.repository.branch_names.select do |branch| + # All branches containing the current issue's ID, except for + # those with a merge request open (that the current user can see) + # referencing the current issue. + def related_branches(current_user) + branches_with_iid = project.repository.branch_names.select do |branch| branch.end_with?("-#{iid}") end + + branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch) + + branches_with_iid - branches_with_merge_request end # Reset issue events cache @@ -161,7 +168,7 @@ class Issue < ActiveRecord::Base def can_be_worked_on?(current_user) !self.closed? && !self.project.forked? && - self.related_branches.empty? && + self.related_branches(current_user).empty? && self.closed_by_merge_requests(current_user).empty? end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 15052aaca2..f33b705810 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -192,10 +192,11 @@ describe Issue, models: true do describe '#related_branches' do it "selects the right branches" do + user = build(:user) allow(subject.project.repository).to receive(:branch_names). and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name]) - expect(subject.related_branches).to eq([subject.to_branch_name]) + expect(subject.related_branches(user)).to eq([subject.to_branch_name]) end end From 3918fce5bd073e18addb7d1d4aaf3c81ce8150b0 Mon Sep 17 00:00:00 2001 From: Baldinof Date: Tue, 12 Apr 2016 10:01:52 +0200 Subject: [PATCH 077/573] Hide number sign for string prefixed external issues --- app/models/external_issue.rb | 8 +++----- spec/models/external_issue_spec.rb | 13 +++++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index 92f1f17494..d47b479faa 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -34,15 +34,13 @@ class ExternalIssue %r{(?\b([A-Z][A-Z0-9_]+-)\d+)} end - def self.reference_prefix - '#' - end - def to_reference(_from_project = nil) id end def reference_link_text(from_project = nil) - "#{self.class.reference_prefix}#{id}" + return "##{id}" if /^\d+$/.match(id) + + id end end diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index b3d0c4efe9..4fc3b06559 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -38,8 +38,17 @@ describe ExternalIssue, models: true do end describe '#reference_link_text' do - it 'returns a String reference to the object' do - expect(issue.reference_link_text).to eq '#EXT-1234' + context 'if issue id has a prefix' do + it 'returns the issue ID' do + expect(issue.reference_link_text).to eq 'EXT-1234' + end + end + + context 'if issue id is a number' do + let(:issue) { described_class.new('1234', project) } + it 'returns the issue ID prefixed by #' do + expect(issue.reference_link_text).to eq '#1234' + end end end end From a650af12261b850d9565cbfc473ea82db59f2e3d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Apr 2016 09:48:39 +0100 Subject: [PATCH 078/573] Moved loading icon to match todo loading icon --- app/views/import/base/create.js.haml | 4 ++-- app/views/import/bitbucket/status.html.haml | 6 +++--- app/views/import/fogbugz/status.html.haml | 4 ++-- app/views/import/github/status.html.haml | 4 ++-- app/views/import/gitlab/status.html.haml | 4 ++-- app/views/import/gitorious/status.html.haml | 4 ++-- app/views/import/google_code/status.html.haml | 6 +++--- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index d8af0295b2..ec7a2b8656 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -20,10 +20,10 @@ job.attr("id", "project_#{@project.id}") target_field = job.find(".import-target") target_field.empty() - target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}') + target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}') $("table.import-jobs tbody").prepend(job) job.addClass("active").find(".import-actions").html(" started") - else :plain job = $("tr#repo_#{@repo_id}") - job.find(".import-actions").html(" Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}") + job.find(".import-actions").html(" Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 134d88481d..e24c01f3e0 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -11,12 +11,12 @@ %p - if @incompatible_repos.any? = button_tag class: "btn btn-import btn-success js-import-all" do - = icon('spinner spin', class: 'loading-icon') Import all compatible projects + = icon('spinner spin', class: 'loading-icon') - else = button_tag class: "btn btn-success js-import-all" do - = icon('spinner spin', class: 'loading-icon') Import all projects + = icon('spinner spin', class: 'loading-icon') .table-responsive %table.table.import-jobs @@ -54,8 +54,8 @@ = "#{repo["owner"]}/#{repo["slug"]}" %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - = icon('spinner spin', class: 'loading-icon') Import + = icon('spinner spin', class: 'loading-icon') - @incompatible_repos.each do |repo| %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} %td diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index f5966fac65..d842d1cbd9 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -14,8 +14,8 @@ %hr %p = button_tag class: 'btn btn-import btn-success js-import-all' do - = icon('spinner spin', class: 'loading-icon') Import all projects + = icon('spinner spin', class: 'loading-icon') .table-responsive %table.table.import-jobs @@ -53,8 +53,8 @@ = "#{current_user.username}/#{repo.name}" %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - = icon('spinner spin', class: 'loading-icon') Import + = icon('spinner spin', class: 'loading-icon') :javascript new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}"); diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 65432951ec..d6fa0d8023 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -9,8 +9,8 @@ %hr %p = button_tag class: "btn btn-import btn-success js-import-all" do - = icon('spinner spin', class: 'loading-icon') Import all projects + = icon('spinner spin', class: 'loading-icon') .table-responsive %table.table.import-jobs @@ -48,8 +48,8 @@ = repo.full_name %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - = icon('spinner spin', class: 'loading-icon') Import + = icon('spinner spin', class: 'loading-icon') :javascript new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}"); diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index f20011f568..8f79f38561 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -9,8 +9,8 @@ %hr %p = button_tag class: "btn btn-import btn-success js-import-all" do - = icon('spinner spin', class: 'loading-icon') Import all projects + = icon('spinner spin', class: 'loading-icon') .table-responsive %table.table.import-jobs @@ -48,8 +48,8 @@ = repo["path_with_namespace"] %td.import-actions.job-status = button_tag class: "btn js-add-to-import" do - = icon('spinner spin', class: 'loading-icon') Import + = icon('spinner spin', class: 'loading-icon') :javascript new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}"); diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index 725910ed21..f0256e0f93 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -9,8 +9,8 @@ %hr %p = button_tag class: "btn btn-import btn-success js-import-all" do - = icon('spinner spin', class: 'loading-icon') Import all projects + = icon('spinner spin', class: 'loading-icon') .table-responsive %table.table.import-jobs @@ -48,8 +48,8 @@ = repo.full_name %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - = icon('spinner spin', class: 'loading-icon') Import + = icon('spinner spin', class: 'loading-icon') :javascript new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}"); diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 2cb0931838..c4ac1b4f9b 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -15,12 +15,12 @@ %p - if @incompatible_repos.any? = button_tag class: "btn btn-import btn-success js-import-all" do - = icon('spinner spin', class: 'loading-icon') Import all compatible projects + = icon('spinner spin', class: 'loading-icon') - else = button_tag class: "btn btn-import btn-success js-import-all" do - = icon('spinner spin', class: 'loading-icon') Import all projects + = icon('spinner spin', class: 'loading-icon') .table-responsive %table.table.import-jobs @@ -58,8 +58,8 @@ = "#{current_user.username}/#{repo.name}" %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - = icon('spinner spin', class: 'loading-icon') Import + = icon('spinner spin', class: 'loading-icon') - @incompatible_repos.each do |repo| %tr{id: "repo_#{repo.id}"} %td From 91034af3c998ce4a4f83281525304e8c50add384 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 12 Apr 2016 15:50:19 +0530 Subject: [PATCH 079/573] Augment the tests for `Issue#related_branches` - Test the case where we have a referenced merge request that's being - excluded as a "related branch" - This took a while to figure out, especially the `create_cross_references!` line. --- spec/models/issue_spec.rb | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index f33b705810..8cacce6a7b 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -191,11 +191,27 @@ describe Issue, models: true do end describe '#related_branches' do - it "selects the right branches" do - user = build(:user) - allow(subject.project.repository).to receive(:branch_names). - and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name]) + let(:user) { build(:user, :admin) } + let(:merge_request) { create(:merge_request, description: "Closes ##{subject.iid}", + source_project: subject.project, source_branch: "branch-#{subject.iid}") } + before(:each) do + allow(subject.project.repository).to receive(:branch_names). + and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "branch-#{subject.iid}"]) + + # Without this stub, the `create(:merge_request)` above fails because it can't find + # the source branch. This seems like a reasonable compromise, in comparison with + # setting up a full repo here. + allow_any_instance_of(MergeRequest).to receive(:create_merge_request_diff) + end + + it "selects the right branches when there are no referenced merge requests" do + expect(subject.related_branches(user)).to eq([subject.to_branch_name, "branch-#{subject.iid}"]) + end + + it "selects the right branches when there is a referenced merge request" do + merge_request.create_cross_references!(user) + expect(subject.referenced_merge_requests).to_not be_empty expect(subject.related_branches(user)).to eq([subject.to_branch_name]) end end From 66ca80181bcb5aef97296fde567b899603186be6 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 12 Apr 2016 16:01:44 +0530 Subject: [PATCH 080/573] Test the `Issue#to_branch_name` method. --- spec/models/issue_spec.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 8cacce6a7b..22dabaa040 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -228,10 +228,19 @@ describe Issue, models: true do end describe "#to_branch_name" do - let(:issue) { create(:issue, title: 'a' * 30) } + let(:issue) { create(:issue, title: 'testing-issue') } - it "starts with the issue iid" do + it "ends with the issue iid" do expect(issue.to_branch_name).to match /-#{issue.iid}\z/ end + + it "contains the issue title if not confidential" do + expect(issue.to_branch_name).to match /\Atesting-issue/ + end + + it "does not contain the issue title if confidential" do + issue = create(:issue, title: 'testing-issue', confidential: true) + expect(issue.to_branch_name).to match /\Aissue/ + end end end From 02cfff4623cc447c20f1990d3e5d9b452e5a7190 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Apr 2016 13:34:03 +0100 Subject: [PATCH 081/573] Removed references to subscribe-button CSS class These were being blocked by adblocks Closes #15043 --- app/assets/javascripts/subscription.js.coffee | 2 +- app/assets/stylesheets/pages/issuable.scss | 6 ------ app/assets/stylesheets/pages/labels.scss | 2 +- app/views/projects/labels/_label.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 4 ++-- features/steps/project/issues/issues.rb | 4 ++-- features/steps/project/labels.rb | 2 +- features/steps/project/merge_requests.rb | 4 ++-- 8 files changed, 10 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee index e4b7a3172e..3894806d61 100644 --- a/app/assets/javascripts/subscription.js.coffee +++ b/app/assets/javascripts/subscription.js.coffee @@ -2,7 +2,7 @@ class @Subscription constructor: (container) -> $container = $(container) @url = $container.attr('data-url') - @subscribe_button = $container.find('.subscribe-button') + @subscribe_button = $container.find('.issuable-subscribe-button') @subscription_status = $container.find('.subscription-status') @subscribe_button.unbind('click').click(@toggleSubscription) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 88c1b614c7..f6bdcacda9 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -173,12 +173,6 @@ } } - .subscribe-button { - span { - margin-top: 0; - } - } - &.right-sidebar-collapsed { /* Extra small devices (phones, less than 768px) */ display: none; diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 3e0a3140be..4f67981975 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -106,7 +106,7 @@ padding: 6px; color: $gl-text-color; - &.subscribe-button { + &.label-subscribe-button { padding-left: 0; } } diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 097a65969a..979726b8de 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -14,7 +14,7 @@ .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .subscription-status{data: {status: label_subscription_status(label)}} - %a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}} + %a.label-subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}} %span= label_subscription_toggle_button_text(label) - if can? current_user, :admin_label, @project diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 94affa4b59..89ce356fed 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -128,7 +128,7 @@ .title.hide-collapsed Notifications - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - %button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'} + %button.btn.btn-block.btn-gray.issuable-subscribe-button.hide-collapsed{:type => 'button'} %span= subscribed ? 'Unsubscribe' : 'Subscribe' .subscription-status.hide-collapsed{data: {status: subscribtion_status}} .unsubscribed{class: ( 'hidden' if subscribed )} @@ -152,4 +152,4 @@ new LabelsSelect(); new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}'); new Subscription('.subscription') - new Sidebar(); \ No newline at end of file + new Sidebar(); diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index aff5ca676b..fc12843ea5 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -20,11 +20,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'I should see that I am subscribed' do - expect(find('.subscribe-button span')).to have_content 'Unsubscribe' + expect(find('.issuable-subscribe-button span')).to have_content 'Unsubscribe' end step 'I should see that I am unsubscribed' do - expect(find('.subscribe-button span')).to have_content 'Subscribe' + expect(find('.issuable-subscribe-button span')).to have_content 'Subscribe' end step 'I click link "Closed"' do diff --git a/features/steps/project/labels.rb b/features/steps/project/labels.rb index 17944527e3..5bb0218902 100644 --- a/features/steps/project/labels.rb +++ b/features/steps/project/labels.rb @@ -29,6 +29,6 @@ class Spinach::Features::Labels < Spinach::FeatureSteps private def subscribe_button - first('.subscribe-button span') + first('.label-subscribe-button span') end end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index f0af0d097f..4f883fe7c2 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -77,11 +77,11 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see that I am subscribed' do - expect(find('.subscribe-button span')).to have_content 'Unsubscribe' + expect(find('.issuable-subscribe-button span')).to have_content 'Unsubscribe' end step 'I should see that I am unsubscribed' do - expect(find('.subscribe-button span')).to have_content 'Subscribe' + expect(find('.issuable-subscribe-button span')).to have_content 'Subscribe' end step 'I click button "Unsubscribe"' do From 318314154e09d051cccd8b8391f2065133906bd7 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 12 Apr 2016 15:56:44 +0200 Subject: [PATCH 082/573] Increase fsck lock timeout to 24 hours --- app/workers/repository_check_worker.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/workers/repository_check_worker.rb b/app/workers/repository_check_worker.rb index 2d75c8bafd..afdc6a4c63 100644 --- a/app/workers/repository_check_worker.rb +++ b/app/workers/repository_check_worker.rb @@ -37,9 +37,11 @@ class RepositoryCheckWorker end def try_obtain_lease(id) + # Use a 24-hour timeout because on servers/projects where 'git fsck' is + # super slow we definitely do not want to run it twice in parallel. lease = Gitlab::ExclusiveLease.new( "project_repository_check:#{id}", - timeout: RUN_TIME + timeout: 24.hours ) lease.try_obtain end From b37d3b9423991763ad03fca791a1daf473dafed1 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 12 Apr 2016 16:21:23 +0200 Subject: [PATCH 083/573] Add repository_checks_enabled setting --- app/models/application_setting.rb | 3 ++- .../20160412140240_add_repository_checks_enabled_setting.rb | 5 +++++ db/schema.rb | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20160412140240_add_repository_checks_enabled_setting.rb diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 052cd87473..36f8815423 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -153,7 +153,8 @@ class ApplicationSetting < ActiveRecord::Base require_two_factor_authentication: false, two_factor_grace_period: 48, recaptcha_enabled: false, - akismet_enabled: false + akismet_enabled: false, + repository_checks_enabled: true, ) end diff --git a/db/migrate/20160412140240_add_repository_checks_enabled_setting.rb b/db/migrate/20160412140240_add_repository_checks_enabled_setting.rb new file mode 100644 index 0000000000..ebfa4bcbc7 --- /dev/null +++ b/db/migrate/20160412140240_add_repository_checks_enabled_setting.rb @@ -0,0 +1,5 @@ +class AddRepositoryChecksEnabledSetting < ActiveRecord::Migration + def change + add_column :application_settings, :repository_checks_enabled, :boolean, default: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 5350995688..a9c595fe36 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: 20160331133914) do +ActiveRecord::Schema.define(version: 20160412140240) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -78,6 +78,7 @@ ActiveRecord::Schema.define(version: 20160331133914) do t.string "akismet_api_key" t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" + t.boolean "repository_checks_enabled", default: true end create_table "audit_events", force: :cascade do |t| From beaee0a71fbb1b08676107b3e619e833dc8902c0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Apr 2016 15:54:26 +0100 Subject: [PATCH 084/573] Fixed issue with failing tests --- app/assets/javascripts/subscription.js.coffee | 2 +- app/views/projects/labels/_label.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee index 3894806d61..1a430f3aa4 100644 --- a/app/assets/javascripts/subscription.js.coffee +++ b/app/assets/javascripts/subscription.js.coffee @@ -2,7 +2,7 @@ class @Subscription constructor: (container) -> $container = $(container) @url = $container.attr('data-url') - @subscribe_button = $container.find('.issuable-subscribe-button') + @subscribe_button = $container.find('.js-subscribe-button') @subscription_status = $container.find('.subscription-status') @subscribe_button.unbind('click').click(@toggleSubscription) diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 979726b8de..c7b4bb1f6e 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -14,7 +14,7 @@ .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .subscription-status{data: {status: label_subscription_status(label)}} - %a.label-subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}} + %button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: 'button', data: {toggle: "tooltip"} } %span= label_subscription_toggle_button_text(label) - if can? current_user, :admin_label, @project diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 89ce356fed..fe6e412800 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -128,7 +128,7 @@ .title.hide-collapsed Notifications - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - %button.btn.btn-block.btn-gray.issuable-subscribe-button.hide-collapsed{:type => 'button'} + %button.btn.btn-block.btn-gray.js-subscribe-button.issuable-subscribe-button.hide-collapsed{:type => 'button'} %span= subscribed ? 'Unsubscribe' : 'Subscribe' .subscription-status.hide-collapsed{data: {status: subscribtion_status}} .unsubscribed{class: ( 'hidden' if subscribed )} From 97f4ffff1e7b5da94e18edc20c009ffb46784187 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 12 Apr 2016 17:07:54 +0200 Subject: [PATCH 085/573] Add a 'circuit breaker' for repo checks --- .../admin/application_settings/_form.html.haml | 13 +++++++++++++ app/workers/repository_check_worker.rb | 8 ++++++++ lib/gitlab/current_settings.rb | 3 ++- spec/workers/repository_check_worker_spec.rb | 8 ++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index de86dacbb1..afd88465a7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -275,5 +275,18 @@ .col-sm-10 = f.text_field :sentry_dsn, class: 'form-control' + %fieldset + %legend Repository Checks + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :repository_checks_enabled do + = f.check_box :repository_checks_enabled + Enable Repository Checks + .help-block + GitLab will periodically run + %a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck' + in all project and wiki repositories to look for silent disk corruption issues. + .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/workers/repository_check_worker.rb b/app/workers/repository_check_worker.rb index afdc6a4c63..017f49de08 100644 --- a/app/workers/repository_check_worker.rb +++ b/app/workers/repository_check_worker.rb @@ -15,6 +15,7 @@ class RepositoryCheckWorker # check, only one (or two) will be checked at a time. project_ids.each do |project_id| break if Time.now - start >= RUN_TIME + break unless current_settings.repository_checks_enabled next if !try_obtain_lease(project_id) @@ -45,4 +46,11 @@ class RepositoryCheckWorker ) lease.try_obtain end + + def current_settings + # No caching of the settings! If we cache them and an admin disables + # this feature, an active RepositoryCheckWorker would keep going for up + # to 1 hour after the feature was disabled. + ApplicationSetting.current || Gitlab::CurrentSettings.fake_application_settings + end end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 1acc22fe5b..f44d1b3a44 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -34,7 +34,8 @@ module Gitlab max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, two_factor_grace_period: 48, - akismet_enabled: false + akismet_enabled: false, + repository_checks_enabled: true, ) end diff --git a/spec/workers/repository_check_worker_spec.rb b/spec/workers/repository_check_worker_spec.rb index d1849321f5..13493ad2c6 100644 --- a/spec/workers/repository_check_worker_spec.rb +++ b/spec/workers/repository_check_worker_spec.rb @@ -28,4 +28,12 @@ describe RepositoryCheckWorker do expect(subject.perform).to eq([projects[1].id]) end + + it 'does nothing when repository checks are disabled' do + create(:empty_project) + current_settings = double('settings', repository_checks_enabled: false) + expect(subject).to receive(:current_settings) { current_settings } + + expect(subject.perform).to eq(nil) + end end From ea787165b3a9604aa86304e29778066bb014824e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 12 Apr 2016 17:32:58 +0200 Subject: [PATCH 086/573] Move 'clear checks' button to applicatoin settings --- .../admin/application_settings_controller.rb | 14 ++++++++ app/controllers/admin/projects_controller.rb | 12 ------- .../application_settings/_form.html.haml | 6 ++++ app/views/admin/projects/index.html.haml | 5 --- config/routes.rb | 5 +-- doc/administration/repository_checks.md | 33 +++++++------------ 6 files changed, 32 insertions(+), 43 deletions(-) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index f010436bd3..993a70e63b 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -19,6 +19,19 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController redirect_to admin_runners_path end + def clear_repository_check_states + Project.update_all( + last_repository_check_failed: false, + last_repository_check_at: nil + ) + + redirect_to( + admin_application_settings_path, + notice: 'All repository check states were cleared' + ) + end + + private def set_application_setting @@ -82,6 +95,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :akismet_enabled, :akismet_api_key, :email_author_in_body, + :repository_checks_enabled, restricted_visibility_levels: [], import_sources: [] ) diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 01257a6861..d7cd9520cc 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -40,18 +40,6 @@ class Admin::ProjectsController < Admin::ApplicationController ) end - def clear_repository_check_states - Project.update_all( - last_repository_check_failed: false, - last_repository_check_at: nil - ) - - redirect_to( - admin_namespaces_projects_path, - notice: 'All project states were cleared' - ) - end - protected def project diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index afd88465a7..c7c82da72c 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -287,6 +287,12 @@ GitLab will periodically run %a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck' in all project and wiki repositories to look for silent disk corruption issues. + .form-group + .col-sm-offset-2.col-sm-10 + = link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove" + .help-block + If you got a lot of false alarms from repository checks (maybe your fileserver was temporarily unavailable) you can choose to clear all repository check information from the database. + .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index c2bf065984..aa07afa0d6 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -49,11 +49,6 @@ = button_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" - .panel.panel-default.repository-check-states - .panel-heading - Repository check states - .panel-body - = link_to 'Clear all', clear_repository_check_states_admin_namespace_projects_path(0), data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove" %section.col-md-9 .panel.panel-default .panel-heading diff --git a/config/routes.rb b/config/routes.rb index c0ed99b196..c163602126 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -267,10 +267,6 @@ Rails.application.routes.draw do post :repository_check end - collection do - put :clear_repository_check_states - end - resources :runner_projects end end @@ -286,6 +282,7 @@ Rails.application.routes.draw do resource :application_settings, only: [:show, :update] do resources :services put :reset_runners_token + put :clear_repository_check_states end resources :labels diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md index 77f28209f2..e22b04928c 100644 --- a/doc/administration/repository_checks.md +++ b/doc/administration/repository_checks.md @@ -4,12 +4,13 @@ _**Note:** This feature was [introduced][ce-3232] in GitLab 8.7_ --- -Git has a built-in mechanism [git fsck][git-fsck] to verify the -integrity of all data commited to a repository. GitLab administrators can -trigger such a check for a project via the admin panel. The checks run -asynchronously so it may take a few minutes before the check result is -visible on the project admin page. If the checks failed you can see their -output on the admin log page under 'repocheck.log'. +Git has a built-in mechanism \[git fsck\]\[git-fsck\] to verify the +integrity of all data commited to a repository. GitLab administrators +can trigger such a check for a project via the project page under the +admin panel. The checks run asynchronously so it may take a few minutes +before the check result is visible on the project admin page. If the +checks failed you can see their output on the admin log page under +'repocheck.log'. ## Periodical checks @@ -22,20 +23,8 @@ than once a day. ## Disabling periodic checks -You can disable the periodic checks by giving them an empty cron -schedule in gitlab.yml. - -``` -# For omnibus installations, in /etc/gitlab/gitlab.rb: -gitlab_rails['cron_jobs_repository_check_worker_cron'] = '' -``` - -``` -# For installations from source, in config/gitlab.yml: - cron_jobs: - repository_check_worker: - cron: "" -``` +You can disable the periodic checks on the 'Settings' page of the admin +panel. ## What to do if a check failed @@ -47,8 +36,8 @@ resolved the issue use the admin panel to trigger a new repository check on the project. This will clear the 'check failed' state. If for some reason the periodical repository check caused a lot of false -alarms you can choose to clear ALL repository check states from the admin -project index page. +alarms you can choose to clear ALL repository check states from the +'Settings' page of the admin panel. --- [ce-3232]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3232 "Auto git fsck" From 061370790e415361c920e1404063955f4932e5ef Mon Sep 17 00:00:00 2001 From: Charles May Date: Tue, 5 Jan 2016 21:26:31 +0000 Subject: [PATCH 087/573] Fix a bug with trailing slash in teamcity_url See https://gitlab.com/gitlab-org/gitlab-ce/issues/3515 --- CHANGELOG | 3 ++ .../project_services/teamcity_service.rb | 31 +++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3561c541df..c6ca0ea3de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -123,6 +123,9 @@ v 8.6.0 - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) - HTTP error pages work independently from location and config (Artem Sidorenko) - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set + - Fix avatar stretching by providing a cropping feature (Johann Pardanaud) + - Fix a bug whith trailing slash in teamcity_url (Charles May) + - Don't load all of GitLab in mail_room - Memoize @group in Admin::GroupsController (Yatish Mehta) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - Added omniauth-auth0 Gem (Daniel Carraro) diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index b8e9416131..246c5eb4a8 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -85,13 +85,15 @@ class TeamcityService < CiService end def build_info(sha) - url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\ - "branch:unspecified:any,number:#{sha}") + url = URI.join( + teamcity_url, + "/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}" + ).to_s auth = { username: username, password: password, } - @response = HTTParty.get("#{url}", verify: false, basic_auth: auth) + @response = HTTParty.get(url, verify: false, basic_auth: auth) end def build_page(sha, ref) @@ -100,12 +102,14 @@ class TeamcityService < CiService if @response.code != 200 # If actual build link can't be determined, # send user to build summary page. - "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}" + URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s else # If actual build link is available, go to build result page. built_id = @response['build']['id'] - "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\ - "&buildTypeId=#{build_type}" + URI.join( + teamcity_url, + "#{teamcity_url}/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" + ).to_s end end @@ -140,12 +144,13 @@ class TeamcityService < CiService branch = Gitlab::Git.ref_name(data[:ref]) - self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", - body: ""\ - ""\ - '', - headers: { 'Content-type' => 'application/xml' }, - basic_auth: auth - ) + self.class.post( + URI.join(teamcity_url, "/httpAuth/app/rest/buildQueue").to_s, + body: ""\ + ""\ + '', + headers: { 'Content-type' => 'application/xml' }, + basic_auth: auth + ) end end From 3170e5d226ee107409b4345b827519da64ba967e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 12 Apr 2016 18:09:45 +0200 Subject: [PATCH 088/573] Basta --- app/controllers/admin/application_settings_controller.rb | 2 +- app/controllers/admin/projects_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 993a70e63b..a54864480a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -27,7 +27,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController redirect_to( admin_application_settings_path, - notice: 'All repository check states were cleared' + notice: 'All repository check states were cleared.' ) end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 0719c90b19..6854e57b65 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -36,7 +36,7 @@ class Admin::ProjectsController < Admin::ApplicationController redirect_to( admin_namespace_project_path(@project.namespace, @project), - notice: 'Repository check was triggered' + notice: 'Repository check was triggered.' ) end From 525ab25ac81a6b81cca56d3cba403ab2a5f372eb Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 12 Apr 2016 18:15:15 +0200 Subject: [PATCH 089/573] Changes suggested by Robert --- app/mailers/repository_check_mailer.rb | 2 -- app/views/admin/application_settings/_form.html.haml | 2 +- app/workers/repository_check_worker.rb | 2 +- spec/workers/repository_check_worker_spec.rb | 6 +++--- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb index 994054c876..2bff5b63cc 100644 --- a/app/mailers/repository_check_mailer.rb +++ b/app/mailers/repository_check_mailer.rb @@ -1,6 +1,4 @@ class RepositoryCheckMailer < BaseMailer - include ActionView::Helpers::TextHelper - def notify(failed_count) if failed_count == 1 @message = "One project failed its last repository check" diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 533a2f4297..555aea554f 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -287,7 +287,7 @@ .col-sm-offset-2.col-sm-10 = link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove" .help-block - If you got a lot of false alarms from repository checks (maybe your fileserver was temporarily unavailable) you can choose to clear all repository check information from the database. + If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database. .form-actions diff --git a/app/workers/repository_check_worker.rb b/app/workers/repository_check_worker.rb index 017f49de08..bdacdd4c6b 100644 --- a/app/workers/repository_check_worker.rb +++ b/app/workers/repository_check_worker.rb @@ -17,7 +17,7 @@ class RepositoryCheckWorker break if Time.now - start >= RUN_TIME break unless current_settings.repository_checks_enabled - next if !try_obtain_lease(project_id) + next unless try_obtain_lease(project_id) SingleRepositoryCheckWorker.new.perform(project_id) end diff --git a/spec/workers/repository_check_worker_spec.rb b/spec/workers/repository_check_worker_spec.rb index 13493ad2c6..7a757658e9 100644 --- a/spec/workers/repository_check_worker_spec.rb +++ b/spec/workers/repository_check_worker_spec.rb @@ -4,7 +4,7 @@ describe RepositoryCheckWorker do subject { RepositoryCheckWorker.new } it 'prefers projects that have never been checked' do - projects = 3.times.map { create(:project) } + projects = create_list(:project, 3) projects[0].update_column(:last_repository_check_at, 1.month.ago) projects[2].update_column(:last_repository_check_at, 3.weeks.ago) @@ -12,7 +12,7 @@ describe RepositoryCheckWorker do end it 'sorts projects by last_repository_check_at' do - projects = 3.times.map { create(:project) } + projects = create_list(:project, 3) projects[0].update_column(:last_repository_check_at, 2.weeks.ago) projects[1].update_column(:last_repository_check_at, 1.month.ago) projects[2].update_column(:last_repository_check_at, 3.weeks.ago) @@ -21,7 +21,7 @@ describe RepositoryCheckWorker do end it 'excludes projects that were checked recently' do - projects = 3.times.map { create(:project) } + projects = create_list(:project, 3) projects[0].update_column(:last_repository_check_at, 2.days.ago) projects[1].update_column(:last_repository_check_at, 1.month.ago) projects[2].update_column(:last_repository_check_at, 3.days.ago) From 4e5ae5a281be109ca22929bb21732f0ee1f6630d Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 12 Apr 2016 15:40:53 -0500 Subject: [PATCH 090/573] Fix Grafana docs and link from Influx page --- doc/monitoring/performance/gitlab_configuration.md | 1 + doc/monitoring/performance/grafana_configuration.md | 6 +++--- doc/monitoring/performance/influxdb_configuration.md | 1 + doc/monitoring/performance/influxdb_schema.md | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md index b856e7935a..90e9930221 100644 --- a/doc/monitoring/performance/gitlab_configuration.md +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -37,3 +37,4 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [InfluxDB Configuration](influxdb_configuration.md) - [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 416c9870aa..10ef100981 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -91,18 +91,18 @@ JSON file. Open the dashboard dropdown menu and click 'Import' -![Grafana dashboard dropdown](/img/grafana_dashboard_dropdown.png) +![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) Click 'Choose file' and browse to the location where you downloaded or cloned the dashboard repository. Pick one of the JSON files to import. -![Grafana dashboard import](/img/grafana_dashboard_import.png) +![Grafana dashboard import](img/grafana_dashboard_import.png) Once the dashboard is imported, be sure to click save icon in the top bar. If you do not save the dashboard after importing it will be removed when you navigate away. -![Grafana save icon](/img/grafana_save_icon.png) +![Grafana save icon](img/grafana_save_icon.png) Repeat this process for each dashboard you wish to import. diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md index 3a2b598b78..63aa03985e 100644 --- a/doc/monitoring/performance/influxdb_configuration.md +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -181,6 +181,7 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [GitLab Configuration](gitlab_configuration.md) - [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md [influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management [influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md index a5a8aebd2d..d31b3788f3 100644 --- a/doc/monitoring/performance/influxdb_schema.md +++ b/doc/monitoring/performance/influxdb_schema.md @@ -85,3 +85,4 @@ Read more on: - [Introduction to GitLab Performance Monitoring](introduction.md) - [GitLab Configuration](gitlab_configuration.md) - [InfluxDB Configuration](influxdb_configuration.md) +- [Grafana Install/Configuration](grafana_configuration.md From d8296f873871120b7f4134bdcf8854a09b9e8be8 Mon Sep 17 00:00:00 2001 From: connorshea Date: Tue, 12 Apr 2016 16:11:58 -0600 Subject: [PATCH 091/573] Remove Bootstrap Carousel The Bootstrap carousel module is used for image carousels, and we don't use it anywhere on the site. Also separated the Bootstrap JavaScript into separate components and removed the carousel component. Fixes #14670. --- app/assets/javascripts/application.js.coffee | 12 +++++++++++- app/assets/stylesheets/framework/tw_bootstrap.scss | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index b05138ac1a..6f435e4c54 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -22,7 +22,17 @@ #= require cal-heatmap #= require turbolinks #= require autosave -#= require bootstrap +#= require bootstrap/affix +#= require bootstrap/alert +#= require bootstrap/button +#= require bootstrap/collapse +#= require bootstrap/dropdown +#= require bootstrap/modal +#= require bootstrap/scrollspy +#= require bootstrap/tab +#= require bootstrap/transition +#= require bootstrap/tooltip +#= require bootstrap/popover #= require select2 #= require raphael #= require g.raphael diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index dd42db1840..96bab7880c 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -43,7 +43,6 @@ @import "bootstrap/modals"; @import "bootstrap/tooltip"; @import "bootstrap/popovers"; -@import "bootstrap/carousel"; // Utility classes .clearfix { From 8dd1a6fc917231e83fb64dbfa65e1edbe9ab3fee Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 13 Apr 2016 09:04:08 +0530 Subject: [PATCH 092/573] Fix the rubocop check. --- spec/models/issue_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 22dabaa040..202ce846dc 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -192,8 +192,6 @@ describe Issue, models: true do describe '#related_branches' do let(:user) { build(:user, :admin) } - let(:merge_request) { create(:merge_request, description: "Closes ##{subject.iid}", - source_project: subject.project, source_branch: "branch-#{subject.iid}") } before(:each) do allow(subject.project.repository).to receive(:branch_names). @@ -210,6 +208,9 @@ describe Issue, models: true do end it "selects the right branches when there is a referenced merge request" do + merge_request = create(:merge_request, { description: "Closes ##{subject.iid}", + source_project: subject.project, + source_branch: "branch-#{subject.iid}" }) merge_request.create_cross_references!(user) expect(subject.referenced_merge_requests).to_not be_empty expect(subject.related_branches(user)).to eq([subject.to_branch_name]) From 769a8bf7284da492732d923cb888c0f5be16aff8 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 13 Apr 2016 09:18:29 +0530 Subject: [PATCH 093/573] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3561c541df..738162d983 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.7.0 (unreleased) - API: Expose user location (Robert Schilling) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - Update number of Todos in the sidebar when it's marked as "Done". !3600 + - Sanitize branch names created for confidential issues v 8.6.5 - Fix importing from GitHub Enterprise. !3529 From e57a9551df0a6b6de218bf881322fcba5a77485b Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 13 Apr 2016 12:02:20 +0530 Subject: [PATCH 094/573] Don't populate the password field on signup validation errors. - Previously, we were pulling `params[:user][:password] as the default value for the password field. This is incorrect; we should be pulling it from `@user.password` or the like. --- app/views/devise/shared/_signup_box.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index cb93ff2465..7f23dbfed9 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -17,7 +17,7 @@ %div = f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true + = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true %div - if current_application_settings.recaptcha_enabled = recaptcha_tags From d5f44d8a4b23b20f8292147db9c7f7730de70a3b Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 13 Apr 2016 12:57:40 +0530 Subject: [PATCH 095/573] Don't retrieve default values from `params` - In the signup page. --- app/views/devise/shared/_signup_box.html.haml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 7f23dbfed9..e5607dacd0 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -6,16 +6,15 @@ .login-heading %h3 Create an account .login-body - - user = params[:user].present? ? params[:user] : {} = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| .devise-errors = devise_error_messages! %div - = f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true + = f.text_field :name, class: "form-control top", placeholder: "Name", required: true %div - = f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true + = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true %div - = f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true + = 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 %div From 3ea955a637127e6e11bc9fe270f87f63226b9d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 5 Apr 2016 15:11:31 +0200 Subject: [PATCH 096/573] Improve TeamcityService and its specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 4 +- .../project_services/teamcity_service.rb | 6 +- .../project_services/teamcity_service_spec.rb | 255 ++++++++++++++---- 3 files changed, 205 insertions(+), 60 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c6ca0ea3de..df40a1e1b8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.7.0 (unreleased) - Expose label description in API (Mariusz Jachimowicz) - Allow back dating on issues when created through the API - Fix Error 500 after renaming a project path (Stan Hu) + - Fix a bug whith trailing slash in teamcity_url (Charles May) - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 @@ -123,9 +124,6 @@ v 8.6.0 - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) - HTTP error pages work independently from location and config (Artem Sidorenko) - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - - Fix avatar stretching by providing a cropping feature (Johann Pardanaud) - - Fix a bug whith trailing slash in teamcity_url (Charles May) - - Don't load all of GitLab in mail_room - Memoize @group in Admin::GroupsController (Yatish Mehta) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - Added omniauth-auth0 Gem (Daniel Carraro) diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 246c5eb4a8..8dceee5e2c 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -91,7 +91,7 @@ class TeamcityService < CiService ).to_s auth = { username: username, - password: password, + password: password } @response = HTTParty.get(url, verify: false, basic_auth: auth) end @@ -108,7 +108,7 @@ class TeamcityService < CiService built_id = @response['build']['id'] URI.join( teamcity_url, - "#{teamcity_url}/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" + "/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}" ).to_s end end @@ -145,7 +145,7 @@ class TeamcityService < CiService branch = Gitlab::Git.ref_name(data[:ref]) self.class.post( - URI.join(teamcity_url, "/httpAuth/app/rest/buildQueue").to_s, + URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s, body: ""\ ""\ '', diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index f26b47a856..bc7423cee6 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -21,73 +21,220 @@ require 'spec_helper' describe TeamcityService, models: true do - describe "Associations" do + describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } end - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project) } + describe 'Validations' do + describe '#teamcity_url' do + it 'does not validate the presence of teamcity_url if service is not active' do + teamcity_service = service + teamcity_service.active = false - context "when a password was previously set" do - before do - @teamcity_service = TeamcityService.create( - project: create(:project), - properties: { - teamcity_url: 'http://gitlab.com', - username: 'mic', - password: "password" - } - ) - end - - it "reset password if url changed" do - @teamcity_service.teamcity_url = 'http://gitlab1.com' - @teamcity_service.save - expect(@teamcity_service.password).to be_nil - end - - it "does not reset password if username changed" do - @teamcity_service.username = "some_name" - @teamcity_service.save - expect(@teamcity_service.password).to eq("password") + expect(teamcity_service).not_to validate_presence_of(:teamcity_url) end - it "does not reset password if new url is set together with password, even if it's the same password" do - @teamcity_service.teamcity_url = 'http://gitlab_edited.com' - @teamcity_service.password = 'password' - @teamcity_service.save - expect(@teamcity_service.password).to eq("password") - expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com") - end + it 'validates the presence of teamcity_url if service is active' do + teamcity_service = service + teamcity_service.active = true - it "should reset password if url changed, even if setter called multiple times" do - @teamcity_service.teamcity_url = 'http://gitlab1.com' - @teamcity_service.teamcity_url = 'http://gitlab1.com' - @teamcity_service.save - expect(@teamcity_service.password).to be_nil + expect(teamcity_service).to validate_presence_of(:teamcity_url) end end - - context "when no password was previously set" do - before do - @teamcity_service = TeamcityService.create( - project: create(:project), - properties: { - teamcity_url: 'http://gitlab.com', - username: 'mic' - } - ) + + describe '#build_type' do + it 'does not validate the presence of build_type if service is not active' do + teamcity_service = service + teamcity_service.active = false + + expect(teamcity_service).not_to validate_presence_of(:build_type) end - it "saves password if new url is set together with password" do - @teamcity_service.teamcity_url = 'http://gitlab_edited.com' - @teamcity_service.password = 'password' - @teamcity_service.save - expect(@teamcity_service.password).to eq("password") - expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com") + it 'validates the presence of build_type if service is active' do + teamcity_service = service + teamcity_service.active = true + + expect(teamcity_service).to validate_presence_of(:build_type) + end + end + + describe '#username' do + it 'does not validate the presence of username if service is not active' do + teamcity_service = service + teamcity_service.active = false + + expect(teamcity_service).not_to validate_presence_of(:username) + end + + it 'does not validate the presence of username if username is nil' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.password = nil + + expect(teamcity_service).not_to validate_presence_of(:username) + end + + it 'validates the presence of username if service is active and username is present' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.password = 'secret' + + expect(teamcity_service).to validate_presence_of(:username) + end + end + + describe '#password' do + it 'does not validate the presence of password if service is not active' do + teamcity_service = service + teamcity_service.active = false + + expect(teamcity_service).not_to validate_presence_of(:password) + end + + it 'does not validate the presence of password if username is nil' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.username = nil + + expect(teamcity_service).not_to validate_presence_of(:password) + end + + it 'validates the presence of password if service is active and username is present' do + teamcity_service = service + teamcity_service.active = true + teamcity_service.username = 'john' + + expect(teamcity_service).to validate_presence_of(:password) end end end + + describe 'Callbacks' do + describe 'before_update :reset_password' do + context 'when a password was previously set' do + it 'resets password if url changed' do + teamcity_service = service + + teamcity_service.teamcity_url = 'http://gitlab1.com' + teamcity_service.save + + expect(teamcity_service.password).to be_nil + end + + it 'does not reset password if username changed' do + teamcity_service = service + + teamcity_service.username = 'some_name' + teamcity_service.save + + expect(teamcity_service.password).to eq('password') + end + + it "does not reset password if new url is set together with password, even if it's the same password" do + teamcity_service = service + + teamcity_service.teamcity_url = 'http://gitlab_edited.com' + teamcity_service.password = 'password' + teamcity_service.save + + expect(teamcity_service.password).to eq('password') + expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + end + end + + it 'saves password if new url is set together with password when no password was previously set' do + teamcity_service = service + teamcity_service.password = nil + + teamcity_service.teamcity_url = 'http://gitlab_edited.com' + teamcity_service.password = 'password' + teamcity_service.save + + expect(teamcity_service.password).to eq('password') + expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + end + end + end + + describe '#build_page' do + it 'returns a specific URL when status is 500' do + stub_request(status: 500) + + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildTypeId=foo') + end + + it 'returns a build URL when teamcity_url has no trailing slash' do + stub_request(body: %Q({"build":{"id":"666"}})) + + expect(service(teamcity_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') + end + + it 'returns a build URL when teamcity_url has a trailing slash' do + stub_request(body: %Q({"build":{"id":"666"}})) + + expect(service(teamcity_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') + end + end + + describe '#commit_status' do + it 'sets commit status to :error when status is 500' do + stub_request(status: 500) + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + + it 'sets commit status to "pending" when status is 404' do + stub_request(status: 404) + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to "success" when build status contains SUCCESS' do + stub_request(build_status: 'YAY SUCCESS!') + + expect(service.commit_status('123', 'unused')).to eq('success') + end + + it 'sets commit status to "failed" when build status contains FAILURE' do + stub_request(build_status: 'NO FAILURE!') + + expect(service.commit_status('123', 'unused')).to eq('failed') + end + + it 'sets commit status to "pending" when build status contains Pending' do + stub_request(build_status: 'NO Pending!') + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to :error when build status is unknown' do + stub_request(build_status: 'FOO BAR!') + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + end + + def service(teamcity_url: 'http://gitlab.com') + described_class.create( + project: build_stubbed(:empty_project), + properties: { + teamcity_url: teamcity_url, + username: 'mic', + password: 'password', + build_type: 'foo' + } + ) + end + + def stub_request(status: 200, body: nil, build_status: 'success') + teamcity_full_url = 'http://mic:password@gitlab.com/httpAuth/app/rest/builds/branch:unspecified:any,number:123' + body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) + + WebMock.stub_request(:get, teamcity_full_url).to_return( + status: status, + headers: { 'Content-Type' => 'application/json' }, + body: body + ) + end end From a0bede92edf1f1df66e977c22540fced3414f3b7 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 11:12:43 +0200 Subject: [PATCH 097/573] More code changes, thanks Robert --- app/workers/repository_check_worker.rb | 19 ++++++++------ app/workers/single_repository_check_worker.rb | 25 +++++++++---------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/workers/repository_check_worker.rb b/app/workers/repository_check_worker.rb index bdacdd4c6b..d7ead91f94 100644 --- a/app/workers/repository_check_worker.rb +++ b/app/workers/repository_check_worker.rb @@ -25,9 +25,11 @@ class RepositoryCheckWorker private - # In an ideal world we would use Project.where(...).find_each. - # Unfortunately, calling 'find_each' drops the 'where', so we must build - # an array of IDs instead. + # Project.find_each does not support WHERE clauses and + # Project.find_in_batches does not support ordering. So we just build an + # array of ID's. This is OK because we do it only once an hour, because + # getting ID's from Postgres is not terribly slow, and because no user + # has to sit and wait for this query to finish. def project_ids limit = 10_000 never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit). @@ -40,17 +42,20 @@ class RepositoryCheckWorker def try_obtain_lease(id) # Use a 24-hour timeout because on servers/projects where 'git fsck' is # super slow we definitely do not want to run it twice in parallel. - lease = Gitlab::ExclusiveLease.new( + Gitlab::ExclusiveLease.new( "project_repository_check:#{id}", timeout: 24.hours - ) - lease.try_obtain + ).try_obtain end def current_settings # No caching of the settings! If we cache them and an admin disables # this feature, an active RepositoryCheckWorker would keep going for up # to 1 hour after the feature was disabled. - ApplicationSetting.current || Gitlab::CurrentSettings.fake_application_settings + if Rails.env.test? + Gitlab::CurrentSettings.fake_application_settings + else + ApplicationSetting.current + end end end diff --git a/app/workers/single_repository_check_worker.rb b/app/workers/single_repository_check_worker.rb index d9eed9bd70..6257f382d8 100644 --- a/app/workers/single_repository_check_worker.rb +++ b/app/workers/single_repository_check_worker.rb @@ -5,30 +5,29 @@ class SingleRepositoryCheckWorker def perform(project_id) project = Project.find(project_id) - update(project, success: check(project)) + project.update_columns( + last_repository_check_failed: !check(project), + last_repository_check_at: Time.now, + ) end private def check(project) - [project.repository.path_to_repo, project.wiki.wiki.path].all? do |path| - git_fsck(path) + [project.repository, project.wiki.repository].all? do |repository| + git_fsck(repository.path_to_repo) end end def git_fsck(path) cmd = %W(nice git --git-dir=#{path} fsck) output, status = Gitlab::Popen.popen(cmd) - return true if status.zero? - Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") - false - end - - def update(project, success:) - project.update_columns( - last_repository_check_failed: !success, - last_repository_check_at: Time.now, - ) + if status.zero? + true + else + Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") + false + end end end From 74783358268a6ce69375780400e661d9aa1fa741 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 11:15:36 +0200 Subject: [PATCH 098/573] Improve English, thanks Robert --- doc/administration/repository_checks.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md index e22b04928c..f4c5a84fd3 100644 --- a/doc/administration/repository_checks.md +++ b/doc/administration/repository_checks.md @@ -4,7 +4,7 @@ _**Note:** This feature was [introduced][ce-3232] in GitLab 8.7_ --- -Git has a built-in mechanism \[git fsck\]\[git-fsck\] to verify the +Git has a built-in mechanism, \[git fsck\]\[git-fsck\], to verify the integrity of all data commited to a repository. GitLab administrators can trigger such a check for a project via the project page under the admin panel. The checks run asynchronously so it may take a few minutes @@ -12,7 +12,7 @@ before the check result is visible on the project admin page. If the checks failed you can see their output on the admin log page under 'repocheck.log'. -## Periodical checks +## Periodic checks GitLab periodically runs a repository check on all project repositories and wiki repositories in order to detect data corruption problems. A @@ -35,7 +35,7 @@ in repocheck.log (in the admin panel or on disk; see resolved the issue use the admin panel to trigger a new repository check on the project. This will clear the 'check failed' state. -If for some reason the periodical repository check caused a lot of false +If for some reason the periodic repository check caused a lot of false alarms you can choose to clear ALL repository check states from the 'Settings' page of the admin panel. From da87fa8a9fb2e786a4115d2da3898e0ec83c446c Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 11:50:39 +0200 Subject: [PATCH 099/573] Make check result staleness more obvious --- app/views/admin/projects/show.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 5bef8e3ad5..1e44172f06 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -10,7 +10,9 @@ .col-md-12 .panel .panel-heading.alert.alert-danger - Last repository check failed. See + Last repository check + = "(#{time_ago_in_words(@project.last_repository_check_at)} ago)" + failed. See = link_to 'repocheck.log', admin_logs_path for error messages. .row From b60b9094d3581e72fa614771456fa9003bee6426 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 11:50:55 +0200 Subject: [PATCH 100/573] Always check the wiki too --- app/workers/single_repository_check_worker.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/workers/single_repository_check_worker.rb b/app/workers/single_repository_check_worker.rb index 6257f382d8..f6c345df8b 100644 --- a/app/workers/single_repository_check_worker.rb +++ b/app/workers/single_repository_check_worker.rb @@ -14,9 +14,10 @@ class SingleRepositoryCheckWorker private def check(project) - [project.repository, project.wiki.repository].all? do |repository| + # Use 'map do', not 'all? do', to prevent short-circuiting + [project.repository, project.wiki.repository].map do |repository| git_fsck(repository.path_to_repo) - end + end.all? end def git_fsck(path) From 33f786b5d32d02a032af9d258167ddd2bb61d44a Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 13 Apr 2016 13:12:05 +0300 Subject: [PATCH 101/573] clean up ExclusiveLease --- lib/gitlab/exclusive_lease.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb index c2260a5f7a..ffe4936437 100644 --- a/lib/gitlab/exclusive_lease.rb +++ b/lib/gitlab/exclusive_lease.rb @@ -52,11 +52,6 @@ module Gitlab private - def redis - # Maybe someday we want to use a connection pool... - @redis ||= Redis.new(url: Gitlab::RedisConfig.url) - end - def redis_key "gitlab:exclusive_lease:#{@key}" end From fa864716a80eba26f75a1d69ca3e5183bee96542 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 12:13:53 +0200 Subject: [PATCH 102/573] Use new "clear all" button in tests --- spec/features/admin/admin_projects_spec.rb | 37 +------------- .../admin_uses_repository_checks_spec.rb | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 36 deletions(-) create mode 100644 spec/features/admin/admin_uses_repository_checks_spec.rb diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 95a230a72c..101d955d69 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' -require 'rails_helper' -describe "Admin Projects", feature: true do +describe "Admin::Projects", feature: true do before do @project = create(:project) login_as :admin @@ -32,38 +31,4 @@ describe "Admin Projects", feature: true do expect(page).to have_content(@project.name) end end - - feature 'repository checks' do - scenario 'trigger repository check' do - visit_admin_project_page - - page.within('.repository-check') do - click_button 'Trigger repository check' - end - - expect(page).to have_content('Repository check was triggered') - end - - scenario 'see failed repository check' do - @project.update_column(:last_repository_check_failed, true) - visit_admin_project_page - - expect(page).to have_content('Last repository check failed') - end - - scenario 'clear repository checks', js: true do - @project.update_column(:last_repository_check_failed, true) - visit admin_namespaces_projects_path - - page.within('.repository-check-states') do - click_link 'Clear all' # pop-up should be auto confirmed - end - - expect(@project.reload.last_repository_check_failed).to eq(false) - end - end - - def visit_admin_project_page - visit admin_namespace_project_path(@project.namespace, @project) - end end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb new file mode 100644 index 0000000000..69b8244191 --- /dev/null +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +feature 'Admin uses repository checks', feature: true do + before do + login_as :admin + end + + scenario 'to trigger a single check' do + project = create(:empty_project) + visit_admin_project_page(project) + + page.within('.repository-check') do + click_button 'Trigger repository check' + end + + expect(page).to have_content('Repository check was triggered') + end + + scenario 'to see a single failed repository check' do + visit_admin_project_page(broken_project) + + page.within('.alert') do + expect(page.text).to match(/Last repository check \(.* ago\) failed/) + end + end + + scenario 'to clear all repository checks', js: true do + project = broken_project + visit admin_application_settings_path + + click_link 'Clear all repository checks' # pop-up should be auto confirmed + + expect(project.reload.last_repository_check_failed).to eq(false) + end + + def visit_admin_project_page(project) + visit admin_namespace_project_path(project.namespace, project) + end + + def broken_project + project = create(:empty_project) + project.update_columns( + last_repository_check_failed: true, + last_repository_check_at: Time.now, + ) + project + end +end From 9a30d3b5aef732e782e9496b2e8ae62069ba521a Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 12:20:43 +0200 Subject: [PATCH 103/573] Remove \f. WT\F ?? --- doc/administration/repository_checks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md index f4c5a84fd3..16f4627a59 100644 --- a/doc/administration/repository_checks.md +++ b/doc/administration/repository_checks.md @@ -28,7 +28,7 @@ panel. ## What to do if a check failed -If the repository check fails for some repository you should look up the error +If the repository check fails for some repository you should look up the error in repocheck.log (in the admin panel or on disk; see `/var/log/gitlab/gitlab-rails` for Omnibus installations or `/home/git/gitlab/log` for installations from source). Once you have From bd0be13f5b52b8eaee019d722980b29acbc55b05 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 8 Apr 2016 14:17:42 +0200 Subject: [PATCH 104/573] API: Ability to subscribe and unsubscribe from an issue --- CHANGELOG | 1 + doc/api/issues.md | 108 +++++++++++++++++++++++++++++++ lib/api/helpers.rb | 4 ++ lib/api/issues.rb | 53 +++++++++++++++ spec/requests/api/issues_spec.rb | 31 +++++++++ 5 files changed, 197 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c633c4bb35..01fffcc369 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.7.0 (unreleased) - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) + - API: Ability to subscribe and unsubscribe from an issue (Robert Schilling) - Expose project badges in project settings - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) diff --git a/doc/api/issues.md b/doc/api/issues.md index f09847aef9..4f7d948eba 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -406,6 +406,114 @@ Example response: } ``` +## Subscribe to an issue + +Subscribes to an issue to receive notifications. If the operation is successful, +status code `201` together with the updated issue is returned. If the user is +already subscribed to the issue, the status code `304` is returned. If the +project or issue is not found, status code `404` is returned. + +``` +POST /projects/:id/issues/:issue_id/subscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `issue_id` | integer | yes | The ID of a project's issue | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscribe +``` + +Example response: + +```json +{ + "id": 92, + "iid": 11, + "project_id": 5, + "title": "Sit voluptas tempora quisquam aut doloribus et.", + "description": "Repellat voluptas quibusdam voluptatem exercitationem.", + "state": "opened", + "created_at": "2016-04-05T21:41:45.652Z", + "updated_at": "2016-04-07T12:20:17.596Z", + "labels": [], + "milestone": null, + "assignee": { + "name": "Miss Monserrate Beier", + "username": "axel.block", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/axel.block" + }, + "author": { + "name": "Kris Steuber", + "username": "solon.cremin", + "id": 10, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/solon.cremin" + } +} +``` + +## Unsubscribe from an issue + +Unsubscribes from an issue to not receive notifications from that issue. If the +operation is successful, status code `201` together with the updated issue is +returned. If the user is not subscribed to the issue, the status code `304` +is returned. If the project or issue is not found, status code `404` is +returned. + +``` +POST /projects/:id/issues/:issue_id/unsubscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `issue_id` | integer | yes | The ID of a project's issue | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/unsubscribe +``` + +Example response: + +```json +{ + "id": 93, + "iid": 12, + "project_id": 5, + "title": "Incidunt et rerum ea expedita iure quibusdam.", + "description": "Et cumque architecto sed aut ipsam.", + "state": "opened", + "created_at": "2016-04-05T21:41:45.217Z", + "updated_at": "2016-04-07T13:02:37.905Z", + "labels": [], + "milestone": null, + "assignee": { + "name": "Edwardo Grady", + "username": "keyon", + "id": 21, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/keyon" + }, + "author": { + "name": "Vivian Hermann", + "username": "orville", + "id": 11, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", + "web_url": "http://lgitlab.example.com/u/orville" + }, + "subscribed": false +} +``` + ## Comments on issues Comments are done via the [notes](notes.md) resource. diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 4921ae99e7..aa0597564e 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -241,6 +241,10 @@ module API render_api_error!('413 Request Entity Too Large', 413) end + def not_modified! + render_api_error!('304 Not modified', 304) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 850e99981f..49911fa298 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -231,6 +231,59 @@ module API authorize!(:destroy_issue, issue) issue.destroy end + + # Subscribes to a project issue + # + # Parameters: + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # POST /projects/:id/issues/:issue_id + post ":id/issues/:issue_idsubscribe" do + issue = user_project.issues.find_by(id: params[:issue_id]) + + if !issue.subscribed?(current_user) + present issue, with: Entities::Issue, current_user: current_user + else + not_modified! + end + end + + # Subscribes to a project issue + # + # Parameters: + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # POST /projects/:id/issues/:issue_id/subscribe + post ":id/issues/:issue_id/subscribe" do + issue = user_project.issues.find_by(id: params[:issue_id]) + + if !issue.subscribed?(current_user) + issue.toggle_subscription(current_user) + present issue, with: Entities::Issue, current_user: current_user + else + not_modified! + end + end + + # Unsubscribes from a project issue + # + # Parameters: + # id (required) - The ID of a project + # issue_id (required) - The ID of a project issue + # Example Request: + # POST /projects/:id/issues/:issue_id/unsubscribe + post ":id/issues/:issue_id/unsubscribe" do + issue = user_project.issues.find_by(id: params[:issue_id]) + + if issue.subscribed?(current_user) + issue.unsubscribe(current_user) + present issue, with: Entities::Issue, current_user: current_user + else + not_modified! + end + end end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 3d7a31cbb6..437caf466b 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } + let(:user2) { create(:user) } let(:non_member) { create(:user) } let(:author) { create(:author) } let(:assignee) { create(:assignee) } @@ -569,4 +570,34 @@ describe API::API, api: true do end end end + + describe 'POST :id/issues/:issue_id/subscribe' do + it 'subscribes to an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user2) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(true) + end + + it 'returns 304 if already subscribed' do + post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) + + expect(response.status).to eq(304) + end + end + + describe 'POST :id/issues/:issue_id/unsubscribe' do + it 'unsubscribes from an issue' do + post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(false) + end + + it 'returns 304 if not subscribed' do + post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user2) + + expect(response.status).to eq(304) + end + end end From f875189b3962bde6e4b7b8c4ffdd18af83cbc922 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 8 Apr 2016 15:03:34 +0200 Subject: [PATCH 105/573] API: Ability to subscribe and unsubscribe from a merge request --- CHANGELOG | 2 +- doc/api/merge_requests.md | 147 +++++++++++++++++++++++ lib/api/issues.rb | 4 +- lib/api/merge_requests.rb | 36 ++++++ spec/requests/api/merge_requests_spec.rb | 30 +++++ 5 files changed, 216 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 01fffcc369..1184ffce76 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,7 +11,7 @@ v 8.7.0 (unreleased) - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - - API: Ability to subscribe and unsubscribe from an issue (Robert Schilling) + - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - Expose project badges in project settings - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 20db73ea6c..16cb892626 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -606,3 +606,150 @@ Example response: }, ] ``` + +## Subscribe to a merge request + +Subscribes to a merge request to receive notification. If the operation is +successful, status code `201` together with the updated merge request is +returned. If the user is already subscribed to the merge request, the status +code `304` is returned. If the project or merge request is not found, status +code `404` is returned. + +``` +POST /projects/:id/merge_requests/:merge_request_id/subscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `merge_request_id` | integer | yes | The ID of the merge request | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +``` + +Example response: + +```json +{ + "id": 17, + "iid": 1, + "project_id": 5, + "title": "Et et sequi est impedit nulla ut rem et voluptatem.", + "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "state": "opened", + "created_at": "2016-04-05T21:42:23.233Z", + "updated_at": "2016-04-05T22:11:52.900Z", + "target_branch": "ui-dev-kit", + "source_branch": "version-1-9", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Eileen Skiles", + "username": "leila", + "id": 19, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/leila" + }, + "assignee": { + "name": "Celine Wehner", + "username": "carli", + "id": 16, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/carli" + }, + "source_project_id": 5, + "target_project_id": 5, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 7, + "iid": 1, + "project_id": 5, + "title": "v2.0", + "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "state": "closed", + "created_at": "2016-04-05T21:41:40.905Z", + "updated_at": "2016-04-05T21:41:40.905Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": true +} +``` +## Unsubscribe from a merge request + +Unsubscribes from a merge request to not receive notifications from that merge +request. If the operation is successful, status code `201` together with the +updated merge request is returned. If the user is not subscribed to the merge +request, the status code `304` is returned. If the project or merge request is +not found, status code `404` is returned. + +``` +POST /projects/:id/merge_requests/:merge_request_id/unsubscribe +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `merge_request_id` | integer | yes | The ID of the merge request | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +``` + +Example response: + +```json +{ + "id": 17, + "iid": 1, + "project_id": 5, + "title": "Et et sequi est impedit nulla ut rem et voluptatem.", + "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "state": "opened", + "created_at": "2016-04-05T21:42:23.233Z", + "updated_at": "2016-04-05T22:11:52.900Z", + "target_branch": "ui-dev-kit", + "source_branch": "version-1-9", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Eileen Skiles", + "username": "leila", + "id": 19, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/leila" + }, + "assignee": { + "name": "Celine Wehner", + "username": "carli", + "id": 16, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/carli" + }, + "source_project_id": 5, + "target_project_id": 5, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 7, + "iid": 1, + "project_id": 5, + "title": "v2.0", + "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "state": "closed", + "created_at": "2016-04-05T21:41:40.905Z", + "updated_at": "2016-04-05T21:41:40.905Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": false +} +``` diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 49911fa298..049618c00f 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -235,7 +235,7 @@ module API # Subscribes to a project issue # # Parameters: - # id (required) - The ID of a project + # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: # POST /projects/:id/issues/:issue_id @@ -270,7 +270,7 @@ module API # Unsubscribes from a project issue # # Parameters: - # id (required) - The ID of a project + # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: # POST /projects/:id/issues/:issue_id/unsubscribe diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4e7de8867b..d166484ba5 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -327,6 +327,42 @@ module API issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) present paginate(issues), with: Entities::Issue, current_user: current_user end + + # Subscribes to a merge request + # + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - The ID of a merge request + # Example Request: + # POST /projects/:id/issues/:merge_request_id/subscribe + post "#{path}/subscribe" do + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + + if !merge_request.subscribed?(current_user) + merge_request.toggle_subscription(current_user) + present merge_request, with: Entities::MergeRequest, current_user: current_user + else + not_modified! + end + end + + # Unsubscribes from a merge request + # + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - The ID of a merge request + # Example Request: + # POST /projects/:id/merge_requests/:merge_request_id/unsubscribe + post "#{path}/unsubscribe" do + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + + if merge_request.subscribed?(current_user) + merge_request.unsubscribe(current_user) + present merge_request, with: Entities::MergeRequest, current_user: current_user + else + not_modified! + end + end end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 25fa30b2f2..b71c72a382 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -516,6 +516,36 @@ describe API::API, api: true do end end + describe 'POST :id/merge_requests/:merge_request_id/subscribe' do + it 'subscribes to a merge request' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", admin) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(true) + end + + it 'returns 304 if already subscribed' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user) + + expect(response.status).to eq(304) + end + end + + describe 'POST :id/merge_requests/:merge_request_id/unsubscribe' do + it 'unsubscribes from a merge request' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user) + + expect(response.status).to eq(201) + expect(json_response['subscribed']).to eq(false) + end + + it 'returns 304 if not subscribed' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", admin) + + expect(response.status).to eq(304) + end + end + def mr_with_later_created_and_updated_at_time merge_request merge_request.created_at += 1.hour From fa3009095fbb995550a20e5d0cbc994f4290fbea Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 14:46:59 +0200 Subject: [PATCH 106/573] Make subscription API more RESTful --- doc/api/issues.md | 27 +++++++++++----------- doc/api/merge_requests.md | 28 +++++++++++------------ lib/api/helpers.rb | 2 +- lib/api/issues.rb | 29 +++++------------------- lib/api/merge_requests.rb | 14 ++++++------ spec/requests/api/issues_spec.rb | 26 +++++++++++++++------ spec/requests/api/merge_requests_spec.rb | 14 ++++++------ 7 files changed, 68 insertions(+), 72 deletions(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index 4f7d948eba..42024becc3 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -408,13 +408,14 @@ Example response: ## Subscribe to an issue -Subscribes to an issue to receive notifications. If the operation is successful, -status code `201` together with the updated issue is returned. If the user is -already subscribed to the issue, the status code `304` is returned. If the -project or issue is not found, status code `404` is returned. +Subscribes the authenticated user to an issue to receive notifications. If the +operation is successful, status code `201` together with the updated issue is +returned. If the user is already subscribed to the issue, the status code `304` +is returned. If the project or issue is not found, status code `404` is +returned. ``` -POST /projects/:id/issues/:issue_id/subscribe +POST /projects/:id/issues/:issue_id/subscription ``` | Attribute | Type | Required | Description | @@ -423,7 +424,7 @@ POST /projects/:id/issues/:issue_id/subscribe | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscribe +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription ``` Example response: @@ -461,14 +462,14 @@ Example response: ## Unsubscribe from an issue -Unsubscribes from an issue to not receive notifications from that issue. If the -operation is successful, status code `201` together with the updated issue is -returned. If the user is not subscribed to the issue, the status code `304` -is returned. If the project or issue is not found, status code `404` is -returned. +Unsubscribes the authenticated user from the issue to not receive notifications +from it. If the operation is successful, status code `200` together with the +updated issue is returned. If the user is not subscribed to the issue, the +status code `304` is returned. If the project or issue is not found, status code +`404` is returned. ``` -POST /projects/:id/issues/:issue_id/unsubscribe +DELETE /projects/:id/issues/:issue_id/subscription ``` | Attribute | Type | Required | Description | @@ -477,7 +478,7 @@ POST /projects/:id/issues/:issue_id/unsubscribe | `issue_id` | integer | yes | The ID of a project's issue | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/unsubscribe +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription ``` Example response: diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 16cb892626..3c18ebfa31 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -609,14 +609,14 @@ Example response: ## Subscribe to a merge request -Subscribes to a merge request to receive notification. If the operation is -successful, status code `201` together with the updated merge request is -returned. If the user is already subscribed to the merge request, the status -code `304` is returned. If the project or merge request is not found, status -code `404` is returned. +Subscribes the authenticated user to a merge request to receive notification. If +the operation is successful, status code `201` together with the updated merge +request is returned. If the user is already subscribed to the merge request, the +status code `304` is returned. If the project or merge request is not found, +status code `404` is returned. ``` -POST /projects/:id/merge_requests/:merge_request_id/subscribe +POST /projects/:id/merge_requests/:merge_request_id/subscription ``` | Attribute | Type | Required | Description | @@ -625,7 +625,7 @@ POST /projects/:id/merge_requests/:merge_request_id/subscribe | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription ``` Example response: @@ -682,14 +682,14 @@ Example response: ``` ## Unsubscribe from a merge request -Unsubscribes from a merge request to not receive notifications from that merge -request. If the operation is successful, status code `201` together with the -updated merge request is returned. If the user is not subscribed to the merge -request, the status code `304` is returned. If the project or merge request is -not found, status code `404` is returned. +Unsubscribes the authenticated user from a merge request to not receive +notifications from that merge request. If the operation is successful, status +code `200` together with the updated merge request is returned. If the user is +not subscribed to the merge request, the status code `304` is returned. If the +project or merge request is not found, status code `404` is returned. ``` -POST /projects/:id/merge_requests/:merge_request_id/unsubscribe +DELETE /projects/:id/merge_requests/:merge_request_id/subscription ``` | Attribute | Type | Required | Description | @@ -698,7 +698,7 @@ POST /projects/:id/merge_requests/:merge_request_id/unsubscribe | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscribe +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription ``` Example response: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index aa0597564e..54452f763a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -242,7 +242,7 @@ module API end def not_modified! - render_api_error!('304 Not modified', 304) + render_api_error!('304 Not Modified', 304) end def render_validation_error!(model) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 049618c00f..37d2507307 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -238,32 +238,15 @@ module API # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: - # POST /projects/:id/issues/:issue_id - post ":id/issues/:issue_idsubscribe" do + # POST /projects/:id/issues/:issue_id/subscription + post ":id/issues/:issue_id/subscription" do issue = user_project.issues.find_by(id: params[:issue_id]) - if !issue.subscribed?(current_user) - present issue, with: Entities::Issue, current_user: current_user - else + if issue.subscribed?(current_user) not_modified! - end - end - - # Subscribes to a project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # POST /projects/:id/issues/:issue_id/subscribe - post ":id/issues/:issue_id/subscribe" do - issue = user_project.issues.find_by(id: params[:issue_id]) - - if !issue.subscribed?(current_user) + else issue.toggle_subscription(current_user) present issue, with: Entities::Issue, current_user: current_user - else - not_modified! end end @@ -273,8 +256,8 @@ module API # id (required) - The ID of a project # issue_id (required) - The ID of a project issue # Example Request: - # POST /projects/:id/issues/:issue_id/unsubscribe - post ":id/issues/:issue_id/unsubscribe" do + # DELETE /projects/:id/issues/:issue_id/subscription + delete ":id/issues/:issue_id/subscription" do issue = user_project.issues.find_by(id: params[:issue_id]) if issue.subscribed?(current_user) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d166484ba5..7e78609ecb 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -334,15 +334,15 @@ module API # id (required) - The ID of a project # merge_request_id (required) - The ID of a merge request # Example Request: - # POST /projects/:id/issues/:merge_request_id/subscribe - post "#{path}/subscribe" do + # POST /projects/:id/issues/:merge_request_id/subscription + post "#{path}/subscription" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - if !merge_request.subscribed?(current_user) + if merge_request.subscribed?(current_user) + not_modified! + else merge_request.toggle_subscription(current_user) present merge_request, with: Entities::MergeRequest, current_user: current_user - else - not_modified! end end @@ -352,8 +352,8 @@ module API # id (required) - The ID of a project # merge_request_id (required) - The ID of a merge request # Example Request: - # POST /projects/:id/merge_requests/:merge_request_id/unsubscribe - post "#{path}/unsubscribe" do + # DELETE /projects/:id/merge_requests/:merge_request_id/subscription + delete "#{path}/subscription" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) if merge_request.subscribed?(current_user) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 437caf466b..8361a1649e 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -571,33 +571,45 @@ describe API::API, api: true do end end - describe 'POST :id/issues/:issue_id/subscribe' do + describe 'POST :id/issues/:issue_id/subscription' do it 'subscribes to an issue' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user2) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) expect(response.status).to eq(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) expect(response.status).to eq(304) end + + it 'returns 404 if the issue is not found' do + post api("/projects/#{project.id}/issues/123/subscription", user) + + expect(response.status).to eq(404) + end end - describe 'POST :id/issues/:issue_id/unsubscribe' do + describe 'DELETE :id/issues/:issue_id/subscription' do it 'unsubscribes from an issue' do - post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response.status).to eq(201) + expect(response.status).to eq(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do - post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user2) + post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) expect(response.status).to eq(304) end + + it 'returns 404 if the issue is not found' do + post api("/projects/#{project.id}/issues/123/subscription", user) + + expect(response.status).to eq(404) + end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index b71c72a382..c247fcf9c9 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -516,31 +516,31 @@ describe API::API, api: true do end end - describe 'POST :id/merge_requests/:merge_request_id/subscribe' do + describe 'POST :id/merge_requests/:merge_request_id/subscription' do it 'subscribes to a merge request' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", admin) + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) expect(response.status).to eq(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user) + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) expect(response.status).to eq(304) end end - describe 'POST :id/merge_requests/:merge_request_id/unsubscribe' do + describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do it 'unsubscribes from a merge request' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user) + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response.status).to eq(201) + expect(response.status).to eq(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do - post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", admin) + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) expect(response.status).to eq(304) end From ea2193aaeb1127746dc78d2dda7037d998911662 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 6 Apr 2016 15:52:16 +0200 Subject: [PATCH 107/573] API: Star and unstar a project --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/projects.md | 127 +++++++++++++++++++++++++++++ lib/api/helpers.rb | 4 + lib/api/projects.rb | 33 ++++++++ spec/requests/api/projects_spec.rb | 50 ++++++++++++ 6 files changed, 216 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c633c4bb35..7a2af5a0eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.7.0 (unreleased) - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) + - API: Ability to star and unstar a project (Robert Schilling) - Add default scope to projects to exclude projects pending deletion - Allow to close merge requests which source projects(forks) are deleted. - Ensure empty recipients are rejected in BuildsEmailService diff --git a/doc/api/README.md b/doc/api/README.md index 7629ef294a..3a8fa6cebd 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -108,6 +108,7 @@ The following table shows the possible return codes for API requests. | ------------- | ----------- | | `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | | `201 Created` | The `POST` request was successful and the resource is returned as JSON. | +| `304 Not Modified` | Indicates that the resource has not been modified since the last request. | | `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | | `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. | | `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. | diff --git a/doc/api/projects.md b/doc/api/projects.md index ab716c229d..c5ffc5514c 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -491,6 +491,133 @@ Parameters: - `id` (required) - The ID of the project to be forked +### Star a project + +Stars a given project. Returns status code 201 and the project on success and +304 if the project is already starred. + +``` +POST /projects/:id/star +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the project | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" +``` + +Example response: + +```json +{ + "id": 3, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 10, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site", + "tag_list": [ + "example", + "disapora project" + ], + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "builds_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "creator_id": 3, + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + }, + "archived": true, + "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 1 +} +``` + +### Unstar a project + +Unstars a given project. Returns status code 201 and the project on success +and 304 if the project is already unstarred. + +``` +POST /projects/:id/unstar +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the project | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/unstar" +``` + +Example response: + +```json +{ + "id": 3, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 10, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site", + "tag_list": [ + "example", + "disapora project" + ], + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "builds_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "creator_id": 3, + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + }, + "archived": true, + "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0 +} +``` + + ### Archive a project Archives the project if the user is either admin or the project owner of this project. This action is diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 4921ae99e7..aa0597564e 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -241,6 +241,10 @@ module API render_api_error!('413 Request Entity Too Large', 413) end + def not_modified! + render_api_error!('304 Not modified', 304) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 24b3100547..ebcf7a4eed 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -272,6 +272,39 @@ module API present user_project, with: Entities::Project end + # Star project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # POST /projects/:id/star + post ':id/star' do + if !current_user.starred?(user_project) + current_user.toggle_star(user_project) + user_project.reload + present user_project, with: Entities::Project + + else + not_modified! + end + end + + # Unstar project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # POST /projects/:id/unstar + post ':id/unstar' do + if current_user.starred?(user_project) + current_user.toggle_star(user_project) + user_project.reload + present user_project, with: Entities::Project + else + not_modified! + end + end + # Remove project # # Parameters: diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index be2034e0f3..f05622f77f 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1020,6 +1020,56 @@ describe API::API, api: true do end end + describe 'POST /projects/:id/star' do + context 'on an unstarred project' do + it 'stars the project' do + post api("/projects/#{project.id}/star", user) + + expect(response.status).to eq(201) + expect(json_response['star_count']).to eq(1) + end + end + + context 'on a starred project' do + before do + user.toggle_star(project) + project.reload + end + + it 'does not modify the star count' do + post api("/projects/#{project.id}/star", user) + + expect(response.status).to eq(304) + expect(project.star_count).to eq(1) + end + end + end + + describe 'POST /projects/:id/unstar' do + context 'on a starred project' do + before do + user.toggle_star(project) + project.reload + end + + it 'unstars the project' do + post api("/projects/#{project.id}/unstar", user) + + expect(response.status).to eq(201) + expect(json_response['star_count']).to eq(0) + end + end + + context 'on an unstarred project' do + it 'does not modify the star count' do + post api("/projects/#{project.id}/unstar", user) + + expect(response.status).to eq(304) + expect(project.star_count).to eq(0) + end + end + end + describe 'DELETE /projects/:id' do context 'when authenticated as user' do it 'should remove project' do From 3ab9ea8dae1edc6ab8c8563843342890736eb24c Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 12 Apr 2016 18:52:43 +0200 Subject: [PATCH 108/573] Make staring API more restful --- doc/api/projects.md | 12 ++++++------ lib/api/projects.rb | 11 +++++------ spec/requests/api/projects_spec.rb | 8 ++++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index c5ffc5514c..b25c908008 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -493,8 +493,8 @@ Parameters: ### Star a project -Stars a given project. Returns status code 201 and the project on success and -304 if the project is already starred. +Stars a given project. Returns status code `201` and the project on success and +`304` if the project is already starred. ``` POST /projects/:id/star @@ -556,11 +556,11 @@ Example response: ### Unstar a project -Unstars a given project. Returns status code 201 and the project on success -and 304 if the project is already unstarred. +Unstars a given project. Returns status code `200` and the project on success +and `304` if the project is not starred. ``` -POST /projects/:id/unstar +DELETE /projects/:id/star ``` | Attribute | Type | Required | Description | @@ -568,7 +568,7 @@ POST /projects/:id/unstar | `id` | integer | yes | The ID of the project | ```bash -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/unstar" +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" ``` Example response: diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ebcf7a4eed..c7fdfbfe57 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -279,13 +279,12 @@ module API # Example Request: # POST /projects/:id/star post ':id/star' do - if !current_user.starred?(user_project) + if current_user.starred?(user_project) + not_modified! + else current_user.toggle_star(user_project) user_project.reload present user_project, with: Entities::Project - - else - not_modified! end end @@ -294,8 +293,8 @@ module API # Parameters: # id (required) - The ID of a project # Example Request: - # POST /projects/:id/unstar - post ':id/unstar' do + # DELETE /projects/:id/unstar + delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) user_project.reload diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index f05622f77f..2a7c55fe65 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1045,7 +1045,7 @@ describe API::API, api: true do end end - describe 'POST /projects/:id/unstar' do + describe 'DELETE /projects/:id/star' do context 'on a starred project' do before do user.toggle_star(project) @@ -1053,16 +1053,16 @@ describe API::API, api: true do end it 'unstars the project' do - post api("/projects/#{project.id}/unstar", user) + delete api("/projects/#{project.id}/star", user) - expect(response.status).to eq(201) + expect(response.status).to eq(200) expect(json_response['star_count']).to eq(0) end end context 'on an unstarred project' do it 'does not modify the star count' do - post api("/projects/#{project.id}/unstar", user) + delete api("/projects/#{project.id}/star", user) expect(response.status).to eq(304) expect(project.star_count).to eq(0) From 3b9edce803c91b5f51675291fdf22f1159cea456 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 15:19:50 +0200 Subject: [PATCH 109/573] Instrument the HousekeepingService class This allows us to track how much time is spent in updating the "pushes_since_gc" column as well as the time needed to obtain the lease. --- CHANGELOG | 1 + app/services/projects/housekeeping_service.rb | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5399e3e5b8..ec8db47157 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.7.0 (unreleased) + - The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse) - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Developers can now add custom tags to transactions (Yorick Peterse) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index a0973c5d26..3b7c36f090 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -26,7 +26,9 @@ module Projects GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace) ensure - @project.update_column(:pushes_since_gc, 0) + Gitlab::Metrics.measure(:reset_pushes_since_gc) do + @project.update_column(:pushes_since_gc, 0) + end end def needed? @@ -34,14 +36,18 @@ module Projects end def increment! - @project.increment!(:pushes_since_gc) + Gitlab::Metrics.measure(:increment_pushes_since_gc) do + @project.increment!(:pushes_since_gc) + end end private def try_obtain_lease - lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) - lease.try_obtain + Gitlab::Metrics.measure(:obtain_housekeeping_lease) do + lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) + lease.try_obtain + end end end end From 0f602be99f99f1ae493478a8a28df2907cfa0082 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 15:56:05 +0200 Subject: [PATCH 110/573] Clear repository check columns asynchronously --- .../admin/application_settings_controller.rb | 8 +-- app/controllers/admin/projects_controller.rb | 2 +- app/workers/repository_check/batch_worker.rb | 63 +++++++++++++++++++ app/workers/repository_check/clear_worker.rb | 17 +++++ .../single_repository_worker.rb | 36 +++++++++++ app/workers/repository_check_worker.rb | 61 ------------------ app/workers/single_repository_check_worker.rb | 34 ---------- config/initializers/1_settings.rb | 2 +- .../admin_uses_repository_checks_spec.rb | 27 ++++---- .../batch_worker_spec.rb} | 4 +- .../repository_check/clear_worker_spec.rb | 17 +++++ 11 files changed, 150 insertions(+), 121 deletions(-) create mode 100644 app/workers/repository_check/batch_worker.rb create mode 100644 app/workers/repository_check/clear_worker.rb create mode 100644 app/workers/repository_check/single_repository_worker.rb delete mode 100644 app/workers/repository_check_worker.rb delete mode 100644 app/workers/single_repository_check_worker.rb rename spec/workers/{repository_check_worker_spec.rb => repository_check/batch_worker_spec.rb} (94%) create mode 100644 spec/workers/repository_check/clear_worker_spec.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index a54864480a..b4a28b8dd3 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -20,18 +20,14 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end def clear_repository_check_states - Project.update_all( - last_repository_check_failed: false, - last_repository_check_at: nil - ) + RepositoryCheck::ClearWorker.perform_async redirect_to( admin_application_settings_path, - notice: 'All repository check states were cleared.' + notice: 'Started asynchronous removal of all repository check states.' ) end - private def set_application_setting diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 6854e57b65..87986fdf8b 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -32,7 +32,7 @@ class Admin::ProjectsController < Admin::ApplicationController end def repository_check - SingleRepositoryCheckWorker.perform_async(@project.id) + RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id) redirect_to( admin_namespace_project_path(@project.namespace, @project), diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb new file mode 100644 index 0000000000..16cd77a9bf --- /dev/null +++ b/app/workers/repository_check/batch_worker.rb @@ -0,0 +1,63 @@ +module RepositoryCheck + class BatchWorker + include Sidekiq::Worker + + RUN_TIME = 3600 + + sidekiq_options retry: false + + def perform + start = Time.now + + # This loop will break after a little more than one hour ('a little + # more' because `git fsck` may take a few minutes), or if it runs out of + # projects to check. By default sidekiq-cron will start a new + # RepositoryCheckWorker each hour so that as long as there are repositories to + # check, only one (or two) will be checked at a time. + project_ids.each do |project_id| + break if Time.now - start >= RUN_TIME + break unless current_settings.repository_checks_enabled + + next unless try_obtain_lease(project_id) + + SingleRepositoryWorker.new.perform(project_id) + end + end + + private + + # Project.find_each does not support WHERE clauses and + # Project.find_in_batches does not support ordering. So we just build an + # array of ID's. This is OK because we do it only once an hour, because + # getting ID's from Postgres is not terribly slow, and because no user + # has to sit and wait for this query to finish. + def project_ids + limit = 10_000 + never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit). + pluck(:id) + old_check_projects = Project.where('last_repository_check_at < ?', 1.week.ago). + reorder('last_repository_check_at ASC').limit(limit).pluck(:id) + never_checked_projects + old_check_projects + end + + def try_obtain_lease(id) + # Use a 24-hour timeout because on servers/projects where 'git fsck' is + # super slow we definitely do not want to run it twice in parallel. + Gitlab::ExclusiveLease.new( + "project_repository_check:#{id}", + timeout: 24.hours + ).try_obtain + end + + def current_settings + # No caching of the settings! If we cache them and an admin disables + # this feature, an active RepositoryCheckWorker would keep going for up + # to 1 hour after the feature was disabled. + if Rails.env.test? + Gitlab::CurrentSettings.fake_application_settings + else + ApplicationSetting.current + end + end + end +end diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb new file mode 100644 index 0000000000..fe0cce9aab --- /dev/null +++ b/app/workers/repository_check/clear_worker.rb @@ -0,0 +1,17 @@ +module RepositoryCheck + class ClearWorker + include Sidekiq::Worker + + sidekiq_options retry: false + + def perform + # Do batched updates because these updates will be slow and locking + Project.select(:id).find_in_batches(batch_size: 1000) do |batch| + Project.where(id: batch.map(&:id)).update_all( + last_repository_check_failed: nil, + last_repository_check_at: nil, + ) + end + end + end +end \ No newline at end of file diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb new file mode 100644 index 0000000000..e54ae86d06 --- /dev/null +++ b/app/workers/repository_check/single_repository_worker.rb @@ -0,0 +1,36 @@ +module RepositoryCheck + class SingleRepositoryWorker + include Sidekiq::Worker + + sidekiq_options retry: false + + def perform(project_id) + project = Project.find(project_id) + project.update_columns( + last_repository_check_failed: !check(project), + last_repository_check_at: Time.now, + ) + end + + private + + def check(project) + # Use 'map do', not 'all? do', to prevent short-circuiting + [project.repository, project.wiki.repository].map do |repository| + git_fsck(repository.path_to_repo) + end.all? + end + + def git_fsck(path) + cmd = %W(nice git --git-dir=#{path} fsck) + output, status = Gitlab::Popen.popen(cmd) + + if status.zero? + true + else + Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") + false + end + end + end +end diff --git a/app/workers/repository_check_worker.rb b/app/workers/repository_check_worker.rb deleted file mode 100644 index d7ead91f94..0000000000 --- a/app/workers/repository_check_worker.rb +++ /dev/null @@ -1,61 +0,0 @@ -class RepositoryCheckWorker - include Sidekiq::Worker - - RUN_TIME = 3600 - - sidekiq_options retry: false - - def perform - start = Time.now - - # This loop will break after a little more than one hour ('a little - # more' because `git fsck` may take a few minutes), or if it runs out of - # projects to check. By default sidekiq-cron will start a new - # RepositoryCheckWorker each hour so that as long as there are repositories to - # check, only one (or two) will be checked at a time. - project_ids.each do |project_id| - break if Time.now - start >= RUN_TIME - break unless current_settings.repository_checks_enabled - - next unless try_obtain_lease(project_id) - - SingleRepositoryCheckWorker.new.perform(project_id) - end - end - - private - - # Project.find_each does not support WHERE clauses and - # Project.find_in_batches does not support ordering. So we just build an - # array of ID's. This is OK because we do it only once an hour, because - # getting ID's from Postgres is not terribly slow, and because no user - # has to sit and wait for this query to finish. - def project_ids - limit = 10_000 - never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit). - pluck(:id) - old_check_projects = Project.where('last_repository_check_at < ?', 1.week.ago). - reorder('last_repository_check_at ASC').limit(limit).pluck(:id) - never_checked_projects + old_check_projects - end - - def try_obtain_lease(id) - # Use a 24-hour timeout because on servers/projects where 'git fsck' is - # super slow we definitely do not want to run it twice in parallel. - Gitlab::ExclusiveLease.new( - "project_repository_check:#{id}", - timeout: 24.hours - ).try_obtain - end - - def current_settings - # No caching of the settings! If we cache them and an admin disables - # this feature, an active RepositoryCheckWorker would keep going for up - # to 1 hour after the feature was disabled. - if Rails.env.test? - Gitlab::CurrentSettings.fake_application_settings - else - ApplicationSetting.current - end - end -end diff --git a/app/workers/single_repository_check_worker.rb b/app/workers/single_repository_check_worker.rb deleted file mode 100644 index f6c345df8b..0000000000 --- a/app/workers/single_repository_check_worker.rb +++ /dev/null @@ -1,34 +0,0 @@ -class SingleRepositoryCheckWorker - include Sidekiq::Worker - - sidekiq_options retry: false - - def perform(project_id) - project = Project.find(project_id) - project.update_columns( - last_repository_check_failed: !check(project), - last_repository_check_at: Time.now, - ) - end - - private - - def check(project) - # Use 'map do', not 'all? do', to prevent short-circuiting - [project.repository, project.wiki.repository].map do |repository| - git_fsck(repository.path_to_repo) - end.all? - end - - def git_fsck(path) - cmd = %W(nice git --git-dir=#{path} fsck) - output, status = Gitlab::Popen.popen(cmd) - - if status.zero? - true - else - Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") - false - end - end -end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 5eb7fdff55..3124dc4306 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -241,7 +241,7 @@ Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker' Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['repository_check_worker']['cron'] ||= '20 * * * *' -Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheckWorker' +Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::BatchWorker' Settings.cron_jobs['admin_email_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker' diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 69b8244191..661fb76180 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -1,9 +1,7 @@ require 'rails_helper' feature 'Admin uses repository checks', feature: true do - before do - login_as :admin - end + before { login_as :admin } scenario 'to trigger a single check' do project = create(:empty_project) @@ -17,7 +15,12 @@ feature 'Admin uses repository checks', feature: true do end scenario 'to see a single failed repository check' do - visit_admin_project_page(broken_project) + project = create(:empty_project) + project.update_columns( + last_repository_check_failed: true, + last_repository_check_at: Time.now, + ) + visit_admin_project_page(project) page.within('.alert') do expect(page.text).to match(/Last repository check \(.* ago\) failed/) @@ -25,24 +28,16 @@ feature 'Admin uses repository checks', feature: true do end scenario 'to clear all repository checks', js: true do - project = broken_project visit admin_application_settings_path + + expect(RepositoryCheck::ClearWorker).to receive(:perform_async) - click_link 'Clear all repository checks' # pop-up should be auto confirmed + click_link 'Clear all repository checks' - expect(project.reload.last_repository_check_failed).to eq(false) + expect(page).to have_content('Started asynchronous removal of all repository check states.') end def visit_admin_project_page(project) visit admin_namespace_project_path(project.namespace, project) end - - def broken_project - project = create(:empty_project) - project.update_columns( - last_repository_check_failed: true, - last_repository_check_at: Time.now, - ) - project - end end diff --git a/spec/workers/repository_check_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb similarity index 94% rename from spec/workers/repository_check_worker_spec.rb rename to spec/workers/repository_check/batch_worker_spec.rb index 7a757658e9..51caa645f4 100644 --- a/spec/workers/repository_check_worker_spec.rb +++ b/spec/workers/repository_check/batch_worker_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe RepositoryCheckWorker do - subject { RepositoryCheckWorker.new } +describe RepositoryCheck::BatchWorker do + subject { described_class.new } it 'prefers projects that have never been checked' do projects = create_list(:project, 3) diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb new file mode 100644 index 0000000000..a3b70c7478 --- /dev/null +++ b/spec/workers/repository_check/clear_worker_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe RepositoryCheck::ClearWorker do + it 'clears repository check columns' do + project = create(:empty_project) + project.update_columns( + last_repository_check_failed: true, + last_repository_check_at: Time.now, + ) + + described_class.new.perform + project.reload + + expect(project.last_repository_check_failed).to be_nil + expect(project.last_repository_check_at).to be_nil + end +end From acf911eeae0e952aec52d0e491efb69c99fb31f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 12 Apr 2016 18:09:52 +0200 Subject: [PATCH 111/573] Fix a bug with trailing slash in bamboo_url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, improve specs for BambooService Similar to https://gitlab.com/gitlab-org/gitlab-ce/issues/3515 Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/models/project_services/bamboo_service.rb | 20 +- .../project_services/bamboo_service_spec.rb | 266 ++++++++++++++---- 3 files changed, 223 insertions(+), 64 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 382318a203..8646d99b64 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.7.0 (unreleased) - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 - Add endpoints to archive or unarchive a project !3372 + - Fix a bug whith trailing slash in bamboo_url - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 9e7f642180..060062aaf7 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -82,17 +82,17 @@ class BambooService < CiService end def build_info(sha) - url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}") + url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s if username.blank? && password.blank? - @response = HTTParty.get(parsed_url.to_s, verify: false) + @response = HTTParty.get(url, verify: false) else - get_url = "#{url}&os_authType=basic" + url << '&os_authType=basic' auth = { - username: username, - password: password, + username: username, + password: password } - @response = HTTParty.get(get_url, verify: false, basic_auth: auth) + @response = HTTParty.get(url, verify: false, basic_auth: auth) end end @@ -101,11 +101,11 @@ class BambooService < CiService if @response.code != 200 || @response['results']['results']['size'] == '0' # If actual build link can't be determined, send user to build summary page. - "#{bamboo_url}/browse/#{build_key}" + URI.join(bamboo_url, "/browse/#{build_key}").to_s else # If actual build link is available, go to build result page. result_key = @response['results']['results']['result']['planResultKey']['key'] - "#{bamboo_url}/browse/#{result_key}" + URI.join(bamboo_url, "/browse/#{result_key}").to_s end end @@ -134,7 +134,7 @@ class BambooService < CiService return unless supported_events.include?(data[:object_kind]) # Bamboo requires a GET and does not take any data. - self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", - verify: false) + url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s + self.class.get(url, verify: false) end end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index c34b2487ec..31b2c90122 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -21,74 +21,232 @@ require 'spec_helper' describe BambooService, models: true do - describe "Associations" do + describe 'Associations' do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } end - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project) } + describe 'Validations' do + describe '#bamboo_url' do + it 'does not validate the presence of bamboo_url if service is not active' do + bamboo_service = service + bamboo_service.active = false - context "when a password was previously set" do - before do - @bamboo_service = BambooService.create( - project: create(:project), - properties: { - bamboo_url: 'http://gitlab.com', - username: 'mic', - password: "password" - } - ) - end - - it "reset password if url changed" do - @bamboo_service.bamboo_url = 'http://gitlab1.com' - @bamboo_service.save - expect(@bamboo_service.password).to be_nil - end - - it "does not reset password if username changed" do - @bamboo_service.username = "some_name" - @bamboo_service.save - expect(@bamboo_service.password).to eq("password") + expect(bamboo_service).not_to validate_presence_of(:bamboo_url) end - it "does not reset password if new url is set together with password, even if it's the same password" do - @bamboo_service.bamboo_url = 'http://gitlab_edited.com' - @bamboo_service.password = 'password' - @bamboo_service.save - expect(@bamboo_service.password).to eq("password") - expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com") - end + it 'validates the presence of bamboo_url if service is active' do + bamboo_service = service + bamboo_service.active = true - it "should reset password if url changed, even if setter called multiple times" do - @bamboo_service.bamboo_url = 'http://gitlab1.com' - @bamboo_service.bamboo_url = 'http://gitlab1.com' - @bamboo_service.save - expect(@bamboo_service.password).to be_nil + expect(bamboo_service).to validate_presence_of(:bamboo_url) end end - - context "when no password was previously set" do - before do - @bamboo_service = BambooService.create( - project: create(:project), - properties: { - bamboo_url: 'http://gitlab.com', - username: 'mic' - } - ) + + describe '#build_key' do + it 'does not validate the presence of build_key if service is not active' do + bamboo_service = service + bamboo_service.active = false + + expect(bamboo_service).not_to validate_presence_of(:build_key) end - it "saves password if new url is set together with password" do - @bamboo_service.bamboo_url = 'http://gitlab_edited.com' - @bamboo_service.password = 'password' - @bamboo_service.save - expect(@bamboo_service.password).to eq("password") - expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com") + it 'validates the presence of build_key if service is active' do + bamboo_service = service + bamboo_service.active = true + + expect(bamboo_service).to validate_presence_of(:build_key) + end + end + + describe '#username' do + it 'does not validate the presence of username if service is not active' do + bamboo_service = service + bamboo_service.active = false + + expect(bamboo_service).not_to validate_presence_of(:username) end + it 'does not validate the presence of username if username is nil' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.password = nil + + expect(bamboo_service).not_to validate_presence_of(:username) + end + + it 'validates the presence of username if service is active and username is present' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.password = 'secret' + + expect(bamboo_service).to validate_presence_of(:username) + end + end + + describe '#password' do + it 'does not validate the presence of password if service is not active' do + bamboo_service = service + bamboo_service.active = false + + expect(bamboo_service).not_to validate_presence_of(:password) + end + + it 'does not validate the presence of password if username is nil' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.username = nil + + expect(bamboo_service).not_to validate_presence_of(:password) + end + + it 'validates the presence of password if service is active and username is present' do + bamboo_service = service + bamboo_service.active = true + bamboo_service.username = 'john' + + expect(bamboo_service).to validate_presence_of(:password) + end end end + + describe 'Callbacks' do + describe 'before_update :reset_password' do + context 'when a password was previously set' do + it 'resets password if url changed' do + bamboo_service = service + + bamboo_service.bamboo_url = 'http://gitlab1.com' + bamboo_service.save + + expect(bamboo_service.password).to be_nil + end + + it 'does not reset password if username changed' do + bamboo_service = service + + bamboo_service.username = 'some_name' + bamboo_service.save + + expect(bamboo_service.password).to eq('password') + end + + it "does not reset password if new url is set together with password, even if it's the same password" do + bamboo_service = service + + bamboo_service.bamboo_url = 'http://gitlab_edited.com' + bamboo_service.password = 'password' + bamboo_service.save + + expect(bamboo_service.password).to eq('password') + expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com') + end + end + + it 'saves password if new url is set together with password when no password was previously set' do + bamboo_service = service + bamboo_service.password = nil + + bamboo_service.bamboo_url = 'http://gitlab_edited.com' + bamboo_service.password = 'password' + bamboo_service.save + + expect(bamboo_service.password).to eq('password') + expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com') + end + end + end + + describe '#build_page' do + it 'returns a specific URL when status is 500' do + stub_request(status: 500) + + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') + end + + it 'returns a specific URL when response has no results' do + stub_request(body: %Q({"results":{"results":{"size":"0"}}})) + + expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') + end + + it 'returns a build URL when bamboo_url has no trailing slash' do + stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) + + expect(service(bamboo_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') + end + + it 'returns a build URL when bamboo_url has a trailing slash' do + stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) + + expect(service(bamboo_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') + end + end + + describe '#commit_status' do + it 'sets commit status to :error when status is 500' do + stub_request(status: 500) + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + + it 'sets commit status to "pending" when status is 404' do + stub_request(status: 404) + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to "pending" when response has no results' do + stub_request(body: %Q({"results":{"results":{"size":"0"}}})) + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to "success" when build state contains Success' do + stub_request(build_state: 'YAY Success!') + + expect(service.commit_status('123', 'unused')).to eq('success') + end + + it 'sets commit status to "failed" when build state contains Failed' do + stub_request(build_state: 'NO Failed!') + + expect(service.commit_status('123', 'unused')).to eq('failed') + end + + it 'sets commit status to "pending" when build state contains Pending' do + stub_request(build_state: 'NO Pending!') + + expect(service.commit_status('123', 'unused')).to eq('pending') + end + + it 'sets commit status to :error when build state is unknown' do + stub_request(build_state: 'FOO BAR!') + + expect(service.commit_status('123', 'unused')).to eq(:error) + end + end + + def service(bamboo_url: 'http://gitlab.com') + described_class.create( + project: build_stubbed(:empty_project), + properties: { + bamboo_url: bamboo_url, + username: 'mic', + password: 'password', + build_key: 'foo' + } + ) + end + + def stub_request(status: 200, body: nil, build_state: 'success') + bamboo_full_url = 'http://mic:password@gitlab.com/rest/api/latest/result?label=123&os_authType=basic' + body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}}) + + WebMock.stub_request(:get, bamboo_full_url).to_return( + status: status, + headers: { 'Content-Type' => 'application/json' }, + body: body + ) + end end From 02cfbf0db5dda8ca86f4811e5d5cb055a8cc5cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 13 Apr 2016 11:25:42 +0200 Subject: [PATCH 112/573] Refactor and expose only Gitlab::UrlBuilder.build(record) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/commit.rb | 6 +- app/services/issues/base_service.rb | 2 +- app/services/merge_requests/base_service.rb | 3 +- app/views/search/results/_note.html.haml | 2 +- lib/gitlab/note_data_builder.rb | 3 +- lib/gitlab/url_builder.rb | 74 +++++----- spec/factories/commits.rb | 12 ++ spec/lib/gitlab/note_data_builder_spec.rb | 3 +- spec/lib/gitlab/url_builder_spec.rb | 143 ++++++++++++-------- 9 files changed, 147 insertions(+), 101 deletions(-) create mode 100644 spec/factories/commits.rb diff --git a/app/models/commit.rb b/app/models/commit.rb index 11ecfcace1..d1f07ccd55 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -154,7 +154,7 @@ class Commit id: id, message: safe_message, timestamp: committed_date.xmlschema, - url: commit_url, + url: Gitlab::UrlBuilder.build(self), author: { name: author_name, email: author_email @@ -168,10 +168,6 @@ class Commit data end - def commit_url - project.present? ? "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{id}" : "" - end - # Discover issues should be closed when this commit is pushed to a project's # default branch. def closes_issues(current_user = self.committer) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 770f32de94..772f5c5fff 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -3,7 +3,7 @@ module Issues def hook_data(issue, action) issue_data = issue.to_hook_data(current_user) - issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) + issue_url = Gitlab::UrlBuilder.build(issue) issue_data[:object_attributes].merge!(url: issue_url, action: action) issue_data end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index ac5b58db86..e6837a1869 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -20,8 +20,7 @@ module MergeRequests def hook_data(merge_request, action) hook_data = merge_request.to_hook_data(current_user) - merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id) - hook_data[:object_attributes][:url] = merge_request_url + hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request) hook_data[:object_attributes][:action] = action hook_data end diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index 9544e3d3e1..d9400b1d9f 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -1,5 +1,5 @@ - project = note.project -- note_url = Gitlab::UrlBuilder.new(:note).build(note.id) +- note_url = Gitlab::UrlBuilder.build(note) - noteable_identifier = note.noteable.try(:iid) || note.noteable.id .search-result-row %h5.note-search-caption.str-truncated diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb index 18523e0aef..8bdc89a775 100644 --- a/lib/gitlab/note_data_builder.rb +++ b/lib/gitlab/note_data_builder.rb @@ -59,8 +59,7 @@ module Gitlab repository: project.hook_attrs.slice(:name, :url, :description, :homepage) } - base_data[:object_attributes][:url] = - Gitlab::UrlBuilder.new(:note).build(note.id) + base_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(note) base_data end diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index f301d42939..f1943222ed 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -4,50 +4,58 @@ module Gitlab include GitlabRoutingHelper include ActionView::RecordIdentifier - def initialize(type) - @type = type + attr_reader :object + + def self.build(object) + new(object).url end - def build(id) - case @type - when :issue - build_issue_url(id) - when :merge_request - build_merge_request_url(id) - when :note - build_note_url(id) - + def url + case object + when Commit + commit_url + when Issue + issue_url(object) + when MergeRequest + merge_request_url(object) + when Note + note_url + else + raise NotImplementedError.new("No URL builder defined for #{object.class}") end end private - def build_issue_url(id) - issue = Issue.find(id) - issue_url(issue) + def initialize(object) + @object = object end - def build_merge_request_url(id) - merge_request = MergeRequest.find(id) - merge_request_url(merge_request) + def commit_url(opts = {}) + return '' if object.project.nil? + + namespace_project_commit_url({ + namespace_id: object.project.namespace, + project_id: object.project, + id: object.id + }.merge!(opts)) end - def build_note_url(id) - note = Note.find(id) - if note.for_commit? - namespace_project_commit_url(namespace_id: note.project.namespace, - id: note.commit_id, - project_id: note.project, - anchor: dom_id(note)) - elsif note.for_issue? - issue = Issue.find(note.noteable_id) - issue_url(issue, anchor: dom_id(note)) - elsif note.for_merge_request? - merge_request = MergeRequest.find(note.noteable_id) - merge_request_url(merge_request, anchor: dom_id(note)) - elsif note.for_snippet? - snippet = Snippet.find(note.noteable_id) - project_snippet_url(snippet, anchor: dom_id(note)) + def note_url + if object.for_commit? + commit_url(id: object.commit_id, anchor: dom_id(object)) + + elsif object.for_issue? + issue = Issue.find(object.noteable_id) + issue_url(issue, anchor: dom_id(object)) + + elsif object.for_merge_request? + merge_request = MergeRequest.find(object.noteable_id) + merge_request_url(merge_request, anchor: dom_id(object)) + + elsif object.for_snippet? + snippet = Snippet.find(object.noteable_id) + project_snippet_url(snippet, anchor: dom_id(object)) end end end diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb new file mode 100644 index 0000000000..ac6eb0a789 --- /dev/null +++ b/spec/factories/commits.rb @@ -0,0 +1,12 @@ +require_relative '../support/repo_helpers' + +FactoryGirl.define do + factory :commit do + git_commit RepoHelpers.sample_commit + project factory: :empty_project + + initialize_with do + new(git_commit, project) + end + end +end diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb index da65267744..f093d0a0d8 100644 --- a/spec/lib/gitlab/note_data_builder_spec.rb +++ b/spec/lib/gitlab/note_data_builder_spec.rb @@ -4,13 +4,12 @@ describe 'Gitlab::NoteDataBuilder', lib: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:data) { Gitlab::NoteDataBuilder.build(note, user) } - let(:note_url) { Gitlab::UrlBuilder.new(:note).build(note.id) } let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors before(:each) do expect(data).to have_key(:object_attributes) expect(data[:object_attributes]).to have_key(:url) - expect(data[:object_attributes][:url]).to eq(note_url) + expect(data[:object_attributes][:url]).to eq(Gitlab::UrlBuilder.build(note)) expect(data[:object_kind]).to eq('note') expect(data[:user]).to eq(user.hook_attrs) end diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index f023be6ae4..6ffc0d6e65 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -1,77 +1,110 @@ require 'spec_helper' describe Gitlab::UrlBuilder, lib: true do - describe 'When asking for an issue' do - it 'returns the issue url' do - issue = create(:issue) - url = Gitlab::UrlBuilder.new(:issue).build(issue.id) - expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}" + describe '.build' do + context 'when passing a Commit' do + it 'returns a proper URL' do + commit = build_stubbed(:commit) + + url = described_class.build(commit) + + expect(url).to eq "#{Settings.gitlab['url']}/#{commit.project.path_with_namespace}/commit/#{commit.id}" + end end - end - describe 'When asking for an merge request' do - it 'returns the merge request url' do - merge_request = create(:merge_request) - url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id) - expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}" + context 'when passing an Issue' do + it 'returns a proper URL' do + issue = build_stubbed(:issue, iid: 42) + + url = described_class.build(issue) + + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}" + end end - end - describe 'When asking for a note on commit' do - let(:note) { create(:note_on_commit) } - let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + context 'when passing a MergeRequest' do + it 'returns a proper URL' do + merge_request = build_stubbed(:merge_request, iid: 42) - it 'returns the note url' do - expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + url = described_class.build(merge_request) + + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}" + end end - end - describe 'When asking for a note on commit diff' do - let(:note) { create(:note_on_commit_diff) } - let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + context 'when passing a Note' do + context 'on a Commit' do + it 'returns a proper URL' do + note = build_stubbed(:note_on_commit) - it 'returns the note url' do - expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" - end - end + url = described_class.build(note) - describe 'When asking for a note on issue' do - let(:issue) { create(:issue) } - let(:note) { create(:note_on_issue, noteable_id: issue.id) } - let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + end + end - it 'returns the note url' do - expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}#note_#{note.id}" - end - end + context 'on a CommitDiff' do + it 'returns a proper URL' do + note = build_stubbed(:note_on_commit_diff) - describe 'When asking for a note on merge request' do - let(:merge_request) { create(:merge_request) } - let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) } - let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + url = described_class.build(note) - it 'returns the note url' do - expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" - end - end + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + end + end - describe 'When asking for a note on merge request diff' do - let(:merge_request) { create(:merge_request) } - let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) } - let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + context 'on an Issue' do + it 'returns a proper URL' do + issue = create(:issue, iid: 42) + note = build_stubbed(:note_on_issue, noteable: issue) - it 'returns the note url' do - expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" - end - end + url = described_class.build(note) - describe 'When asking for a note on project snippet' do - let(:snippet) { create(:project_snippet) } - let(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) } - let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}#note_#{note.id}" + end + end - it 'returns the note url' do - expect(url).to eq "#{Settings.gitlab['url']}/#{snippet.project.path_with_namespace}/snippets/#{note.noteable_id}#note_#{note.id}" + context 'on a MergeRequest' do + it 'returns a proper URL' do + merge_request = create(:merge_request, iid: 42) + note = build_stubbed(:note_on_merge_request, noteable: merge_request) + + url = described_class.build(note) + + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + end + end + + context 'on a MergeRequestDiff' do + it 'returns a proper URL' do + merge_request = create(:merge_request, iid: 42) + note = build_stubbed(:note_on_merge_request_diff, noteable: merge_request) + + url = described_class.build(note) + + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + end + end + + context 'on a ProjectSnippet' do + it 'returns a proper URL' do + project_snippet = create(:project_snippet) + note = build_stubbed(:note_on_project_snippet, noteable: project_snippet) + + url = described_class.build(note) + + expect(url).to eq "#{Settings.gitlab['url']}/#{project_snippet.project.path_with_namespace}/snippets/#{note.noteable_id}#note_#{note.id}" + end + end + + context 'on another object' do + it 'returns a proper URL' do + project = build_stubbed(:project) + + expect { described_class.build(project) }. + to raise_error(NotImplementedError, 'No URL builder defined for Project') + end + end end end end From 54231aa4e036179d035ddd3641bc15a5b31883f2 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 13 Apr 2016 12:50:00 +0200 Subject: [PATCH 113/573] Styling changes to code and docs --- doc/api/projects.md | 1 - lib/api/helpers.rb | 2 +- lib/api/projects.rb | 4 +++- spec/requests/api/projects_spec.rb | 10 ++++------ 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index b25c908008..de1faadebf 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -617,7 +617,6 @@ Example response: } ``` - ### Archive a project Archives the project if the user is either admin or the project owner of this project. This action is diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index aa0597564e..54452f763a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -242,7 +242,7 @@ module API end def not_modified! - render_api_error!('304 Not modified', 304) + render_api_error!('304 Not Modified', 304) end def render_validation_error!(model) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c7fdfbfe57..cc2c7a0c50 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -284,6 +284,7 @@ module API else current_user.toggle_star(user_project) user_project.reload + present user_project, with: Entities::Project end end @@ -293,11 +294,12 @@ module API # Parameters: # id (required) - The ID of a project # Example Request: - # DELETE /projects/:id/unstar + # DELETE /projects/:id/star delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) user_project.reload + present user_project, with: Entities::Project else not_modified! diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 2a7c55fe65..fccd08bd6d 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1023,7 +1023,7 @@ describe API::API, api: true do describe 'POST /projects/:id/star' do context 'on an unstarred project' do it 'stars the project' do - post api("/projects/#{project.id}/star", user) + expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) expect(response.status).to eq(201) expect(json_response['star_count']).to eq(1) @@ -1037,10 +1037,9 @@ describe API::API, api: true do end it 'does not modify the star count' do - post api("/projects/#{project.id}/star", user) + expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } expect(response.status).to eq(304) - expect(project.star_count).to eq(1) end end end @@ -1053,7 +1052,7 @@ describe API::API, api: true do end it 'unstars the project' do - delete api("/projects/#{project.id}/star", user) + expect { delete api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1) expect(response.status).to eq(200) expect(json_response['star_count']).to eq(0) @@ -1062,10 +1061,9 @@ describe API::API, api: true do context 'on an unstarred project' do it 'does not modify the star count' do - delete api("/projects/#{project.id}/star", user) + expect { delete api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } expect(response.status).to eq(304) - expect(project.star_count).to eq(0) end end end From bf31b4495e020ef5fa45b204d2494d8d3b4d1494 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 17:07:12 +0200 Subject: [PATCH 114/573] Schema improvements suggested by Yorick --- db/migrate/20160315135439_project_add_repository_check.rb | 4 +++- db/schema.rb | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/db/migrate/20160315135439_project_add_repository_check.rb b/db/migrate/20160315135439_project_add_repository_check.rb index 5a0859a30b..8687d5d629 100644 --- a/db/migrate/20160315135439_project_add_repository_check.rb +++ b/db/migrate/20160315135439_project_add_repository_check.rb @@ -1,6 +1,8 @@ class ProjectAddRepositoryCheck < ActiveRecord::Migration def change - add_column :projects, :last_repository_check_failed, :boolean, default: false + add_column :projects, :last_repository_check_failed, :boolean + add_index :projects, :last_repository_check_failed + add_column :projects, :last_repository_check_at, :datetime end end diff --git a/db/schema.rb b/db/schema.rb index db18d56f9c..863e1f3f07 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -732,7 +732,7 @@ ActiveRecord::Schema.define(version: 20160412140240) do t.boolean "public_builds", default: true, null: false t.string "main_language" t.integer "pushes_since_gc", default: 0 - t.boolean "last_repository_check_failed", default: false + t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" end @@ -743,6 +743,7 @@ ActiveRecord::Schema.define(version: 20160412140240) do add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree + add_index "projects", ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed", using: :btree add_index "projects", ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree From 970b2ed9da79b39123d2e21e6e6d5c70779e903c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 13 Apr 2016 16:10:03 +0100 Subject: [PATCH 115/573] Updated MR compare Based on in feedback in !3228 --- app/assets/stylesheets/pages/commits.scss | 5 +++ app/views/projects/commits/_commit.html.haml | 2 +- .../merge_requests/_new_compare.html.haml | 32 +++++++------------ .../dropdowns/_branch.html.haml | 5 +++ .../dropdowns/_project.html.haml | 5 +++ .../merge_requests/update_branches.html.haml | 8 ++--- 6 files changed, 31 insertions(+), 26 deletions(-) create mode 100644 app/views/projects/merge_requests/dropdowns/_branch.html.haml create mode 100644 app/views/projects/merge_requests/dropdowns/_project.html.haml diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 6453c91d95..c8c6bbde08 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -75,6 +75,11 @@ li.commit { } } + .item-title { + display: inline-block; + max-width: 70%; + } + .commit-row-description { font-size: 14px; border-left: 1px solid #eee; diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7da8923124..34f27f1e79 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -11,7 +11,7 @@ = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } .commit-row-title - %span.item-title.str-truncated + %span.item-title = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" - if commit.description? %a.text-expander.js-toggle-button ... diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 7d7c487e97..3e2a37e050 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -16,11 +16,9 @@ = dropdown_title("Select source project") = dropdown_filter("Search projects") = dropdown_content do - - is_active = f.object.source_project_id == @merge_request.source_project.id - %ul - %li - %a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } } - = @merge_request.source_project_path + = render 'projects/merge_requests/dropdowns/project', + projects: [@merge_request.source_project], + selected: f.object.source_project_id .merge-request-select.dropdown = f.hidden_field :source_branch = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } @@ -28,11 +26,9 @@ = dropdown_title("Select source branch") = dropdown_filter("Search branches") = dropdown_content do - %ul - - @merge_request.source_branches.each do |branch| - %li - %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } } - = branch + = render 'projects/merge_requests/dropdowns/branch', + branches: @merge_request.source_branches, + selected: f.object.source_branch .panel-footer = icon('spinner spin', class: 'js-source-loading') %ul.list-unstyled.mr_source_commit @@ -50,11 +46,9 @@ = dropdown_title("Select target project") = dropdown_filter("Search projects") = dropdown_content do - %ul - - projects.each do |project| - %li - %a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } } - = project.path_with_namespace + = render 'projects/merge_requests/dropdowns/project', + projects: projects, + selected: f.object.target_project_id .merge-request-select.dropdown = f.hidden_field :target_branch = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } @@ -62,11 +56,9 @@ = dropdown_title("Select target branch") = dropdown_filter("Search branches") = dropdown_content do - %ul - - @merge_request.target_branches.each do |branch| - %li - %a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } } - = branch + = render 'projects/merge_requests/dropdowns/branch', + branches: @merge_request.target_branches, + selected: f.object.target_branch .panel-footer = icon('spinner spin', class: "js-target-loading") %ul.list-unstyled.mr_target_commit diff --git a/app/views/projects/merge_requests/dropdowns/_branch.html.haml b/app/views/projects/merge_requests/dropdowns/_branch.html.haml new file mode 100644 index 0000000000..ba8d9a5835 --- /dev/null +++ b/app/views/projects/merge_requests/dropdowns/_branch.html.haml @@ -0,0 +1,5 @@ +%ul + - branches.each do |branch| + %li + %a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } } + = branch diff --git a/app/views/projects/merge_requests/dropdowns/_project.html.haml b/app/views/projects/merge_requests/dropdowns/_project.html.haml new file mode 100644 index 0000000000..25d5dc92f8 --- /dev/null +++ b/app/views/projects/merge_requests/dropdowns/_project.html.haml @@ -0,0 +1,5 @@ +%ul + - projects.each do |project| + %li + %a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } } + = project.path_with_namespace diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml index 1b93188a10..64482973a8 100644 --- a/app/views/projects/merge_requests/update_branches.html.haml +++ b/app/views/projects/merge_requests/update_branches.html.haml @@ -1,5 +1,3 @@ -%ul - - @target_branches.each do |branch| - %li - %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } } - = branch += render 'projects/merge_requests/dropdowns/branch', +branches: @target_branches, +selected: nil From a9200d93d3e3d586302887fcaa0cf8f5fbd9a613 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 13 Apr 2016 16:30:20 +0200 Subject: [PATCH 116/573] Ensure that issues and merge requests are found --- doc/api/merge_requests.md | 1 + lib/api/issues.rb | 8 ++++---- spec/requests/api/issues_spec.rb | 6 +++--- spec/requests/api/merge_requests_spec.rb | 12 ++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 3c18ebfa31..2057f9d77a 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -680,6 +680,7 @@ Example response: "subscribed": true } ``` + ## Unsubscribe from a merge request Unsubscribes the authenticated user from a merge request to not receive diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 37d2507307..4cdecadfe0 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -239,8 +239,8 @@ module API # issue_id (required) - The ID of a project issue # Example Request: # POST /projects/:id/issues/:issue_id/subscription - post ":id/issues/:issue_id/subscription" do - issue = user_project.issues.find_by(id: params[:issue_id]) + post ':id/issues/:issue_id/subscription' do + issue = user_project.issues.find(params[:issue_id]) if issue.subscribed?(current_user) not_modified! @@ -257,8 +257,8 @@ module API # issue_id (required) - The ID of a project issue # Example Request: # DELETE /projects/:id/issues/:issue_id/subscription - delete ":id/issues/:issue_id/subscription" do - issue = user_project.issues.find_by(id: params[:issue_id]) + delete ':id/issues/:issue_id/subscription' do + issue = user_project.issues.find(params[:issue_id]) if issue.subscribed?(current_user) issue.unsubscribe(current_user) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 8361a1649e..86ea223f20 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -594,20 +594,20 @@ describe API::API, api: true do describe 'DELETE :id/issues/:issue_id/subscription' do it 'unsubscribes from an issue' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) + delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) expect(response.status).to eq(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do - post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) + delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) expect(response.status).to eq(304) end it 'returns 404 if the issue is not found' do - post api("/projects/#{project.id}/issues/123/subscription", user) + delete api("/projects/#{project.id}/issues/123/subscription", user) expect(response.status).to eq(404) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index c247fcf9c9..1fa7e76894 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -529,6 +529,12 @@ describe API::API, api: true do expect(response.status).to eq(304) end + + it 'returns 404 if the merge request is not found' do + post api("/projects/#{project.id}/merge_requests/123/subscription", user) + + expect(response.status).to eq(404) + end end describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do @@ -544,6 +550,12 @@ describe API::API, api: true do expect(response.status).to eq(304) end + + it 'returns 404 if the merge request is not found' do + post api("/projects/#{project.id}/merge_requests/123/subscription", user) + + expect(response.status).to eq(404) + end end def mr_with_later_created_and_updated_at_time From a5755812847d6866c51738a0b36927aa809b47d2 Mon Sep 17 00:00:00 2001 From: "P.S.V.R" Date: Wed, 17 Feb 2016 19:31:54 +0800 Subject: [PATCH 117/573] Use rugged to change HEAD --- CHANGELOG | 1 + app/models/project.rb | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2ab0cc1124..42f73259fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ v 8.7.0 (unreleased) - Add default scope to projects to exclude projects pending deletion - Allow to close merge requests which source projects(forks) are deleted. - Ensure empty recipients are rejected in BuildsEmailService + - Use rugged to change HEAD in Project#change_head (P.S.V.R) - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - API: Fix milestone filtering by `iid` (Robert Schilling) - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) diff --git a/app/models/project.rb b/app/models/project.rb index fadc8bb2c9..c4b0e48434 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -865,7 +865,9 @@ class Project < ActiveRecord::Base def change_head(branch) repository.before_change_head - gitlab_shell.update_repository_head(self.path_with_namespace, branch) + repository.rugged.references.create('HEAD', + "refs/heads/#{branch}", + force: true) reload_default_branch end From 31e28ebcebc054eaeef2eddba64ff2ff7ca3104f Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 12 Apr 2016 15:55:54 +0200 Subject: [PATCH 118/573] Load related MRs/branches asynchronously Currently this works by loading the HAML partials via XHR. While this is not the nicest setup it _is_ the easiest setup using the tools we currently have. Loading this data asynchronously doesn't make loading the related MRs/branches itself faster, it merely ensures that loading the issue itself is not slowed down. Fixes gitlab-org/gitlab-ce#14949 --- CHANGELOG | 1 + app/assets/javascripts/issue.js.coffee | 23 +++++++++++ app/controllers/projects/issues_controller.rb | 40 ++++++++++++++----- app/views/projects/issues/show.html.haml | 8 ++-- config/routes.rb | 2 + features/steps/shared/issuable.rb | 7 +++- 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed59bc1b25..adbcd8f8bc 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.7.0 (unreleased) - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Developers can now add custom tags to transactions (Yorick Peterse) + - Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 946d83b7bd..c7d74a12f9 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -10,6 +10,9 @@ class @Issue @initTaskList() @initIssueBtnEventListeners() + @initMergeRequests() + @initRelatedBranches() + initTaskList: -> $('.detail-page-description .js-task-list-container').taskList('enable') $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList @@ -69,3 +72,23 @@ class @Issue type: 'PATCH' url: $('form.js-issuable-update').attr('action') data: patchData + + initMergeRequests: -> + $container = $('#merge-requests') + + $.getJSON($container.data('url')) + .error -> + new Flash('Failed to load referenced merge requests', 'alert') + .success (data) -> + if 'html' of data + $container.html(data.html) + + initRelatedBranches: -> + $container = $('#related-branches') + + $.getJSON($container.data('url')) + .error -> + new Flash('Failed to load related branches', 'alert') + .success (data) -> + if 'html' of data + $container.html(data.html) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6d649e72f8..c26cfeccf1 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController include IssuableActions before_action :module_enabled - before_action :issue, only: [:edit, :update, :show] + before_action :issue, + only: [:edit, :update, :show, :referenced_merge_requests, :related_branches] # Allow read any issue before_action :authorize_read_issue!, only: [:show] @@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController # Allow issues bulk update before_action :authorize_admin_issues!, only: [:bulk_update] - # Cross-reference merge requests - before_action :closed_by_merge_requests, only: [:show] - respond_to :html def index @@ -65,8 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController @note = @project.notes.new(noteable: @issue) @notes = @issue.notes.nonawards.with_associations.fresh @noteable = @issue - @merge_requests = @issue.referenced_merge_requests(current_user) - @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch) respond_to do |format| format.html @@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController end end + def referenced_merge_requests + @merge_requests = @issue.referenced_merge_requests(current_user) + @closed_by_merge_requests = @issue.closed_by_merge_requests(current_user) + + respond_to do |format| + format.json do + render json: { + html: view_to_html_string('projects/issues/_merge_requests') + } + end + end + end + + def related_branches + merge_requests = @issue.referenced_merge_requests(current_user) + + @related_branches = @issue.related_branches - + merge_requests.map(&:source_branch) + + respond_to do |format| + format.json do + render json: { + html: view_to_html_string('projects/issues/_related_branches') + } + end + end + end + def bulk_update result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" }) end - def closed_by_merge_requests - @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user) - end - protected def issue diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6fa059cbe6..5fe5ddc081 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -64,9 +64,11 @@ = @issue.description = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') - .merge-requests - = render 'merge_requests' - = render 'related_branches' + #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)} + // This element is filled in using JavaScript. + + #related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)} + // This element is filled in using JavaScript. .content-block.content-block-small = render 'new_branch' diff --git a/config/routes.rb b/config/routes.rb index 48601b7567..688b83d2c9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -701,6 +701,8 @@ Rails.application.routes.draw do resources :issues, constraints: { id: /\d+/ } do member do post :toggle_subscription + get :referenced_merge_requests + get :related_branches end collection do post :bulk_update diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index b6d70a26c2..24b3fb6eac 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -71,13 +71,16 @@ module SharedIssuable step 'I should not see any related merge requests' do page.within '.issue-details' do - expect(page).not_to have_content('.merge-requests') + expect(page).not_to have_content('#merge-requests .merge-requests-title') end end step 'I should see the "Enterprise fix" related merge request' do - page.within '.merge-requests' do + page.within '#merge-requests .merge-requests-title' do expect(page).to have_content('1 Related Merge Request') + end + + page.within '#merge-requests ul' do expect(page).to have_content('Enterprise fix') end end From 0a37976a4dcc88c7cbb746c89de122d3d10a453d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 17:29:04 +0200 Subject: [PATCH 119/573] Updated InfluxDB/Grafana setup/import docs The grafana-dashboards repository now contains _all_ GitLab.com dashboards and thus requires some extra continuous queries to be set up. The repository now also provided a way to automatically import/export dashboards. [ci skip] --- .../performance/grafana_configuration.md | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 10ef100981..a79c8d48d3 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -61,24 +61,32 @@ contents below and paste it in to the interactive session: ``` CREATE RETENTION POLICY gitlab_30d ON gitlab DURATION 30d REPLICATION 1 DEFAULT CREATE RETENTION POLICY seven_days ON gitlab DURATION 7d REPLICATION 1 -CREATE CONTINUOUS QUERY rails_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean", percentile(sql_duration, 95.000) AS "sql_duration_95th", percentile(sql_duration, 99.000) AS "sql_duration_99th", mean(sql_duration) AS "sql_duration_mean", percentile(view_duration, 95.000) AS "view_duration_95th", percentile(view_duration, 99.000) AS "view_duration_99th", mean(view_duration) AS "view_duration_mean" INTO gitlab.seven_days.rails_transaction_timings FROM gitlab.gitlab_30d.rails_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean", percentile(sql_duration, 95.000) AS "sql_duration_95th", percentile(sql_duration, 99.000) AS "sql_duration_99th", mean(sql_duration) AS "sql_duration_mean", percentile(view_duration, 95.000) AS "view_duration_95th", percentile(view_duration, 99.000) AS "view_duration_99th", mean(view_duration) AS "view_duration_mean" INTO gitlab.seven_days.sidekiq_transaction_timings FROM gitlab.gitlab_30d.sidekiq_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.rails_transaction_counts FROM gitlab.gitlab_30d.rails_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.sidekiq_transaction_counts FROM gitlab.gitlab_30d.sidekiq_transactions GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.rails_method_call_timings FROM gitlab.gitlab_30d.rails_method_calls GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.sidekiq_method_call_timings FROM gitlab.gitlab_30d.sidekiq_method_calls GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.rails_method_call_timings_per_method FROM gitlab.gitlab_30d.rails_method_calls GROUP BY time(1m), method END -CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.sidekiq_method_call_timings_per_method FROM gitlab.gitlab_30d.sidekiq_method_calls GROUP BY time(1m), method END -CREATE CONTINUOUS QUERY rails_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.rails_memory_usage_per_minute FROM gitlab.gitlab_30d.rails_memory_usage GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.sidekiq_memory_usage_per_minute FROM gitlab.gitlab_30d.sidekiq_memory_usage GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.sidekiq_file_descriptors_per_minute FROM gitlab.gitlab_30d.sidekiq_file_descriptors GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.rails_file_descriptors_per_minute FROM gitlab.gitlab_30d.rails_file_descriptors GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.rails_gc_counts_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.sidekiq_gc_counts_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.rails_gc_timings_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.sidekiq_gc_timings_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY rails_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.rails_gc_major_minor_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END -CREATE CONTINUOUS QUERY sidekiq_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.sidekiq_gc_major_minor_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END +CREATE CONTINUOUS QUERY rails_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.rails_transaction_counts FROM rails_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.sidekiq_transaction_counts FROM sidekiq_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.rails_method_call_timings FROM rails_method_calls GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.sidekiq_method_call_timings FROM sidekiq_method_calls GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.rails_method_call_timings_per_method FROM rails_method_calls GROUP BY time(1m), method END; +CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.sidekiq_method_call_timings_per_method FROM sidekiq_method_calls GROUP BY time(1m), method END; +CREATE CONTINUOUS QUERY rails_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.rails_memory_usage_per_minute FROM rails_memory_usage GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.sidekiq_memory_usage_per_minute FROM sidekiq_memory_usage GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.sidekiq_file_descriptors_per_minute FROM sidekiq_file_descriptors GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.rails_file_descriptors_per_minute FROM rails_file_descriptors GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.rails_gc_counts_per_minute FROM rails_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.sidekiq_gc_counts_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.rails_gc_timings_per_minute FROM rails_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.sidekiq_gc_timings_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.rails_gc_major_minor_per_minute FROM rails_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.sidekiq_gc_major_minor_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_allowed_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_allowed_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_allowed_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_allowed_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_authorized_keys_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_internal_authorized_keys_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY rails_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.rails_transaction_timings FROM rails_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY sidekiq_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.sidekiq_transaction_timings FROM sidekiq_transactions GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_transaction_counts FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END; +CREATE CONTINUOUS QUERY grape_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.grape_transaction_timings FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END; ``` ## Import Dashboards @@ -106,6 +114,10 @@ navigate away. Repeat this process for each dashboard you wish to import. +Alternatively you can automatically import all the dashboards into your Grafana +instance. See the README of the [Grafana dashboards][grafana-dashboards] +repository for more information on this process. + [grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards --- From 6f6d2d0ad5039fa8ace7e09c4f5867bb680b0f79 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 13 Apr 2016 17:33:25 +0200 Subject: [PATCH 120/573] Use more conservative limits --- app/workers/repository_check/batch_worker.rb | 2 +- app/workers/repository_check/clear_worker.rb | 4 ++-- spec/workers/repository_check/batch_worker_spec.rb | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb index 16cd77a9bf..44b3145d50 100644 --- a/app/workers/repository_check/batch_worker.rb +++ b/app/workers/repository_check/batch_worker.rb @@ -35,7 +35,7 @@ module RepositoryCheck limit = 10_000 never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit). pluck(:id) - old_check_projects = Project.where('last_repository_check_at < ?', 1.week.ago). + old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago). reorder('last_repository_check_at ASC').limit(limit).pluck(:id) never_checked_projects + old_check_projects end diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb index fe0cce9aab..9c3347a704 100644 --- a/app/workers/repository_check/clear_worker.rb +++ b/app/workers/repository_check/clear_worker.rb @@ -5,8 +5,8 @@ module RepositoryCheck sidekiq_options retry: false def perform - # Do batched updates because these updates will be slow and locking - Project.select(:id).find_in_batches(batch_size: 1000) do |batch| + # Do small batched updates because these updates will be slow and locking + Project.select(:id).find_in_batches(batch_size: 100) do |batch| Project.where(id: batch.map(&:id)).update_all( last_repository_check_failed: nil, last_repository_check_at: nil, diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb index 51caa645f4..f486e45dda 100644 --- a/spec/workers/repository_check/batch_worker_spec.rb +++ b/spec/workers/repository_check/batch_worker_spec.rb @@ -5,17 +5,17 @@ describe RepositoryCheck::BatchWorker do it 'prefers projects that have never been checked' do projects = create_list(:project, 3) - projects[0].update_column(:last_repository_check_at, 1.month.ago) - projects[2].update_column(:last_repository_check_at, 3.weeks.ago) + projects[0].update_column(:last_repository_check_at, 4.months.ago) + projects[2].update_column(:last_repository_check_at, 3.months.ago) expect(subject.perform).to eq(projects.values_at(1, 0, 2).map(&:id)) end it 'sorts projects by last_repository_check_at' do projects = create_list(:project, 3) - projects[0].update_column(:last_repository_check_at, 2.weeks.ago) - projects[1].update_column(:last_repository_check_at, 1.month.ago) - projects[2].update_column(:last_repository_check_at, 3.weeks.ago) + projects[0].update_column(:last_repository_check_at, 2.months.ago) + projects[1].update_column(:last_repository_check_at, 4.months.ago) + projects[2].update_column(:last_repository_check_at, 3.months.ago) expect(subject.perform).to eq(projects.values_at(1, 2, 0).map(&:id)) end @@ -23,7 +23,7 @@ describe RepositoryCheck::BatchWorker do it 'excludes projects that were checked recently' do projects = create_list(:project, 3) projects[0].update_column(:last_repository_check_at, 2.days.ago) - projects[1].update_column(:last_repository_check_at, 1.month.ago) + projects[1].update_column(:last_repository_check_at, 2.months.ago) projects[2].update_column(:last_repository_check_at, 3.days.ago) expect(subject.perform).to eq([projects[1].id]) From 2783877e869c7bfec30423f0a1e73bc33c269453 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 13 Apr 2016 10:54:51 -0500 Subject: [PATCH 121/573] Move declarations --- app/assets/javascripts/todos.js.coffee | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 37b4d2f66c..08b29bd821 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -73,14 +73,10 @@ class @Todos getTodosPerPage: -> @el.data('perPage') - redirectIfNeeded: (total) -> currPages = @getTotalPages() currPage = @getCurrentPage() - newPages = Math.ceil(total / @getTodosPerPage()) - url = location.href # Includes query strings - # Refresh if no remaining Todos if not total location.reload() @@ -89,6 +85,9 @@ class @Todos # Do nothing if no pagination return if not currPages + newPages = Math.ceil(total / @getTodosPerPage()) + url = location.href # Includes query strings + # If new total of pages is different than we have now if newPages isnt currPages # Redirect to previous page if there's one available From 28a7fe25fdf28042630282ace35e37310c8f0a12 Mon Sep 17 00:00:00 2001 From: Michael Greene Date: Tue, 5 Apr 2016 12:05:55 -0500 Subject: [PATCH 122/573] Allow back dating issues on update --- CHANGELOG | 2 +- doc/api/issues.md | 1 + lib/api/issues.rb | 7 +++++-- spec/requests/api/issues_spec.rb | 20 ++++++++++++++++---- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 21f24b5b61..e5b8b9d7ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,11 +18,11 @@ v 8.7.0 (unreleased) - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) - Expose label description in API (Mariusz Jachimowicz) - - Allow back dating on issues when created through the API - API: Ability to update a group (Robert Schilling) - API: Ability to move issues (Robert Schilling) - Fix Error 500 after renaming a project path (Stan Hu) - Fix a bug whith trailing slash in teamcity_url (Charles May) + - Allow back dating on issues when created or updated through the API - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 diff --git a/doc/api/issues.md b/doc/api/issues.md index 42024becc3..3e78149f44 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -298,6 +298,7 @@ PUT /projects/:id/issues/:issue_id | `milestone_id` | integer | no | The ID of a milestone to assign the issue to | | `labels` | string | no | Comma-separated label names for an issue | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | +| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | ```bash curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4cdecadfe0..8aa08fd5ac 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -117,7 +117,7 @@ module API # assignee_id (optional) - The ID of a user to assign issue # milestone_id (optional) - The ID of a milestone to assign issue # labels (optional) - The labels of an issue - # created_at (optional) - The date + # created_at (optional) - Date time string, ISO 8601 formatted # Example Request: # POST /projects/:id/issues post ":id/issues" do @@ -166,12 +166,15 @@ module API # milestone_id (optional) - The ID of a milestone to assign issue # labels (optional) - The labels of an issue # state_event (optional) - The state event of an issue (close|reopen) + # updated_at (optional) - Date time string, ISO 8601 formatted # Example Request: # PUT /projects/:id/issues/:issue_id put ":id/issues/:issue_id" do issue = user_project.issues.find(params[:issue_id]) authorize! :update_issue, issue - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] + keys = [:title, :description, :assignee_id, :milestone_id, :state_event] + keys << :updated_at if current_user.admin? || user_project.owner == current_user + attrs = attributes_for_keys(keys) # Validate label names in advance if (errors = validate_label_params(params)).any? diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 86ea223f20..f88e39cad9 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -321,13 +321,13 @@ describe API::API, api: true do end context 'when an admin or owner makes the request' do - it "accepts the creation date to be set" do + it 'accepts the creation date to be set' do + creation_time = 2.weeks.ago post api("/projects/#{project.id}/issues", user), - title: 'new issue', labels: 'label, label2', created_at: 2.weeks.ago + title: 'new issue', labels: 'label, label2', created_at: creation_time expect(response.status).to eq(201) - # this take about a second, so probably not equal - expect(Time.parse(json_response['created_at'])).to be <= 2.weeks.ago + expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) end end end @@ -478,6 +478,18 @@ describe API::API, api: true do expect(json_response['labels']).to include 'label2' expect(json_response['state']).to eq "closed" end + + context 'when an admin or owner makes the request' do + it 'accepts the update date to be set' do + update_time = 2.weeks.ago + put api("/projects/#{project.id}/issues/#{issue.id}", user), + labels: 'label3', state_event: 'close', updated_at: update_time + expect(response.status).to eq(200) + + expect(json_response['labels']).to include 'label3' + expect(Time.parse(json_response['updated_at'])).to be_within(1.second).of(update_time) + end + end end describe "DELETE /projects/:id/issues/:issue_id" do From c1467f5d97c04c22e2119ace084bb016f8f53d48 Mon Sep 17 00:00:00 2001 From: Michael Greene Date: Tue, 5 Apr 2016 13:04:11 -0500 Subject: [PATCH 123/573] Allow back dating notes on creation --- CHANGELOG | 1 + doc/api/notes.md | 1 + lib/api/notes.rb | 5 +++++ spec/requests/api/notes_spec.rb | 13 +++++++++++++ 4 files changed, 20 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e5b8b9d7ce..9baf6516ef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.7.0 (unreleased) - Fix Error 500 after renaming a project path (Stan Hu) - Fix a bug whith trailing slash in teamcity_url (Charles May) - Allow back dating on issues when created or updated through the API + - Allow back dating on issue notes when created through the API - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 diff --git a/doc/api/notes.md b/doc/api/notes.md index 2e0936f11b..7aa1c2155b 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -89,6 +89,7 @@ Parameters: - `id` (required) - The ID of a project - `issue_id` (required) - The ID of an issue - `body` (required) - The content of a note +- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z ### Modify existing issue note diff --git a/lib/api/notes.rb b/lib/api/notes.rb index a1c98f5e8f..71a53e6f0d 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -61,6 +61,7 @@ module API # id (required) - The ID of a project # noteable_id (required) - The ID of an issue or snippet # body (required) - The content of a note + # created_at (optional) - The date # Example Request: # POST /projects/:id/issues/:noteable_id/notes # POST /projects/:id/snippets/:noteable_id/notes @@ -73,6 +74,10 @@ module API noteable_id: params[noteable_id_str] } + if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) + opts[:created_at] = params[:created_at] + end + @note = ::Notes::CreateService.new(user_project, current_user, opts).execute if @note.valid? diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index a467bc935a..ec9eda0a2e 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -158,6 +158,19 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!' expect(response.status).to eq(401) end + + context 'when an admin or owner makes the request' do + it 'accepts the creation date to be set' do + creation_time = 2.weeks.ago + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), + body: 'hi!', created_at: creation_time + expect(response.status).to eq(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['author']['username']).to eq(user.username) + expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) + end + end + end context "when noteable is a Snippet" do From 2244aaf98f6c9b68e30febf6b80f2c0d965e541a Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 13 Apr 2016 13:20:57 +0300 Subject: [PATCH 124/573] Redis configuration consistency --- config/initializers/session_store.rb | 2 +- config/initializers/sidekiq.rb | 6 ++---- lib/gitlab/redis.rb | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 7028525587..88cb859871 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -14,7 +14,7 @@ if Rails.env.test? Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session" else redis_config = Gitlab::Redis.redis_store_options - redis_config[:namespace] = 'session:gitlab' + redis_config[:namespace] = Gitlab::Redis::SESSION_NAMESPACE Gitlab::Application.config.session_store( :redis_store, # Using the cookie_store would enable session replay attacks. diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 9182d92980..f1eec67488 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,9 +1,7 @@ -SIDEKIQ_REDIS_NAMESPACE = 'resque:gitlab' - Sidekiq.configure_server do |config| config.redis = { url: Gitlab::Redis.url, - namespace: SIDEKIQ_REDIS_NAMESPACE + namespace: Gitlab::Redis::SIDEKIQ_NAMESPACE } config.server_middleware do |chain| @@ -30,6 +28,6 @@ end Sidekiq.configure_client do |config| config.redis = { url: Gitlab::Redis.url, - namespace: SIDEKIQ_REDIS_NAMESPACE + namespace: Gitlab::Redis::SIDEKIQ_NAMESPACE } end diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 319447669d..5c352c96de 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -1,6 +1,8 @@ module Gitlab class Redis CACHE_NAMESPACE = 'cache:gitlab' + SESSION_NAMESPACE = 'session:gitlab' + SIDEKIQ_NAMESPACE = 'resque:gitlab' attr_reader :url From 11f46b459e9d97900cb71f7f6bd313b5e18fbaa6 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 13 Apr 2016 15:28:10 -0300 Subject: [PATCH 125/573] Setup visibility level for project when transfering for a group --- app/services/projects/transfer_service.rb | 21 +++++++++++++---- app/views/projects/edit.html.haml | 1 + .../projects/transfer_service_spec.rb | 23 +++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 2e73465446..0d8f8c6fbe 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -34,9 +34,12 @@ module Projects raise TransferError.new("Project with same path in target namespace already exists") end - # Apply new namespace id - project.namespace = new_namespace - project.save! + # Apply new namespace id and visibility level + project.tap do |p| + p.namespace = new_namespace + setup_visibility_level(p, new_namespace) + p.save! + end # Notifications project.send_move_instructions(old_path) @@ -56,7 +59,7 @@ module Projects Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) project.old_path_with_namespace = old_path - + SystemHooksService.new.execute_hooks_for(project, :transfer) true end @@ -68,5 +71,15 @@ module Projects namespace.id != project.namespace_id && current_user.can?(:create_projects, namespace) end + + private + + def setup_visibility_level(project, new_namespace) + return unless new_namespace.is_a?(Group) + + if project.visibility_level > new_namespace.visibility_level + project.visibility_level = new_namespace.visibility_level + end + end end end diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 6d872cd0b2..76a4f41193 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -210,6 +210,7 @@ %li Be careful. Changing the project's namespace can have unintended side effects. %li You can only transfer the project to namespaces you manage. %li You will need to update your local repositories to point to the new location. + %li Project visibility level will be changed to match namespace rules when transfering to a group. .form-actions = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) } - else diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index c46259431a..0601731733 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -38,4 +38,27 @@ describe Projects::TransferService, services: true do def transfer_project(project, user, new_namespace) Projects::TransferService.new(project, user).execute(new_namespace) end + + context 'visibility level' do + let(:internal_group) { create(:group, :internal) } + + before { internal_group.add_owner(user) } + + context 'when namespace visibility level < project visibility level' do + let(:public_project) { create(:project, :public, namespace: user.namespace) } + + before { transfer_project(public_project, user, internal_group) } + + it { expect(public_project.visibility_level).to eq(internal_group.visibility_level) } + end + + context 'when namespace visibility level > project visibility level' do + let(:private_project) { create(:project, :private, namespace: user.namespace) } + + before { transfer_project(private_project, user, internal_group) } + + it { expect(private_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) } + end + end + end From 6b3a53848c68c8fd0931de9b7c6ab15b53b9475b Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 13 Apr 2016 15:30:50 -0300 Subject: [PATCH 126/573] Add changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 5399e3e5b8..416fffece6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.7.0 (unreleased) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) - Add setting for customizing the list of trusted proxies !3524 + - Allow projects to be transfered to a lower visibility level group - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) From dfadbe5e45c54a0e76cb712ba8851c72bc83851f Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Tue, 29 Mar 2016 10:52:25 -0400 Subject: [PATCH 127/573] Initial mutli label filter --- app/assets/javascripts/issues.js.coffee | 2 ++ app/assets/javascripts/labels_select.js.coffee | 11 ++++++++++- app/views/shared/issuable/_label_dropdown.html.haml | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 0d9f2094c2..084ae6e7ef 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -52,7 +52,9 @@ filterResults: (form) => $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') formAction = form.attr('action') + console.log form.find("input[type='hidden'][name='label_names[]']") formData = form.serialize() + console.log 'formData', formData issuesUrl = formAction issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}") issuesUrl += formData diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index bc80980acb..f864f4fd46 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -234,11 +234,20 @@ class @LabelsSelect label.id hidden: -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + $selectbox.hide() # display:block overrides the hide-collapse rule $value.removeAttr('style') if $dropdown.hasClass 'js-multiselect' - saveLabelData() + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass('js-filter-submit') + $dropdown.closest('form').submit() + else + saveLabelData() multiSelect: $dropdown.hasClass 'js-multiselect' clicked: (label) -> diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index f722e61eea..f57d837c45 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -2,6 +2,7 @@ = hidden_field_tag(:label_name, params[:label_name]) .dropdown %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} + %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %span.dropdown-toggle-text = h(params[:label_name].presence || "Label") = icon('chevron-down') From 19b9df2d4fe73bb30de1711a15664eedb2e46afa Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 12:47:32 -0400 Subject: [PATCH 128/573] storing multiple values for comma seperation --- app/assets/javascripts/issues.js.coffee | 6 ++---- app/assets/javascripts/labels_select.js.coffee | 16 +++++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 084ae6e7ef..dca5bc55eb 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -49,16 +49,14 @@ Issues.filterResults $("#issue_search_form") , 500) - filterResults: (form) => + filterResults: (form, inputs) => + console.log('form', form) $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') formAction = form.attr('action') - console.log form.find("input[type='hidden'][name='label_names[]']") formData = form.serialize() - console.log 'formData', formData issuesUrl = formAction issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}") issuesUrl += formData - $.ajax type: "GET" url: formAction diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index f864f4fd46..9e66f8ae96 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -243,7 +243,10 @@ class @LabelsSelect $value.removeAttr('style') if $dropdown.hasClass 'js-multiselect' if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) - Issues.filterResults $dropdown.closest('form') + selectedLabels = $dropdown + .closest('form') + .find("input[type='hidden'][name='#{$dropdown.data('field-name')}']") + Issues.filterResults $dropdown.closest('form'), selectedLabels else if $dropdown.hasClass('js-filter-submit') $dropdown.closest('form').submit() else @@ -254,15 +257,18 @@ class @LabelsSelect page = $('body').data 'page' isIssueIndex = page is 'projects:issues:index' isMRIndex = page is page is 'projects:merge_requests:index' - + console.log 'clicked' if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) - selectedLabel = label.title - - Issues.filterResults $dropdown.closest('form') + if not $dropdown.hasClass 'js-multiselect' + selectedLabel = label.title + Issues.filterResults $dropdown.closest('form') else if $dropdown.hasClass 'js-filter-submit' + console.log 'clicked else if' $dropdown.closest('form').submit() else + console.log 'clicked else' if $dropdown.hasClass 'js-multiselect' + console.log 'clicked else --> if' return else saveLabelData() From e684480eebe803c21545b3a8ea5a972c54ba7ea4 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 7 Apr 2016 14:57:21 -0400 Subject: [PATCH 129/573] Proper selecting multiple labels. --- app/assets/javascripts/issues.js.coffee | 44 +++++++++++++++++-- .../javascripts/labels_select.js.coffee | 13 +++--- app/helpers/issuables_helper.rb | 13 ++++++ .../shared/issuable/_label_dropdown.html.haml | 2 +- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index dca5bc55eb..40a89d8c2f 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -49,11 +49,49 @@ Issues.filterResults $("#issue_search_form") , 500) - filterResults: (form, inputs) => - console.log('form', form) + filterResults: (form) => + # Assume for now there is only 1 multi select field + # Find the hidden inputs with square brackets + $multiInputs = form.find('input[name$="[]"]') + if $multiInputs.length + # get the name of one of them + multiInputName = $multiInputs + .first() + .attr('name') + + # get the singular name by + # removing the square brackets from the name + singularName = multiInputName.replace('[]','') + # clone the form so we can mess around with it. + $clonedForm = form.clone() + + # get those inputs from the cloned form + $inputs = $clonedForm + .find("input[name='#{multiInputName}']") + + # make a comma seperated list of labels + commaSeperated = $inputs + .map( -> $(this).val()) + .get() + .join(',') + # append on a hidden input with the comma + # seperated values in it + $clonedForm.append( + $('') + .attr('type','hidden') + .attr('name', singularName) + .val(commaSeperated) + ) + # remove the multi inputs from the + # cloned form so they don't get serialized + $inputs.remove() + # serialize the cloned form + formData = $clonedForm.serialize() + else + formData = form.serialize() + $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') formAction = form.attr('action') - formData = form.serialize() issuesUrl = formAction issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}") issuesUrl += formData diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 9e66f8ae96..9cebc26e66 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -6,7 +6,7 @@ class @LabelsSelect labelUrl = $dropdown.data('labels') issueUpdateURL = $dropdown.data('issueUpdate') selectedLabel = $dropdown.data('selected') - if selectedLabel? + if selectedLabel? and not $dropdown.hasClass 'js-multiselect' selectedLabel = selectedLabel.split(',') newLabelField = $('#new_label_name') newColorField = $('#new_label_color') @@ -246,7 +246,12 @@ class @LabelsSelect selectedLabels = $dropdown .closest('form') .find("input[type='hidden'][name='#{$dropdown.data('field-name')}']") - Issues.filterResults $dropdown.closest('form'), selectedLabels + Issues.filterResults( + $dropdown.closest('form'), + selectedLabels, + $dropdown.data('singularFieldName'), + $dropdown.data('fieldName') + ) else if $dropdown.hasClass('js-filter-submit') $dropdown.closest('form').submit() else @@ -257,18 +262,14 @@ class @LabelsSelect page = $('body').data 'page' isIssueIndex = page is 'projects:issues:index' isMRIndex = page is page is 'projects:merge_requests:index' - console.log 'clicked' if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if not $dropdown.hasClass 'js-multiselect' selectedLabel = label.title Issues.filterResults $dropdown.closest('form') else if $dropdown.hasClass 'js-filter-submit' - console.log 'clicked else if' $dropdown.closest('form').submit() else - console.log 'clicked else' if $dropdown.hasClass 'js-multiselect' - console.log 'clicked else --> if' return else saveLabelData() diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index b14b8218d0..d5af0116cf 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -16,6 +16,19 @@ module IssuablesHelper base_issuable_scope(issuable).where('iid > ?', issuable.iid).last end + def multi_label_name(current_labels, default_label) + if current_labels.presence + if current_labels.include? ',' + labels = current_labels.split(',') + "#{labels[0]} +#{labels.count - 1} more" + else + current_labels + end + else + default_label + end + end + def issuable_json_path(issuable) project = issuable.project diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index f57d837c45..4ded2d98f3 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -4,7 +4,7 @@ %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %span.dropdown-toggle-text - = h(params[:label_name].presence || "Label") + = h(multi_label_name(params[:label_name], "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable .dropdown-page-one From 1617d1e0267f389e040772bfed0dd29e34b25c06 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Fri, 8 Apr 2016 14:23:51 -0400 Subject: [PATCH 130/573] Move functionality to label[] --- app/assets/javascripts/issues.js.coffee | 41 ++----------------------- app/helpers/issuables_helper.rb | 20 ++++++++---- 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 40a89d8c2f..fc9f6301bc 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -50,45 +50,8 @@ , 500) filterResults: (form) => - # Assume for now there is only 1 multi select field - # Find the hidden inputs with square brackets - $multiInputs = form.find('input[name$="[]"]') - if $multiInputs.length - # get the name of one of them - multiInputName = $multiInputs - .first() - .attr('name') - - # get the singular name by - # removing the square brackets from the name - singularName = multiInputName.replace('[]','') - # clone the form so we can mess around with it. - $clonedForm = form.clone() - - # get those inputs from the cloned form - $inputs = $clonedForm - .find("input[name='#{multiInputName}']") - - # make a comma seperated list of labels - commaSeperated = $inputs - .map( -> $(this).val()) - .get() - .join(',') - # append on a hidden input with the comma - # seperated values in it - $clonedForm.append( - $('') - .attr('type','hidden') - .attr('name', singularName) - .val(commaSeperated) - ) - # remove the multi inputs from the - # cloned form so they don't get serialized - $inputs.remove() - # serialize the cloned form - formData = $clonedForm.serialize() - else - formData = form.serialize() + + formData = form.serialize() $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') formAction = form.attr('action') diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index d5af0116cf..14e624cb7c 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -17,15 +17,23 @@ module IssuablesHelper end def multi_label_name(current_labels, default_label) - if current_labels.presence - if current_labels.include? ',' - labels = current_labels.split(',') - "#{labels[0]} +#{labels.count - 1} more" + # current_labels may be a string from before + if current_labels.respond_to?('any?') + if current_labels.any? + if current_labels.count > 1 + "#{current_labels[0]} +#{current_labels.count - 1} more" + else + current_labels[0] + end + else + default_label + end + else + if current_labels.nil? + default_label else current_labels end - else - default_label end end From 42e0625dfb2a791affd592df1f879083702e6f86 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Sat, 9 Apr 2016 00:09:09 -0400 Subject: [PATCH 131/573] Filter by multiple labels with little animation. --- app/assets/javascripts/issues.js.coffee | 36 ++++++++++++++++++- .../javascripts/labels_select.js.coffee | 9 +++-- app/assets/javascripts/lib/animate.js.coffee | 32 ++++++++++++++--- app/controllers/projects/issues_controller.rb | 5 +-- app/views/shared/_label_row.html.haml | 8 ++--- app/views/shared/issuable/_filter.html.haml | 6 ++-- .../shared/issuable/_label_dropdown.html.haml | 4 ++- 7 files changed, 79 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index fc9f6301bc..320b92d2a6 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -1,5 +1,6 @@ @Issues = init: -> + Issues.initTemplates() Issues.initSearch() Issues.initChecks() @@ -15,6 +16,15 @@ else $(this).html totalIssues - 1 + initTemplates: -> + Issue.labelRow = _.template( + '<% _.each(labels, function(label){ %> + + <%= label.title %> + + <% }); %>' + ) + reload: -> Issues.initChecks() $('#filter_issue_search').val($('#issue_search').val()) @@ -50,7 +60,6 @@ , 500) filterResults: (form) => - formData = form.serialize() $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') @@ -70,6 +79,31 @@ history.replaceState {page: issuesUrl}, document.title, issuesUrl Issues.reload() Issues.updateStateFilters() + $filteredLabels = $('.filtered-labels') + $filteredLabelsSpans = $filteredLabels.find('span') + gl.animate.animateEach( + $filteredLabelsSpans, + 'fadeOutDown', 20, { + cssStart: { + opacity: 1 + }, + cssEnd: { + opacity: 0 + } + }).then( -> + $filteredLabels.html(Issue.labelRow(data)) + $spans = $filteredLabels.find('span') + $spans.css('opacity',0) + return gl.animate.animateEach($spans, 'fadeInUp', 20, { + cssStart: { + opacity: 0 + }, + cssEnd: { + opacity: 1 + } + }) + ) + dataType: "json" checkChanged: -> diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 9cebc26e66..97a813577e 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -16,6 +16,7 @@ class @LabelsSelect abilityName = $dropdown.data('ability-name') $selectbox = $dropdown.closest('.selectbox') $block = $selectbox.closest('.block') + $form = $dropdown.closest('form') $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span') $value = $block.find('.value') $loading = $block.find('.block-loading').fadeOut() @@ -171,7 +172,7 @@ class @LabelsSelect .find('a') .each((i) -> setTimeout(=> - glAnimate($(@), 'pulse') + gl.animate.animate($(@), 'pulse') ,200 * i ) ) @@ -201,9 +202,9 @@ class @LabelsSelect renderRow: (label) -> selectedClass = '' - if $selectbox.find("input[type='hidden']\ + if $form.find("input[type='hidden']\ [name='#{$dropdown.data('field-name')}']\ - [value='#{label.id}']").length + [value='#{this.id(label)}']").length selectedClass = 'is-active' color = if label.color? then "" else "" @@ -248,8 +249,6 @@ class @LabelsSelect .find("input[type='hidden'][name='#{$dropdown.data('field-name')}']") Issues.filterResults( $dropdown.closest('form'), - selectedLabels, - $dropdown.data('singularFieldName'), $dropdown.data('fieldName') ) else if $dropdown.hasClass('js-filter-submit') diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee index 8f892b5a2b..64aef4c6d4 100644 --- a/app/assets/javascripts/lib/animate.js.coffee +++ b/app/assets/javascripts/lib/animate.js.coffee @@ -1,13 +1,37 @@ ((w) -> + if not w.gl? then w.gl = {} + if not gl.animate? then gl.animate = {} - w.glAnimate = ($el, animation, done) -> + gl.animate.animate = ($el, animation, options, done) -> + if options?.cssStart? + $el.css(options.cssStart) $el - .removeClass() + .removeClass(animation + ' animated') .addClass(animation + ' animated') .one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', -> - $(this).removeClass() + $(this).removeClass(animation + ' animated') + if done? + done() + if options?.cssEnd? + $el.css(options.cssEnd) return return - return + gl.animate.animateEach = ($els, animation, time, options, done) -> + dfd = $.Deferred() + $els.each((i) -> + setTimeout(=> + $this = $(@) + gl.animate.animate($this, animation, options, => + if i is $els.length - 1 + dfd.resolve() + if done? + done() + ) + ,time * i + ) + return + ) + return dfd.promise() + return ) window \ No newline at end of file diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c26cfeccf1..8ce6772c40 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -33,14 +33,15 @@ class Projects::IssuesController < Projects::ApplicationController end @issues = @issues.page(params[:page]) - @label = @project.labels.find_by(title: params[:label_name]) + @labels = @project.labels.where(title: params[:label_name]) respond_to do |format| format.html format.atom { render layout: false } format.json do render json: { - html: view_to_html_string("projects/issues/_issues") + html: view_to_html_string("projects/issues/_issues"), + labels: @labels } end end diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index b38c5e18ef..f81a04a3c8 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -1,5 +1,3 @@ -%span.label-row - %span.label-name - = link_to_label(label, tooltip: false) - %span.prepend-left-10 - = markdown(label.description, pipeline: :single_line) +- labels.each do |l| + %span.label-row + = link_to_label(l, tooltip: false) \ No newline at end of file diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 921eaefd79..02982516de 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -46,9 +46,9 @@ .filter-item.inline = button_tag "Update issues", class: "btn update_selected_issues btn-save" -- if @label - .gray-content-block.second-block - = render "shared/label_row", label: @label +- if @labels + .gray-content-block.second-block.filtered-labels + = render "shared/label_row", labels: @labels :javascript new UsersSelect(); diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 4ded2d98f3..6df1bd0ca3 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,5 +1,7 @@ - if params[:label_name].present? - = hidden_field_tag(:label_name, params[:label_name]) + - if params[:label_name].respond_to?('any?') + - params[:label_name].each do |label| + = hidden_field_tag "label_name[]", label, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} From dc13f7c31dee2c0515c36fba2398bc8b843a8108 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 12:05:10 +0200 Subject: [PATCH 132/573] Return unique issues when using multiple labels This ensures that IssuableFinder returns a collection of unique issues, even when filtering issues using multiple labels. --- app/finders/issuable_finder.rb | 4 +++- spec/finders/issues_finder_spec.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f1df6832bf..d7c5b0a598 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -278,7 +278,9 @@ class IssuableFinder end end - items + # When filtering by multiple labels we may end up duplicating issues (if one + # has multiple labels). This ensures we only return unique issues. + items.distinct end def label_names diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index b164805546..bc607a2975 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -62,6 +62,22 @@ describe IssuesFinder do expect(issues).to eq([issue2]) end + it 'returns unique issues when filtering by multiple labels' do + label2 = create(:label, project: project2) + + create(:label_link, label: label2, target: issue2) + + params = { + scope: 'all', + label_name: [label.title, label2.title].join(','), + state: 'opened' + } + + issues = IssuesFinder.new(user, params).execute + + expect(issues).to eq([issue2]) + end + it 'should filter by no label name' do params = { scope: "all", label_name: Label::None.title, state: 'opened' } issues = IssuesFinder.new(user, params).execute From f2aacc25fb5ded3c4aa70495d05aaad2cf9b23f8 Mon Sep 17 00:00:00 2001 From: Hyunwoo Jung Date: Wed, 13 Apr 2016 21:19:50 +0000 Subject: [PATCH 133/573] Fix typos in CI docs. --- doc/ci/quick_start/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 9aba4326e1..6a42a935ab 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you and configure your GitLab project to use a [Runner], then each merge request or push triggers a build. -The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it +The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it runs three [stages]: `build`, `test`, and `deploy`. If everything runs OK (no non-zero return values), you'll get a nice green From 22c089c2fd8ebc5c2e1f5768afd3d5accfd6c9bb Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 13 Apr 2016 17:31:42 -0400 Subject: [PATCH 134/573] Removes dup dropdown --- app/views/shared/issuable/_label_dropdown.html.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 6df1bd0ca3..3eaa45258f 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -3,7 +3,6 @@ - params[:label_name].each do |label| = hidden_field_tag "label_name[]", label, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %span.dropdown-toggle-text = h(multi_label_name(params[:label_name], "Label")) From a31c1dccb69e6c5999af31c2be4e6ee71043a5e3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 13 Apr 2016 16:43:07 -0500 Subject: [PATCH 135/573] Add test to check if top right search form is not present --- spec/features/search_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 3e6289a46b..029a11ea43 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -10,6 +10,10 @@ describe "Search", feature: true do visit search_path end + it 'top right search form is not present' do + expect(page).not_to have_selector('.search') + end + describe 'searching for Projects' do it 'finds a project' do page.within '.search-holder' do From 6745e58f8f809a6813ac42fcd2ac82729234df1e Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 13 Apr 2016 18:25:39 -0400 Subject: [PATCH 136/573] Fix issue with labels not showing initially. --- app/assets/javascripts/lib/animate.js.coffee | 2 ++ app/views/shared/issuable/_filter.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee index 64aef4c6d4..ec3b44d612 100644 --- a/app/assets/javascripts/lib/animate.js.coffee +++ b/app/assets/javascripts/lib/animate.js.coffee @@ -19,6 +19,8 @@ gl.animate.animateEach = ($els, animation, time, options, done) -> dfd = $.Deferred() + if not $els.length + dfd.resolve() $els.each((i) -> setTimeout(=> $this = $(@) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 02982516de..623ca5ab98 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -46,9 +46,9 @@ .filter-item.inline = button_tag "Update issues", class: "btn update_selected_issues btn-save" -- if @labels .gray-content-block.second-block.filtered-labels - = render "shared/label_row", labels: @labels + - if @labels + = render "shared/label_row", labels: @labels :javascript new UsersSelect(); From 897892132334f4004719d2489530898491f4fff6 Mon Sep 17 00:00:00 2001 From: Tom Downes Date: Thu, 14 Apr 2016 01:03:50 +0000 Subject: [PATCH 137/573] Update shibboleth configuration for GitLab 8.6 and Apache 2.4 --- doc/integration/shibboleth.md | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md index a0be3dd4e5..b6b2d4e5e8 100644 --- a/doc/integration/shibboleth.md +++ b/doc/integration/shibboleth.md @@ -76,3 +76,50 @@ sudo gitlab-ctl reconfigure ``` On the sign in page there should now be a "Sign in with: Shibboleth" icon below the regular sign in form. Click the icon to begin the authentication process. You will be redirected to IdP server (Depends on your Shibboleth module configuration). If everything goes well the user will be returned to GitLab and will be signed in. + +## Apache 2.4 / GitLab 8.6 update +The order of the first 2 Location directives is important. If they are reversed, +you will not get a shibboleth session! + +``` + + Require all granted + ProxyPassReverse http://127.0.0.1:8181 + ProxyPassReverse http://YOUR_SERVER_FQDN/ + + + + AuthType shibboleth + ShibRequestSetting requireSession 1 + ShibUseHeaders On + Require shib-session + + + Alias /shibboleth-sp /usr/share/shibboleth + + + Require all granted + + + + SetHandler shib + + + RewriteEngine on + + #Don't escape encoded characters in api requests + RewriteCond %{REQUEST_URI} ^/api/v3/.* + RewriteCond %{REQUEST_URI} !/Shibboleth.sso + RewriteCond %{REQUEST_URI} !/shibboleth-sp + RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA,NE] + + #Forward all requests to gitlab-workhorse except existing files + RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f [OR] + RewriteCond %{REQUEST_URI} ^/uploads/.* + RewriteCond %{REQUEST_URI} !/Shibboleth.sso + RewriteCond %{REQUEST_URI} !/shibboleth-sp + RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA] + + RequestHeader set X_FORWARDED_PROTO 'https' + RequestHeader set X-Forwarded-Ssl on +``` \ No newline at end of file From f5e8667fc5fd7f650704e9507edad48c7fbdef79 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 09:13:32 +0100 Subject: [PATCH 138/573] Fixed haml style issues --- app/views/projects/labels/_label.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index c7b4bb1f6e..8bf544b837 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -14,7 +14,7 @@ .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .subscription-status{data: {status: label_subscription_status(label)}} - %button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: 'button', data: {toggle: "tooltip"} } + %button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } } %span= label_subscription_toggle_button_text(label) - if can? current_user, :admin_label, @project From 2fd05aed463684203cf21923b54e28888fcad0ea Mon Sep 17 00:00:00 2001 From: Frank Groeneveld Date: Thu, 14 Apr 2016 10:24:09 +0200 Subject: [PATCH 139/573] Allow empty recipient list when pusher is added Closes #13574 --- CHANGELOG | 1 + .../project_services/builds_email_service.rb | 10 +++-- .../builds_email_service_spec.rb | 37 ++++++++++++++++++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9baf6516ef..ba98b3b100 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -60,6 +60,7 @@ v 8.7.0 (unreleased) - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - API: User can leave a project through the API when not master or owner. !3613 - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) + - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index f9f0483876..6ab6d7417b 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -23,7 +23,7 @@ class BuildsEmailService < Service prop_accessor :recipients boolean_accessor :add_pusher boolean_accessor :notify_only_broken_builds - validates :recipients, presence: true, if: :activated? + validates :recipients, presence: true, if: ->(s) { s.activated? && !s.add_pusher? } def initialize_properties if properties.nil? @@ -87,10 +87,14 @@ class BuildsEmailService < Service end def all_recipients(data) - all_recipients = recipients.split(',').compact.reject(&:blank?) + all_recipients = [] + + unless recipients.blank? + all_recipients += recipients.split(',').compact.reject(&:blank?) + end if add_pusher? && data[:user][:email] - all_recipients << "#{data[:user][:email]}" + all_recipients << data[:user][:email] end all_recipients diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb index 2ccbff553f..7c23c2efcc 100644 --- a/spec/models/project_services/builds_email_service_spec.rb +++ b/spec/models/project_services/builds_email_service_spec.rb @@ -3,9 +3,10 @@ require 'spec_helper' describe BuildsEmailService do let(:build) { create(:ci_build) } let(:data) { Gitlab::BuildDataBuilder.build(build) } - let(:service) { BuildsEmailService.new } + let!(:project) { create(:project, :public, ci_id: 1) } + let(:service) { described_class.new(project: project, active: true) } - describe :execute do + describe '#execute' do it 'sends email' do service.recipients = 'test@gitlab.com' data[:build_status] = 'failed' @@ -40,4 +41,36 @@ describe BuildsEmailService do service.execute(data) end end + + describe 'validations' do + + context 'when pusher is not added' do + before { service.add_pusher = false } + + it 'does not allow empty recipient input' do + service.recipients = '' + expect(service.valid?).to be false + end + + it 'does allow non-empty recipient input' do + service.recipients = 'test@example.com' + expect(service.valid?).to be true + end + + end + + context 'when pusher is added' do + before { service.add_pusher = true } + + it 'does allow empty recipient input' do + service.recipients = '' + expect(service.valid?).to be true + end + + it 'does allow non-empty recipient input' do + service.recipients = 'test@example.com' + expect(service.valid?).to be true + end + end + end end From 3d438870f34cb1bd5667bc5db2f848c4f82d5d25 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 09:32:02 +0100 Subject: [PATCH 140/573] Updated based on feedback --- .../javascripts/importer_status.js.coffee | 20 +++++++++---------- app/views/import/base/create.js.haml | 2 +- app/views/import/bitbucket/status.html.haml | 6 +++--- app/views/import/fogbugz/status.html.haml | 4 ++-- app/views/import/github/status.html.haml | 4 ++-- app/views/import/gitlab/status.html.haml | 4 ++-- app/views/import/gitorious/status.html.haml | 4 ++-- app/views/import/google_code/status.html.haml | 6 +++--- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index c51c4a1a18..95f9f86c0a 100644 --- a/app/assets/javascripts/importer_status.js.coffee +++ b/app/assets/javascripts/importer_status.js.coffee @@ -4,16 +4,16 @@ class @ImporterStatus this.setAutoUpdate() initStatusPage: -> - $(".js-add-to-import") + $('.js-add-to-import') .off 'click' .on 'click', (event) => new_namespace = null $btn = $(event.currentTarget) - $tr = $btn.closest("tr") - id = $tr.attr("id").replace("repo_", "") - if $tr.find(".import-target input").length > 0 - new_namespace = $tr.find(".import-target input").prop("value") - $tr.find(".import-target").empty().append(new_namespace + "/" + $tr.find(".import-target").data("project_name")) + $tr = $btn.closest('tr') + id = $tr.attr('id').replace('repo_', '') + if $tr.find('.import-target input').length > 0 + new_namespace = $tr.find('.import-target input').prop('value') + $tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}") $btn .disable() @@ -21,15 +21,15 @@ class @ImporterStatus $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' - $(".js-import-all") + $('.js-import-all') .off 'click' - .on 'click', (event) => - $btn = $(event.currentTarget) + .on 'click', (e) => + $btn = $(e.currentTarget) $btn .disable() .addClass 'is-loading' - $(".js-add-to-import").each -> + $('.js-add-to-import').each -> $(this).click() setAutoUpdate: -> diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index ec7a2b8656..dfebf7768d 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -26,4 +26,4 @@ - else :plain job = $("tr#repo_#{@repo_id}") - job.find(".import-actions").html(" Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}") + job.find(".import-actions").html(" Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index e24c01f3e0..6e993e58f0 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -12,11 +12,11 @@ - if @incompatible_repos.any? = button_tag class: "btn btn-import btn-success js-import-all" do Import all compatible projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") - else = button_tag class: "btn btn-success js-import-all" do Import all projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") .table-responsive %table.table.import-jobs @@ -55,7 +55,7 @@ %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") - @incompatible_repos.each do |repo| %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} %td diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index d842d1cbd9..d3d3c595c1 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -15,7 +15,7 @@ %p = button_tag class: 'btn btn-import btn-success js-import-all' do Import all projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") .table-responsive %table.table.import-jobs @@ -54,7 +54,7 @@ %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") :javascript new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}"); diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index d6fa0d8023..9639da4cb5 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -10,7 +10,7 @@ %p = button_tag class: "btn btn-import btn-success js-import-all" do Import all projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") .table-responsive %table.table.import-jobs @@ -49,7 +49,7 @@ %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") :javascript new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}"); diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 8f79f38561..e3a356b537 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -10,7 +10,7 @@ %p = button_tag class: "btn btn-import btn-success js-import-all" do Import all projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") .table-responsive %table.table.import-jobs @@ -49,7 +49,7 @@ %td.import-actions.job-status = button_tag class: "btn js-add-to-import" do Import - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") :javascript new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}"); diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index f0256e0f93..267eee4f26 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -10,7 +10,7 @@ %p = button_tag class: "btn btn-import btn-success js-import-all" do Import all projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") .table-responsive %table.table.import-jobs @@ -49,7 +49,7 @@ %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") :javascript new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}"); diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index c4ac1b4f9b..5ada6b174e 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -16,11 +16,11 @@ - if @incompatible_repos.any? = button_tag class: "btn btn-import btn-success js-import-all" do Import all compatible projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") - else = button_tag class: "btn btn-import btn-success js-import-all" do Import all projects - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") .table-responsive %table.table.import-jobs @@ -59,7 +59,7 @@ %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import - = icon('spinner spin', class: 'loading-icon') + = icon("spinner spin", class: "loading-icon") - @incompatible_repos.each do |repo| %tr{id: "repo_#{repo.id}"} %td From ec3f4f5bf06aa662b86079d815f9a68b556190f3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 14 Apr 2016 10:57:13 +0200 Subject: [PATCH 141/573] Projects on group page should be sorted by last activity instead of id/created_at Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index c1adc99956..ee4fcc4e36 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController @last_push = current_user.recent_push if current_user @projects = @projects.includes(:namespace) + @projects = @projects.sorted_by_activity @projects = filter_projects(@projects) @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]) if params[:filter_projects].blank? From 65410e63ac8c2e4e25acabb73ede6a2dfdc3d829 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 10:15:38 +0100 Subject: [PATCH 142/573] Variable name --- app/assets/javascripts/importer_status.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index 95f9f86c0a..2eb151a641 100644 --- a/app/assets/javascripts/importer_status.js.coffee +++ b/app/assets/javascripts/importer_status.js.coffee @@ -6,9 +6,9 @@ class @ImporterStatus initStatusPage: -> $('.js-add-to-import') .off 'click' - .on 'click', (event) => + .on 'click', (e) => new_namespace = null - $btn = $(event.currentTarget) + $btn = $(e.currentTarget) $tr = $btn.closest('tr') id = $tr.attr('id').replace('repo_', '') if $tr.find('.import-target input').length > 0 From 82164a9f777f7eee37707b19ae6ec514a588c1ec Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 10:17:00 +0100 Subject: [PATCH 143/573] Notes form JS update Updated the JS to have a standard class with standard actions for each form Created ability to have toolbar buttons that insert different prefixes dependant upon the data-prefix attribute --- app/assets/javascripts/gl_form.js.coffee | 40 +++++++++++++ .../javascripts/gl_form_actions.js.coffee | 43 ++++++++++++++ app/assets/javascripts/notes.js.coffee | 58 +++---------------- app/views/projects/notes/_form.html.haml | 2 +- app/views/projects/notes/_hints.html.haml | 3 + 5 files changed, 95 insertions(+), 51 deletions(-) create mode 100644 app/assets/javascripts/gl_form.js.coffee create mode 100644 app/assets/javascripts/gl_form_actions.js.coffee diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee new file mode 100644 index 0000000000..efa9284ed0 --- /dev/null +++ b/app/assets/javascripts/gl_form.js.coffee @@ -0,0 +1,40 @@ +class @GLForm + constructor: (@form) -> + @textarea = @form.find(".js-note-text") + + @setupForm() + + setupForm: -> + isNewForm = @form.is(':not(.gfm-form)') + + @form.removeClass "js-new-note-form" + + if isNewForm + @form.find('.div-dropzone').remove() + @form.addClass('gfm-form') + disableButtonIfEmptyField @form.find(".js-note-text"), @form.find(".js-comment-button") + + # remove notify commit author checkbox for non-commit notes + GitLab.GfmAutoComplete.setup() + new DropzoneInput(@form) + + autosize(@textarea) + + # Setup action buttons + actions = new GLFormActions @form, @textarea + @form.data 'form-actions', actions + + # form and textarea event listeners + @addEventListeners() + + # hide discard button + @form.find('.js-note-discard').hide() + + @form.show() + + addEventListeners: -> + @textarea.on 'focus', -> + $(@).closest('.md-area').addClass 'is-focused' + + @textarea.on 'blur', -> + $(@).closest('.md-area').removeClass 'is-focused' diff --git a/app/assets/javascripts/gl_form_actions.js.coffee b/app/assets/javascripts/gl_form_actions.js.coffee new file mode 100644 index 0000000000..d8de63a2be --- /dev/null +++ b/app/assets/javascripts/gl_form_actions.js.coffee @@ -0,0 +1,43 @@ +class @GLFormActions + constructor: (@form, @textarea) -> + @clearEventListeners() + @addEventListeners() + + clearEventListeners: -> + @form.off 'click', '.js-toolbar-button' + + addEventListeners: -> + @form.on 'click', '.js-toolbar-button', @toolbarButtonClick + + toolbarButtonClick: (e) => + $btn = $(e.currentTarget) + + # Get the prefix from the button + prefix = $btn.data('prefix') + @addPrefixToTextarea(prefix) + + addPrefixToTextarea: (prefix) -> + caretStart = @textarea.get(0).selectionStart + caretEnd = @textarea.get(0).selectionEnd + textEnd = @textarea.val().length + + beforeSelection = @textarea.val().substring 0, caretStart + afterSelection = @textarea.val().substring caretEnd, textEnd + + beforeSelectionSplit = beforeSelection.split '' + beforeSelectionLength = beforeSelection.length + + # Get the last character in the before selection + beforeSelectionLastChar = beforeSelectionSplit[beforeSelectionLength - 1] + + if beforeSelectionLastChar? and beforeSelectionLastChar isnt '' + # Append a white space char to the prefix if the previous char isn't a space + prefix = " #{prefix}" + + # Update the textarea + @textarea.val beforeSelection + prefix + afterSelection + @textarea.get(0).setSelectionRange caretStart + prefix.length, caretEnd + prefix.length + + # Focus the textarea + @textarea.focus() + @textarea.trigger('keyup') diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index a67890200d..0581774424 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -283,32 +283,10 @@ class @Notes show the form ### setupNoteForm: (form) -> - disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") - form.removeClass "js-new-note-form" - form.find('.div-dropzone').remove() - - # hide discard button - form.find('.js-note-discard').hide() - - # setup preview buttons - previewButton = form.find(".js-md-preview-button") + new GLForm form textarea = form.find(".js-note-text") - textarea.on "input", -> - if $(this).val().trim() isnt "" - previewButton.removeClass("turn-off").addClass "turn-on" - else - previewButton.removeClass("turn-on").addClass "turn-off" - - textarea.on 'focus', -> - $(this).closest('.md-area').addClass 'is-focused' - - textarea.on 'blur', -> - $(this).closest('.md-area').removeClass 'is-focused' - - autosize(textarea) - new Autosave textarea, [ "Note" form.find("#note_commit_id").val() @@ -317,11 +295,6 @@ class @Notes form.find("#note_noteable_id").val() ] - # remove notify commit author checkbox for non-commit notes - GitLab.GfmAutoComplete.setup() - new DropzoneInput(form) - form.show() - ### Called in response to the new note form being submitted @@ -375,34 +348,15 @@ class @Notes note = $(this).closest(".note") note.addClass "is-editting" form = note.find(".note-edit-form") - isNewForm = form.is(':not(.gfm-form)') - if isNewForm - form.addClass('gfm-form') + form.addClass('current-note-edit-form') # Show the attachment delete link note.find(".js-note-attachment-delete").show() - # Setup markdown form - if isNewForm - GitLab.GfmAutoComplete.setup() - new DropzoneInput(form) + new GLForm form - textarea = form.find("textarea") - textarea.focus() - - if isNewForm - autosize(textarea) - - # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?). - # The textarea has the correct value, Chrome just won't show it unless we - # modify it, so let's clear it and re-set it! - value = textarea.val() - textarea.val "" - textarea.val value - - if isNewForm - disableButtonIfEmptyField textarea, form.find(".js-comment-button") + form.find(".js-note-text").focus() ### Called in response to clicking the edit note link @@ -570,6 +524,10 @@ class @Notes # only remove the form form.remove() + # Remove the note actions + actions = form.data('form-actions') + actions.clearEventListeners() + form.data('form-actions', null) cancelDiscussionForm: (e) => e.preventDefault() diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index c446ecec2c..261eef4df4 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f| += form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form" }, authenticity_token: true do |f| = hidden_field_tag :view, diff_view = hidden_field_tag :line_type = note_target_fields(@note) diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml index 0b00204340..0c6758210b 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -1,4 +1,7 @@ .comment-toolbar.clearfix + %button.toolbar-button.js-toolbar-button{ type: 'button', data: { prefix: ':' }, tabindex: '-1' } + = icon('smile-o', class: 'toolbar-button-icon') + Emoji .toolbar-text Styling with = link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1 From 5dd01f572c9a7504f34c5a3b30792197c856bd6f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 10:28:10 +0100 Subject: [PATCH 144/573] Destroy discussion form --- app/assets/javascripts/gl_form.js.coffee | 22 +++++++++++++++++++--- app/assets/javascripts/notes.js.coffee | 8 +++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee index efa9284ed0..087a46cbac 100644 --- a/app/assets/javascripts/gl_form.js.coffee +++ b/app/assets/javascripts/gl_form.js.coffee @@ -1,18 +1,30 @@ class @GLForm constructor: (@form) -> - @textarea = @form.find(".js-note-text") + @textarea = @form.find('.js-note-text') @setupForm() + @form.data 'gl-form', @ + + destroy: -> + # Destroy actions + actions = @form.data 'form-actions' + actions.clearEventListeners() + @form.data 'form-actions', null + + # Clean form listeners + @clearEventListeners() + @form.data 'gl-form', null + setupForm: -> isNewForm = @form.is(':not(.gfm-form)') - @form.removeClass "js-new-note-form" + @form.removeClass 'js-new-note-form' if isNewForm @form.find('.div-dropzone').remove() @form.addClass('gfm-form') - disableButtonIfEmptyField @form.find(".js-note-text"), @form.find(".js-comment-button") + disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button') # remove notify commit author checkbox for non-commit notes GitLab.GfmAutoComplete.setup() @@ -32,6 +44,10 @@ class @GLForm @form.show() + clearEventListeners: -> + @textarea.off 'focus' + @textarea.off 'blur' + addEventListeners: -> @textarea.on 'focus', -> $(@).closest('.md-area').addClass 'is-focused' diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 0581774424..fa91baa07c 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -513,6 +513,9 @@ class @Notes removeDiscussionNoteForm: (form)-> row = form.closest("tr") + glForm = form.data 'gl-form' + glForm.destroy() + form.find(".js-note-text").data("autosave").reset() # show the reply button (will only work for replies) @@ -524,11 +527,6 @@ class @Notes # only remove the form form.remove() - # Remove the note actions - actions = form.data('form-actions') - actions.clearEventListeners() - form.data('form-actions', null) - cancelDiscussionForm: (e) => e.preventDefault() form = $(e.target).closest(".js-discussion-note-form") From ff7fb09ea80036045e6fd17624b894f389de38b9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 10:34:02 +0100 Subject: [PATCH 145/573] Updated issue form to use new GLForm --- app/assets/javascripts/dispatcher.js.coffee | 2 +- app/assets/javascripts/gl_form.js.coffee | 12 +++++++++--- app/views/projects/issues/_form.html.haml | 2 +- app/views/shared/issuable/_form.html.haml | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 70fd6f50e9..6efbe214aa 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -35,7 +35,7 @@ class Dispatcher new Diff() when 'projects:issues:new','projects:issues:edit' shortcut_handler = new ShortcutsNavigation() - new DropzoneInput($('.issue-form')) + new GLForm($('.issue-form')) new IssuableForm($('.issue-form')) when 'projects:merge_requests:new', 'projects:merge_requests:edit' new Diff() diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee index 087a46cbac..aff3e909ce 100644 --- a/app/assets/javascripts/gl_form.js.coffee +++ b/app/assets/javascripts/gl_form.js.coffee @@ -1,7 +1,11 @@ class @GLForm constructor: (@form) -> - @textarea = @form.find('.js-note-text') + @textarea = @form.find('textarea.js-gfm-input') + # Before we start, we should clean up any previous data for this form + @destroy() + + # Setup the form @setupForm() @form.data 'gl-form', @ @@ -9,8 +13,10 @@ class @GLForm destroy: -> # Destroy actions actions = @form.data 'form-actions' - actions.clearEventListeners() - @form.data 'form-actions', null + + if actions? + actions.clearEventListeners() + @form.data 'form-actions', null # Clean form listeners @clearEventListeners() diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 33c48199ba..7076f5db01 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-quick-submit js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form common-note-form js-quick-submit js-requires-input' } do |f| = render 'shared/issuable/form', f: f, issuable: @issue :javascript diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 6de07a92cd..d77c74e5d7 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -29,7 +29,7 @@ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, - classes: 'description form-control' + classes: 'note-textarea' = render 'projects/notes/hints' .clearfix .error-alert From c45ca936c77eceff367e27e1bf8fd5f11c6a7c39 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 10:39:16 +0100 Subject: [PATCH 146/573] merge request form uses new GLForm --- app/assets/javascripts/dispatcher.js.coffee | 2 +- app/views/projects/merge_requests/_form.html.haml | 2 +- app/views/projects/merge_requests/_new_submit.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 6efbe214aa..d02e113070 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -40,7 +40,7 @@ class Dispatcher when 'projects:merge_requests:new', 'projects:merge_requests:edit' new Diff() shortcut_handler = new ShortcutsNavigation() - new DropzoneInput($('.merge-request-form')) + new GLForm($('.merge-request-form')) new IssuableForm($('.merge-request-form')) when 'projects:tags:new' new ZenMode() diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 1e6724fc92..88525f4036 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input js-quick-submit' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request :javascript diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 9e59f7df71..2f14a91e64 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -10,7 +10,7 @@ %span.pull-right = link_to 'Change branches', mr_change_branches_path(@merge_request) %hr -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request = f.hidden_field :source_project_id = f.hidden_field :source_branch From d67c3e35c98cff2372db803f93a97a60aa8b4104 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 10:42:38 +0100 Subject: [PATCH 147/573] Milestones use new GLForm class --- app/assets/javascripts/dispatcher.js.coffee | 2 +- app/views/groups/milestones/new.html.haml | 4 ++-- app/views/projects/milestones/_form.html.haml | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index d02e113070..a56019cd88 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -28,7 +28,7 @@ class Dispatcher new Todos() when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() - new DropzoneInput($('.milestone-form')) + new GLForm($('.milestone-form')) when 'groups:milestones:new' new ZenMode() when 'projects:compare:show' diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 4290e0bf72..caa8c4bc0e 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -8,7 +8,7 @@ This will create milestone in every selected project %hr -= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f| += form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f| .row - if @milestone.errors.any? #error_explanation @@ -27,7 +27,7 @@ = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea' .clearfix .error-alert .form-group diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index b2dae1c70e..06b2175479 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,6 +1,5 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f| += form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input'} do |f| = form_errors(@milestone) - .row .col-md-6 .form-group @@ -11,7 +10,7 @@ = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea' = render 'projects/notes/hints' .clearfix .error-alert From 3de09d1475db3d23f839991e57d16e00bff07cb4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 12:33:30 +0100 Subject: [PATCH 148/573] Updated all GFM forms to use new GLForm class --- app/assets/javascripts/dispatcher.js.coffee | 6 +++--- app/assets/stylesheets/framework/gfm.scss | 18 ------------------ app/views/projects/_zen.html.haml | 4 ++-- app/views/projects/notes/_edit_form.html.haml | 2 +- app/views/projects/notes/_form.html.haml | 2 +- app/views/projects/releases/edit.html.haml | 12 ++++++------ app/views/projects/tags/new.html.haml | 6 +++--- app/views/projects/wikis/_form.html.haml | 4 ++-- app/views/shared/issuable/_form.html.haml | 3 ++- 9 files changed, 20 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index a56019cd88..0b9110d35f 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -44,10 +44,10 @@ class Dispatcher new IssuableForm($('.merge-request-form')) when 'projects:tags:new' new ZenMode() - new DropzoneInput($('.tag-form')) + new GLForm($('.tag-form')) when 'projects:releases:edit' new ZenMode() - new DropzoneInput($('.release-form')) + new GLForm($('.release-form')) when 'projects:merge_requests:show' new Diff() shortcut_handler = new ShortcutsIssuable(true) @@ -137,7 +137,7 @@ class Dispatcher new Wikis() shortcut_handler = new ShortcutsNavigation() new ZenMode() - new DropzoneInput($('.wiki-form')) + new GLForm($('.wiki-form')) when 'snippets' shortcut_handler = new ShortcutsNavigation() new ZenMode() if path[2] == 'show' diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss index 5ae0520fd7..f4d35c4b4b 100644 --- a/app/assets/stylesheets/framework/gfm.scss +++ b/app/assets/stylesheets/framework/gfm.scss @@ -1,24 +1,6 @@ /** * Styles that apply to all GFM related forms. */ -.issue-form, .merge-request-form, .wiki-form { - .description { - height: 16em; - border-top-left-radius: 0; - } -} - -.wiki-form { - .description { - height: 26em; - } -} - -.milestone-form { - .description { - height: 14em; - } -} .gfm-commit, .gfm-commit_range { font-family: $monospace_font; diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index bddff5cdcb..e1e3501396 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,8 +1,8 @@ .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' - if defined?(f) && f - = f.text_area attr, class: classes, placeholder: "Write a comment or drag your files here..." + = f.text_area attr, class: classes, placeholder: placeholder - else - = text_area_tag attr, nil, class: classes, placeholder: "Write a comment or drag your files here..." + = text_area_tag attr, nil, class: classes, placeholder: placeholder %a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" } = icon('compress') diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index 23e4f93eab..c87a3fadf7 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -2,7 +2,7 @@ = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f| = note_target_fields(note) = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do - = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field' + = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..." = render 'projects/notes/hints' .note-form-actions.clearfix diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 261eef4df4..d0ac380f21 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -8,7 +8,7 @@ = f.hidden_field :noteable_type = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text' + = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..." = render 'projects/notes/hints' .error-alert diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index c4a3f06ee0..6f0b32aa16 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -9,11 +9,11 @@ %strong #{@tag.name} .prepend-top-default - = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form js-quick-submit' }) do |f| + = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'projects/notes/hints' - .error-alert - .form-actions.prepend-top-default - = f.submit 'Save changes', class: 'btn btn-save' - = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" + .error-alert + .form-actions.prepend-top-default + = f.submit 'Save changes', class: 'btn btn-save' + = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 77c7c4d23d..b40a6e5cb2 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -10,7 +10,7 @@ New Tag %hr -= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-quick-submit js-requires-input" do += form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal common-note-form tag-form js-quick-submit js-requires-input" do .form-group = label_tag :tag_name, nil, class: 'control-label' .col-sm-10 @@ -30,9 +30,9 @@ = label_tag :release_description, 'Release notes', class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', attr: :release_description, classes: 'description form-control' + = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'projects/notes/hints' - .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. + .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. .form-actions = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 812876e283..797a1a59e9 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f| = form_errors(@page) = f.hidden_field :title, value: @page.title @@ -11,7 +11,7 @@ = f.label :content, class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...' = render 'projects/notes/hints' .clearfix diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index d77c74e5d7..aed2622a6d 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -29,7 +29,8 @@ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, - classes: 'note-textarea' + classes: 'note-textarea', + placeholder: "Write a comment or drag your files here..." = render 'projects/notes/hints' .clearfix .error-alert From 6e93bd56f0147929006daba6410aa5640403b2c7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Apr 2016 13:23:43 +0100 Subject: [PATCH 149/573] Placeholder on milestone form Updated JS spec to include gl_form --- app/views/projects/milestones/_form.html.haml | 2 +- spec/javascripts/notes_spec.js.coffee | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 06b2175479..687222fa92 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -10,7 +10,7 @@ = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' = render 'projects/notes/hints' .clearfix .error-alert diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee index 050b6e362c..dd160e821b 100644 --- a/spec/javascripts/notes_spec.js.coffee +++ b/spec/javascripts/notes_spec.js.coffee @@ -1,4 +1,5 @@ #= require notes +#= require gl_form window.gon = {} window.disableButtonIfEmptyField = -> null From 7a1800fec87133e9971a83b457c969b5adaf5919 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Apr 2016 09:49:29 +0100 Subject: [PATCH 150/573] Fixed group milestones placeholder bug --- app/views/groups/milestones/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index caa8c4bc0e..7d9d27ae1f 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -27,7 +27,7 @@ = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' .clearfix .error-alert .form-group From 094cafcaa7c135a839a7329ed06fdd5d19a05f4b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 13 Apr 2016 08:46:30 +0100 Subject: [PATCH 151/573] Removed GL Actions class --- app/assets/javascripts/gl_form.js.coffee | 11 ----- .../javascripts/gl_form_actions.js.coffee | 43 ------------------- app/views/projects/notes/_hints.html.haml | 3 -- 3 files changed, 57 deletions(-) delete mode 100644 app/assets/javascripts/gl_form_actions.js.coffee diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee index aff3e909ce..d540cc4dc4 100644 --- a/app/assets/javascripts/gl_form.js.coffee +++ b/app/assets/javascripts/gl_form.js.coffee @@ -11,13 +11,6 @@ class @GLForm @form.data 'gl-form', @ destroy: -> - # Destroy actions - actions = @form.data 'form-actions' - - if actions? - actions.clearEventListeners() - @form.data 'form-actions', null - # Clean form listeners @clearEventListeners() @form.data 'gl-form', null @@ -38,10 +31,6 @@ class @GLForm autosize(@textarea) - # Setup action buttons - actions = new GLFormActions @form, @textarea - @form.data 'form-actions', actions - # form and textarea event listeners @addEventListeners() diff --git a/app/assets/javascripts/gl_form_actions.js.coffee b/app/assets/javascripts/gl_form_actions.js.coffee deleted file mode 100644 index d8de63a2be..0000000000 --- a/app/assets/javascripts/gl_form_actions.js.coffee +++ /dev/null @@ -1,43 +0,0 @@ -class @GLFormActions - constructor: (@form, @textarea) -> - @clearEventListeners() - @addEventListeners() - - clearEventListeners: -> - @form.off 'click', '.js-toolbar-button' - - addEventListeners: -> - @form.on 'click', '.js-toolbar-button', @toolbarButtonClick - - toolbarButtonClick: (e) => - $btn = $(e.currentTarget) - - # Get the prefix from the button - prefix = $btn.data('prefix') - @addPrefixToTextarea(prefix) - - addPrefixToTextarea: (prefix) -> - caretStart = @textarea.get(0).selectionStart - caretEnd = @textarea.get(0).selectionEnd - textEnd = @textarea.val().length - - beforeSelection = @textarea.val().substring 0, caretStart - afterSelection = @textarea.val().substring caretEnd, textEnd - - beforeSelectionSplit = beforeSelection.split '' - beforeSelectionLength = beforeSelection.length - - # Get the last character in the before selection - beforeSelectionLastChar = beforeSelectionSplit[beforeSelectionLength - 1] - - if beforeSelectionLastChar? and beforeSelectionLastChar isnt '' - # Append a white space char to the prefix if the previous char isn't a space - prefix = " #{prefix}" - - # Update the textarea - @textarea.val beforeSelection + prefix + afterSelection - @textarea.get(0).setSelectionRange caretStart + prefix.length, caretEnd + prefix.length - - # Focus the textarea - @textarea.focus() - @textarea.trigger('keyup') diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml index 0c6758210b..0b00204340 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -1,7 +1,4 @@ .comment-toolbar.clearfix - %button.toolbar-button.js-toolbar-button{ type: 'button', data: { prefix: ':' }, tabindex: '-1' } - = icon('smile-o', class: 'toolbar-button-icon') - Emoji .toolbar-text Styling with = link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1 From c5d7b467ab3c67a71419e314c128d0ab5f371cb5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 13 Apr 2016 09:08:34 +0100 Subject: [PATCH 152/573] Updated dropzone hover styling --- app/assets/javascripts/dropzone_input.js.coffee | 9 +++++---- app/assets/stylesheets/framework/markdown_area.scss | 13 +++---------- app/assets/stylesheets/framework/typography.scss | 8 -------- app/assets/stylesheets/pages/note_form.scss | 12 ++++++++++++ 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index b502131a99..6eb8d27ee2 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -15,11 +15,13 @@ class @DropzoneInput project_uploads_path = window.project_uploads_path or null max_file_size = gon.max_file_size or 10 - form_textarea = $(form).find("textarea.markdown-area") + form_textarea = $(form).find(".js-gfm-input") form_textarea.wrap "
" form_textarea.on 'paste', (event) => handlePaste(event) + $mdArea = $(form_textarea).closest('.md-area') + $(form).setupMarkdownPreview() form_dropzone = $(form).find('.div-dropzone') @@ -49,17 +51,16 @@ class @DropzoneInput $(".div-dropzone-alert").alert "close" dragover: -> - form_textarea.addClass "div-dropzone-focus" + $mdArea.addClass 'is-dropzone-hover' form.find(".div-dropzone-hover").css "opacity", 0.7 return dragleave: -> - form_textarea.removeClass "div-dropzone-focus" + $mdArea.removeClass 'is-dropzone-hover' form.find(".div-dropzone-hover").css "opacity", 0 return drop: -> - form_textarea.removeClass "div-dropzone-focus" form.find(".div-dropzone-hover").css "opacity", 0 form_textarea.focus() return diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index c8f86d60e3..0f32d36d59 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -1,22 +1,15 @@ .div-dropzone-wrapper { .div-dropzone { position: relative; - margin-bottom: -5px; - - .div-dropzone-focus { - border-color: #66afe9 !important; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important; - outline: 0 !important; - } .div-dropzone-hover { position: absolute; top: 50%; left: 50%; - margin-top: -0.5em; - margin-left: -0.6em; + margin-top: -11.5px; + margin-left: -15px; opacity: 0; - font-size: 50px; + font-size: 30px; transition: opacity 200ms ease-in-out; pointer-events: none; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 7b2aada5a0..0a5b4b8834 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -250,14 +250,6 @@ a > code { * Textareas intended for GFM * */ -.js-gfm-input { - font-family: $monospace_font; - color: $gl-text-color; -} - -.md-preview { -} - .strikethrough { text-decoration: line-through; } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index f4da17fada..0d92ebcc8e 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -40,6 +40,7 @@ } .note-textarea { + display: block; padding: 10px 0; font-family: $regular_font; border: 0; @@ -72,6 +73,17 @@ } } + &.is-dropzone-hover { + border-color: $gl-success; + box-shadow: 0 0 2px rgba(#000, .2), + 0 0 4px rgba($gl-success, .4); + + .comment-toolbar, + .nav-links { + border-color: $gl-success; + } + } + p { code { white-space: normal; From 8ffb02d9f812c001514ff4c67e2eac53e1d9dd57 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 13 Apr 2016 09:09:10 +0100 Subject: [PATCH 153/573] Added CHANGELOG item --- CHANGELOG | 1 + app/assets/stylesheets/pages/note_form.scss | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba98b3b100..e5c8620beb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -61,6 +61,7 @@ v 8.7.0 (unreleased) - API: User can leave a project through the API when not master or owner. !3613 - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) + - Improved markdown forms v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 0d92ebcc8e..2395de2e17 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -64,7 +64,7 @@ &.is-focused { border-color: $focus-border-color; - box-shadow: 0 0 2px rgba(#000, .2), + box-shadow: 0 0 2px $black-transparent, 0 0 4px rgba($focus-border-color, .4); .comment-toolbar, @@ -75,7 +75,7 @@ &.is-dropzone-hover { border-color: $gl-success; - box-shadow: 0 0 2px rgba(#000, .2), + box-shadow: 0 0 2px $black-transparent, 0 0 4px rgba($gl-success, .4); .comment-toolbar, From ca01103dedd1a44937068c6834865881ade3d301 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 13 Apr 2016 18:14:01 +0100 Subject: [PATCH 154/573] Focus variable for dropzone --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/note_form.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 0b6be86ce6..fcf1f2193b 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -150,6 +150,7 @@ $light-grey-header: #faf9f9; */ $gl-primary: $blue-normal; $gl-success: $green-normal; +$gl-success-focus: rgba($gl-success, .4) $gl-info: $blue-normal; $gl-warning: $orange-normal; $gl-danger: $red-normal; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 2395de2e17..07c707e7b7 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -76,7 +76,7 @@ &.is-dropzone-hover { border-color: $gl-success; box-shadow: 0 0 2px $black-transparent, - 0 0 4px rgba($gl-success, .4); + 0 0 4px $gl-success-focus; .comment-toolbar, .nav-links { From 0d8462b897847ae87b131d5111b8772848ba45ab Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 09:11:38 +0100 Subject: [PATCH 155/573] Fixed scss syntax issue --- app/assets/stylesheets/framework/variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index fcf1f2193b..f910cf6181 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -150,7 +150,7 @@ $light-grey-header: #faf9f9; */ $gl-primary: $blue-normal; $gl-success: $green-normal; -$gl-success-focus: rgba($gl-success, .4) +$gl-success-focus: rgba($gl-success, .4); $gl-info: $blue-normal; $gl-warning: $orange-normal; $gl-danger: $red-normal; From efff7e9e54e1d1ebbb36dc8dca35b894aeab2630 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 14 Apr 2016 12:03:45 +0200 Subject: [PATCH 156/573] updated migration based on testing findings --- ...8_remove_wrong_import_url_from_projects.rb | 19 +++++++++++++------ lib/gitlab/import_url.rb | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb index b97b79f920..8a351cf27a 100644 --- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb +++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb @@ -23,10 +23,14 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration def process_projects_with_wrong_url projects_with_wrong_import_url.each do |project| - import_url = Gitlab::ImportUrl.new(project["import_url"]) + begin + import_url = Gitlab::ImportUrl.new(project["import_url"]) - update_import_url(import_url, project) - update_import_data(import_url, project) + update_import_url(import_url, project) + update_import_data(import_url, project) + rescue URI::InvalidURIError + nullify_import_url(project) + end end end @@ -82,6 +86,10 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}") end + def nullify_import_url(project) + execute("UPDATE projects SET import_url = NULL WHERE id = #{project['id']}") + end + def insert_import_data_sql(project_id, fake_import_data) %( INSERT INTO project_import_data @@ -108,14 +116,13 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration end #GitHub projects with token, and any user:password@ based URL - #TODO: may need to add import_type != list def projects_with_wrong_import_url - select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_url LIKE '%//%@%'") + select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url <> '' AND p.import_url LIKE '%//%@%'") end # All imports with data for import_type def unencrypted_import_data(import_type: ) - select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url IS NOT NULL AND p.import_type = '#{import_type}' ") + select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url <> '' AND p.import_type = '#{import_type}' ") end def quote(value) diff --git a/lib/gitlab/import_url.rb b/lib/gitlab/import_url.rb index 3cfbc17b89..d23b013c1f 100644 --- a/lib/gitlab/import_url.rb +++ b/lib/gitlab/import_url.rb @@ -1,7 +1,7 @@ module Gitlab class ImportUrl def initialize(url, credentials: nil) - @url = URI.parse(url) + @url = URI.parse(URI.encode(url)) @credentials = credentials end From 5f53ca69ace953bf06afe478072836d083f941ca Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 14 Apr 2016 11:16:24 +0100 Subject: [PATCH 157/573] fix failing tests --- app/views/shared/_label_row.html.haml | 8 +++++--- app/views/shared/_labels_row.html.haml | 3 +++ app/views/shared/issuable/_filter.html.haml | 2 +- features/project/issues/filter_labels.feature | 1 + features/steps/project/issues/filter_labels.rb | 4 ++++ 5 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 app/views/shared/_labels_row.html.haml diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index f81a04a3c8..9ce5562e66 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -1,3 +1,5 @@ -- labels.each do |l| - %span.label-row - = link_to_label(l, tooltip: false) \ No newline at end of file +%span.label-row + %span.label-name + = link_to_label(label, tooltip: false) + %span.prepend-left-10 + = markdown(label.description, pipeline: :single_line) \ No newline at end of file diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml new file mode 100644 index 0000000000..f81a04a3c8 --- /dev/null +++ b/app/views/shared/_labels_row.html.haml @@ -0,0 +1,3 @@ +- labels.each do |l| + %span.label-row + = link_to_label(l, tooltip: false) \ No newline at end of file diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 623ca5ab98..c14391ada0 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -48,7 +48,7 @@ .gray-content-block.second-block.filtered-labels - if @labels - = render "shared/label_row", labels: @labels + = render "shared/labels_row", labels: @labels :javascript new UsersSelect(); diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature index e07f8053fb..49d7a3b9af 100644 --- a/features/project/issues/filter_labels.feature +++ b/features/project/issues/filter_labels.feature @@ -12,6 +12,7 @@ Feature: Project Issues Filter Labels @javascript Scenario: I filter by one label Given I click link "bug" + And I click "dropdown close button" Then I should see "Bugfix1" in issues list And I should see "Bugfix2" in issues list And I should not see "Feature1" in issues list diff --git a/features/steps/project/issues/filter_labels.rb b/features/steps/project/issues/filter_labels.rb index 6d50501a72..d82c685691 100644 --- a/features/steps/project/issues/filter_labels.rb +++ b/features/steps/project/issues/filter_labels.rb @@ -32,6 +32,10 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps page.find('.js-label-select').click sleep 0.5 execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") + end + + step 'I click "dropdown close button"' do + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click sleep 2 end From 8a8d4c9bf27d17939bfce4b53287210d8108da2c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Apr 2016 13:19:55 +0100 Subject: [PATCH 158/573] Diff viewer links to correct part of the file Added highlight colours to diff rows Closes #13852 --- .../javascripts/merge_request_tabs.js.coffee | 28 ++++++++++++++++--- app/assets/stylesheets/highlight/dark.scss | 6 ++++ app/assets/stylesheets/highlight/monokai.scss | 6 ++++ .../stylesheets/highlight/solarized_dark.scss | 6 ++++ .../highlight/solarized_light.scss | 6 ++++ app/assets/stylesheets/highlight/white.scss | 6 ++++ .../projects/diffs/_parallel_view.html.haml | 8 +++--- 7 files changed, 58 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index ef0b534a70..d45c772b8d 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -85,8 +85,11 @@ class @MergeRequestTabs scrollToElement: (container) -> if window.location.hash - $el = $("div#{container} #{window.location.hash}") - $('body').scrollTo($el.offset().top) if $el.length + navBarHeight = $('.navbar-gitlab').outerHeight() + + $el = $("#{container} #{window.location.hash}") + $('body').scrollTo 0 + $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length # Activate a tab based on the current action activateTab: (action) -> @@ -152,12 +155,29 @@ class @MergeRequestTabs @_get url: "#{source}.json" + @_location.search success: (data) => - document.querySelector("div#diffs").innerHTML = data.html + $('#diffs').html data.html gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')) - $('div#diffs .js-syntax-highlight').syntaxHighlight() + $('#diffs .js-syntax-highlight').syntaxHighlight() @expandViewContainer() if @diffViewType() is 'parallel' @diffsLoaded = true @scrollToElement("#diffs") + @highlighSelectedLine() + + highlighSelectedLine: -> + locationHash = location.hash + + if locationHash isnt '' + hashClassString = ".#{locationHash.replace('#', '')}" + $diffLine = $(locationHash) + + if $diffLine.is ':not(tr)' + $diffLine = $("td#{locationHash}, td#{hashClassString}") + else + $diffLine = $('td', $diffLine) + + $diffLine.addClass 'hll' + diffLineTop = $diffLine.offset().top + navBarHeight = $('.navbar-gitlab').outerHeight() loadBuilds: (source) -> return if @buildsLoaded diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 4767394489..77a73dc379 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #557; + border-color: darken(#557, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080); } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 806401c21a..28253d4ccb 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #49483e; + border-color: darken(#49483e, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080); } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 6a809d4dfd..c62bd021ae 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #174652; + border-color: darken(#174652, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46); } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index c482a1258f..524cfaf90c 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #ddd8c5; + border-color: darken(#ddd8c5, 15%); + } + .diff-line-num.new, .line_content.new { @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4); } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 28331f5975..1ff6ad75e0 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -21,6 +21,12 @@ // Diff line .line_holder { + td.diff-line-num.hll:not(.empty-cell), + td.line_content.hll:not(.empty-cell) { + background-color: #f8eec7; + border-color: darken(#f8eec7, 15%); + } + .diff-line-num { &.old { background-color: $line-number-old; diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index d7c4906874..81948513e4 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -14,11 +14,11 @@ %td.new_line.diff-line-num %td.line_content.parallel.match= left[:text] - else - %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"} + %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"} = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code] - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(left[:line_code], 'old') - %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text]) + %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text]) - if right[:type] == 'new' - new_line_class = 'new' @@ -27,11 +27,11 @@ - new_line_class = nil - new_line_code = left[:line_code] - %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }} + %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }} = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(right[:line_code], 'new') - %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text]) + %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text]) - if @reply_allowed - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code]) From f3ed4763f54ee46a50d2e7b3b894f895b4067b61 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Apr 2016 09:07:20 +0100 Subject: [PATCH 159/573] Correctly scrolls to the line when clicking --- app/assets/javascripts/merge_request_tabs.js.coffee | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index d45c772b8d..316d1fefa5 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -88,7 +88,6 @@ class @MergeRequestTabs navBarHeight = $('.navbar-gitlab').outerHeight() $el = $("#{container} #{window.location.hash}") - $('body').scrollTo 0 $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length # Activate a tab based on the current action @@ -163,8 +162,17 @@ class @MergeRequestTabs @scrollToElement("#diffs") @highlighSelectedLine() + $(document) + .off 'click', '.diff-content a' + .on 'click', '.diff-content a', (e) => + e.preventDefault() + window.location.hash = $(e.currentTarget).attr 'href' + @highlighSelectedLine() + @scrollToElement("#diffs") + highlighSelectedLine: -> - locationHash = location.hash + $('.hll').removeClass 'hll' + locationHash = window.location.hash if locationHash isnt '' hashClassString = ".#{locationHash.replace('#', '')}" From fd9c3b7c7446d9b8513793606aa2ae0d980ad4b7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Apr 2016 10:06:35 +0100 Subject: [PATCH 160/573] Fixed issue with other links being clicked in diffs --- app/assets/javascripts/merge_request_tabs.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 316d1fefa5..1ab6e5114b 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -163,8 +163,8 @@ class @MergeRequestTabs @highlighSelectedLine() $(document) - .off 'click', '.diff-content a' - .on 'click', '.diff-content a', (e) => + .off 'click', '.diff-line-num a' + .on 'click', '.diff-line-num a', (e) => e.preventDefault() window.location.hash = $(e.currentTarget).attr 'href' @highlighSelectedLine() From badb35335086b90e2522ea2da767830f2f3ba9a7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 13 Apr 2016 09:23:28 +0100 Subject: [PATCH 161/573] Added CHANGELOG item --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e5c8620beb..511889b376 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -62,6 +62,8 @@ v 8.7.0 (unreleased) - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - Improved markdown forms + - Diffs load at the correct point when linking from from number + - Selected diff rows highlight v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) From 0c61f052def4c363eea0b3621e540e9af78c7555 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 14 Apr 2016 13:23:46 +0100 Subject: [PATCH 162/573] add some tests for the new feature --- spec/features/issues/filter_by_labels.spec.rb | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 spec/features/issues/filter_by_labels.spec.rb diff --git a/spec/features/issues/filter_by_labels.spec.rb b/spec/features/issues/filter_by_labels.spec.rb new file mode 100644 index 0000000000..7944403f87 --- /dev/null +++ b/spec/features/issues/filter_by_labels.spec.rb @@ -0,0 +1,172 @@ +require 'rails_helper' + +feature 'Issue filtering by Labels', feature: true do + let(:project) { create(:project, :public) } + let!(:user) { create(:user)} + let!(:label) { create(:label, project: project) } + + before do + ['bug', 'feature', 'enhancement'].each do |title| + create(:label, + project: project, + title: title) + end + + issue1 = create(:issue, title: "Bugfix1", project: project) + issue1.labels << project.labels.find_by(title: 'bug') + + issue2 = create(:issue, title: "Bugfix2", project: project) + issue2.labels << project.labels.find_by(title: 'bug') + issue2.labels << project.labels.find_by(title: 'enhancement') + + issue3 = create(:issue, title: "Feature1", project: project) + issue3.labels << project.labels.find_by(title: 'feature') + + project.team << [user, :master] + login_as(user) + + visit namespace_project_issues_path(project.namespace, project) + end + + context 'filter by label bug', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix1" + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1" in issues list' do + expect(page).not_to have_content "Feature1" + end + + it 'should show label "bug" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "bug" + end + + it 'should not show label "feature" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "feature" + expect(find('.filtered-labels')).not_to have_content "enhancement" + end + end + + context 'filter by label feature', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Feature1" in issues list' do + expect(page).to have_content "Feature1" + end + + it 'should not show "Bugfix1" and "Bugfix2" in issues list' do + expect(page).not_to have_content "Bugfix2" + expect(page).not_to have_content "Bugfix1" + end + + it 'should show label "feature" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "feature" + end + + it 'should not show label "bug" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + expect(find('.filtered-labels')).not_to have_content "enhancement" + end + end + + context 'filter by label enhancement', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1" and "Bugfix1" in issues list' do + expect(page).not_to have_content "Feature1" + expect(page).not_to have_content "Bugfix1" + end + + it 'should show label "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "enhancement" + end + + it 'should not show label "feature" and "bug" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + expect(find('.filtered-labels')).not_to have_content "feature" + end + end + + context 'filter by label enhancement or feature', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix2" or "Feature1" in issues list' do + expect(page).to have_content "Bugfix2" + expect(page).to have_content "Feature1" + end + + it 'should not show "Bugfix1" in issues list' do + expect(page).not_to have_content "Bugfix1" + end + + it 'should show label "enhancement" and "feature" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "enhancement" + expect(find('.filtered-labels')).to have_content "feature" + end + + it 'should not show label "bug" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + end + end + + context 'filter by label enhancement or bug in issues list', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix2" or "Bugfix1" in issues list' do + expect(page).to have_content "Bugfix2" + expect(page).to have_content "Bugfix1" + end + + it 'should not show "Feature1"' do + expect(page).not_to have_content "Feature1" + end + + it 'should show label "bug" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "bug" + expect(find('.filtered-labels')).to have_content "enhancement" + end + + it 'should not show label "feature" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "feature" + end + end +end From 072b9c2c8fe466d43834c16bfbb565043a033fdf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 14 Apr 2016 15:40:05 +0300 Subject: [PATCH 163/573] Add TOC to yaml README and an intro section [ci skip] --- doc/ci/yaml/README.md | 73 +++++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 7da9b31e30..abb6e97e5e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1,5 +1,47 @@ # Configuration of your builds with .gitlab-ci.yml +This document describes the usage of `.gitlab-ci.yml`, the file that is used by +GitLab Runner to manage your project's builds. + +If you want a quick introduction to GitLab CI, follow our +[quick start guide](../quick_start/README.md). + +--- + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [.gitlab-ci.yml](#gitlab-ci-yml) + - [image and services](#image-and-services) + - [before_script](#before_script) + - [stages](#stages) + - [types](#types) + - [variables](#variables) + - [cache](#cache) + - [cache:key](#cache-key) +- [Jobs](#jobs) + - [script](#script) + - [stage](#stage) + - [only and except](#only-and-except) + - [tags](#tags) + - [when](#when) + - [artifacts](#artifacts) + - [artifacts:name](#artifacts-name) + - [dependencies](#dependencies) +- [Hidden jobs](#hidden-jobs) +- [Special YAML features](#special-yaml-features) + - [Anchors](#anchors) +- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml) +- [Skipping builds](#skipping-builds) +- [Examples](#examples) + + + +--- + +## .gitlab-ci.yml + From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML) file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root of your repository and contains definitions of how your project should be built. @@ -23,12 +65,10 @@ Of course a command can execute code directly (`./configure;make;make install`) or run a script (`test.sh`) in the repository. Jobs are used to create builds, which are then picked up by -[runners](../runners/README.md) and executed within the environment of the -runner. What is important, is that each job is run independently from each +[Runners](../runners/README.md) and executed within the environment of the +Runner. What is important, is that each job is run independently from each other. -## .gitlab-ci.yml - The YAML syntax allows for using more complex job specifications than in the above example: @@ -71,7 +111,7 @@ There are a few reserved `keywords` that **cannot** be used as job names: This allows to specify a custom Docker image and a list of services that can be used for time of the build. The configuration of this feature is covered in -separate document: [Use Docker](../docker/README.md). +[a separate document](../docker/README.md). ### before_script @@ -86,7 +126,8 @@ The specification of `stages` allows for having flexible multi stage pipelines. The ordering of elements in `stages` defines the ordering of builds' execution: 1. Builds of the same stage are run in parallel. -1. Builds of next stage are run after success. +1. Builds of the next stage are run after the jobs from the previous stage + complete successfully. Let's consider the following example, which defines 3 stages: @@ -98,9 +139,9 @@ stages: ``` 1. First all jobs of `build` are executed in parallel. -1. If all jobs of `build` succeeds, the `test` jobs are executed in parallel. -1. If all jobs of `test` succeeds, the `deploy` jobs are executed in parallel. -1. If all jobs of `deploy` succeeds, the commit is marked as `success`. +1. If all jobs of `build` succeed, the `test` jobs are executed in parallel. +1. If all jobs of `test` succeed, the `deploy` jobs are executed in parallel. +1. If all jobs of `deploy` succeed, the commit is marked as `success`. 1. If any of the previous jobs fails, the commit is marked as `failed` and no jobs of further stage are executed. @@ -278,14 +319,14 @@ job_name: | Keyword | Required | Description | |---------------|----------|-------------| -| script | yes | Defines a shell script which is executed by runner | +| script | yes | Defines a shell script which is executed by Runner | | image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | | services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | | stage | no | Defines a build stage (default: `test`) | | type | no | Alias for `stage` | | only | no | Defines a list of git refs for which build is created | | except | no | Defines a list of git refs for which build is not created | -| tags | no | Defines a list of tags which are used to select runner | +| tags | no | Defines a list of tags which are used to select Runner | | allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status | | when | no | Define when to run build. Can be `on_success`, `on_failure` or `always` | | dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them| @@ -294,7 +335,7 @@ job_name: ### script -`script` is a shell script which is executed by the runner. For example: +`script` is a shell script which is executed by the Runner. For example: ```yaml job: @@ -375,13 +416,13 @@ except master. ### tags -`tags` is used to select specific runners from the list of all runners that are +`tags` is used to select specific Runners from the list of all Runners that are allowed to run this project. -During the registration of a runner, you can specify the runner's tags, for +During the registration of a Runner, you can specify the Runner's tags, for example `ruby`, `postgres`, `development`. -`tags` allow you to run builds with runners that have the specified tags +`tags` allow you to run builds with Runners that have the specified tags assigned to them: ```yaml @@ -391,7 +432,7 @@ job: - postgres ``` -The specification above, will make sure that `job` is built by a runner that +The specification above, will make sure that `job` is built by a Runner that has both `ruby` AND `postgres` tags defined. ### when From 0385cd5a585572be4d3b72797c14cad23efc48f5 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 13 Apr 2016 21:20:03 +0200 Subject: [PATCH 164/573] Start with iid on branch creation --- app/models/issue.rb | 4 ++-- app/services/merge_requests/build_service.rb | 2 +- app/services/system_note_service.rb | 2 +- doc/workflow/web_editor.md | 2 +- spec/models/issue_spec.rb | 17 ++++++++++++----- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/models/issue.rb b/app/models/issue.rb index e064b0f8b9..3f188e0477 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -106,7 +106,7 @@ class Issue < ActiveRecord::Base def related_branches project.repository.branch_names.select do |branch| - branch.end_with?("-#{iid}") + branch =~ /\A#{iid}-(?!\d+-stable)/i end end @@ -151,7 +151,7 @@ class Issue < ActiveRecord::Base end def to_branch_name - "#{title.parameterize}-#{iid}" + "#{iid}-#{title.parameterize}" end def can_be_worked_on?(current_user) diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 6e9152e444..fa34753c4f 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -51,7 +51,7 @@ module MergeRequests # be interpreted as the use wants to close that issue on this project # Pattern example: 112-fix-mep-mep # Will lead to appending `Closes #112` to the description - if match = merge_request.source_branch.match(/-(\d+)\z/) + if match = merge_request.source_branch.match(/\A(\d+)-/) iid = match[1] closes_issue = "Closes ##{iid}" diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 658b086496..82a0e2fd1f 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -222,7 +222,7 @@ class SystemNoteService # Called when a branch is created from the 'new branch' button on a issue # Example note text: # - # "Started branch `issue-branch-button-201`" + # "Started branch `201-issue-branch-button`" def self.new_issue_branch(issue, project, author, branch) h = Gitlab::Routing.url_helpers link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md index 5685a9d89d..1832567a34 100644 --- a/doc/workflow/web_editor.md +++ b/doc/workflow/web_editor.md @@ -85,7 +85,7 @@ Once you click it, a new branch will be created that diverges from the default branch of your project, by default `master`. The branch name will be based on the title of the issue and as suffix it will have its ID. Thus, the example screenshot above will yield a branch named -`et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum-2`. +`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`. After the branch is created, you can edit files in the repository to fix the issue. When a merge request is created based on the newly created branch, diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 15052aaca2..fac516f956 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -191,12 +191,19 @@ describe Issue, models: true do end describe '#related_branches' do - it "selects the right branches" do + it 'selects the right branches' do allow(subject.project.repository).to receive(:branch_names). - and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name]) + and_return(['mpempe', "#{subject.iid}mepmep", subject.to_branch_name]) expect(subject.related_branches).to eq([subject.to_branch_name]) end + + it 'excludes stable branches from the related branches' do + allow(subject.project.repository).to receive(:branch_names). + and_return(["#{subject.iid}-0-stable"]) + + expect(subject.related_branches).to eq [] + end end it_behaves_like 'an editable mentionable' do @@ -210,11 +217,11 @@ describe Issue, models: true do let(:subject) { create :issue } end - describe "#to_branch_name" do + describe '#to_branch_name' do let(:issue) { create(:issue, title: 'a' * 30) } - it "starts with the issue iid" do - expect(issue.to_branch_name).to match /-#{issue.iid}\z/ + it 'starts with the issue iid' do + expect(issue.to_branch_name).to match /\A#{issue.iid}-a+\z/ end end end From a2931a392a4a96ad6df00c98578c193b91e4027f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 14:06:33 +0100 Subject: [PATCH 165/573] Fixed style issues --- app/views/shared/issuable/_sidebar.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index fe6e412800..56c8eaa059 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -128,7 +128,7 @@ .title.hide-collapsed Notifications - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - %button.btn.btn-block.btn-gray.js-subscribe-button.issuable-subscribe-button.hide-collapsed{:type => 'button'} + %button.btn.btn-block.btn-gray.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } %span= subscribed ? 'Unsubscribe' : 'Subscribe' .subscription-status.hide-collapsed{data: {status: subscribtion_status}} .unsubscribed{class: ( 'hidden' if subscribed )} From e490a54105b6c820e330514aeb91d004997b031c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 14:50:48 +0100 Subject: [PATCH 166/573] Discussion notes update At the moment discussion notes are rendered in a list but arent actually a list item and that can cause overflow issues Moved the dates to be in the header like other notes Fixed overlapping issue on mobile --- .../stylesheets/framework/timeline.scss | 3 +-- app/assets/stylesheets/pages/notes.scss | 6 +++++ .../projects/notes/_discussion.html.haml | 23 +++++++++---------- app/views/projects/notes/_notes.html.haml | 3 ++- .../notes/discussions/_active.html.haml | 6 +---- .../notes/discussions/_commit.html.haml | 9 +++----- .../notes/discussions/_outdated.html.haml | 6 +---- 7 files changed, 25 insertions(+), 31 deletions(-) diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index b91f2f6f89..f0ec250de2 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -39,8 +39,7 @@ .diff-file { border: 1px solid $border-color; border-bottom: none; - margin-left: 0; - margin-right: 0; + margin: 0; } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e421a31549..7489d5de5f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -198,6 +198,12 @@ ul.notes { color: $notes-light-color; } +.discussion-headline-light { + a { + color: $gl-link-color; + } +} + /** * Actions for Discussions/Notes */ diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index b8068835b3..b66fead09b 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -1,13 +1,12 @@ - note = discussion_notes.first -.timeline-entry - .timeline-entry-inner - .timeline-icon - = link_to user_path(note.author) do - = image_tag avatar_icon(note.author_email), class: "avatar s40" - .timeline-content - - if note.for_merge_request? - - (active_notes, outdated_notes) = discussion_notes.partition(&:active?) - = render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0 - = render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0 - - else - = render "projects/notes/discussions/commit", discussion_notes: discussion_notes +.timeline-entry-inner + .timeline-icon + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: "avatar s40" + .timeline-content + - if note.for_merge_request? + - (active_notes, outdated_notes) = discussion_notes.partition(&:active?) + = render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0 + = render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0 + - else + = render "projects/notes/discussions/commit", discussion_notes: discussion_notes diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml index 62db86fb18..4eeaf70e98 100644 --- a/app/views/projects/notes/_notes.html.haml +++ b/app/views/projects/notes/_notes.html.haml @@ -6,7 +6,8 @@ = render discussion_notes - else - = render 'projects/notes/discussion', discussion_notes: discussion_notes + %li.note.note-discussion.timeline-entry + = render 'projects/notes/discussion', discussion_notes: discussion_notes - else - @notes.each do |note| - next unless note.author diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml index cd8a5f0bd0..0ea8862a68 100644 --- a/app/views/projects/notes/discussions/_active.html.haml +++ b/app/views/projects/notes/discussions/_active.html.haml @@ -6,15 +6,11 @@ = "#{note.author.to_reference} started a discussion" = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do on the diff + = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago") .discussion-actions = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do %i.fa.fa-chevron-up Show/hide discussion - .last-update.hide.js-toggle-content - - last_note = discussion_notes.last - last updated by - = link_to_member(@project, last_note.author, avatar: false) - #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')} .discussion-body.js-toggle-content = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml index 46f2ba4bbc..2a2ead58ee 100644 --- a/app/views/projects/notes/discussions/_commit.html.haml +++ b/app/views/projects/notes/discussions/_commit.html.haml @@ -8,21 +8,18 @@ = "#{note.author.to_reference} started a discussion on #{commit_description}" - if commit = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace') + = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago") .discussion-actions = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do %i.fa.fa-chevron-up Show/hide discussion - .last-update.hide.js-toggle-content - - last_note = discussion_notes.last - last updated by - = link_to_member(@project, last_note.author, avatar: false) - #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')} .discussion-body.js-toggle-content - if note.for_diff_line? = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note - else .panel.panel-default .notes{ data: { discussion_id: discussion_notes.first.discussion_id } } - = render discussion_notes + %ul.notes.timeline + = render discussion_notes .discussion-reply-holder = link_to_reply_diff(discussion_notes.first) diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml index f8e000b424..45141bcd1d 100644 --- a/app/views/projects/notes/discussions/_outdated.html.haml +++ b/app/views/projects/notes/discussions/_outdated.html.haml @@ -5,14 +5,10 @@ .inline.discussion-headline-light = "#{note.author.to_reference} started a discussion" on the outdated diff + = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago") .discussion-actions = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do %i.fa.fa-chevron-down Show/hide discussion - .last-update.hide.js-toggle-content - - last_note = discussion_notes.last - last updated by - = link_to_member(@project, last_note.author, avatar: false) - #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')} .discussion-body.js-toggle-content.hide = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note From a54af831bae023770bf9b2633cc45ec0d5f5a66a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 14 Apr 2016 15:53:54 +0200 Subject: [PATCH 167/573] Use rake db:reset instead of db:setup Using db:reset ensures existing tables are first dropped. This in turn ensures that we can drop tables regardless of any foreign key constraints. While CE currently doesn't have any foreign keys EE defines the following relation: remote_mirrors.project_id -> projects.id MySQL will complain whenever you try to drop the "projects" table first even when using "DROP TABLE ... CASCADE". --- bin/setup | 2 +- doc/development/rake_tasks.md | 2 +- lib/tasks/gitlab/setup.rake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/setup b/bin/setup index acdb2c1389..6cb2d7f1e3 100755 --- a/bin/setup +++ b/bin/setup @@ -18,7 +18,7 @@ Dir.chdir APP_ROOT do # end puts "\n== Preparing database ==" - system "bin/rake db:setup" + system "bin/rake db:reset" puts "\n== Removing old logs and tempfiles ==" system "rm -f log/*" diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 9f3fd69fc4..6d04b9590e 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -9,7 +9,7 @@ bundle exec rake setup ``` The `setup` task is a alias for `gitlab:setup`. -This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database. +This tasks calls `db:reset` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database. Note: `db:setup` calls `db:seed` but this does nothing. ## Run tests diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake index 4cbccf2ca8..48baecfd2a 100644 --- a/lib/tasks/gitlab/setup.rake +++ b/lib/tasks/gitlab/setup.rake @@ -14,7 +14,7 @@ namespace :gitlab do puts "" end - Rake::Task["db:setup"].invoke + Rake::Task["db:reset"].invoke Rake::Task["add_limits_mysql"].invoke Rake::Task["setup_postgresql"].invoke Rake::Task["db:seed_fu"].invoke From 44f3f42bac143f87d756ed38fa7142f98c7be90d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 14:56:05 +0100 Subject: [PATCH 168/573] Dicussion action mobile fix --- app/assets/stylesheets/pages/notes.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 7489d5de5f..e4f96c11aa 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -215,6 +215,17 @@ ul.notes { color: $notes-action-color; } +.discussion-actions { + @media (max-width: $screen-sm-max) { + float: none; + margin-left: 0; + + .note-action-button { + margin-left: 0; + } + } +} + .note-action-button, .discussion-action-button { display: inline-block; From 6191037a248002c6324a2ec242c511050cf36632 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 14:59:16 +0100 Subject: [PATCH 169/573] Increased z-index of right sidebar Fixes issue with active button overlapping it Closes #15243 --- app/assets/stylesheets/pages/issuable.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index d9218e1509..68a26e42b6 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -128,6 +128,7 @@ top: 58px; bottom: 0; right: 0; + z-index: 10; transition: width .3s; background: $gray-light; padding: 10px 20px; From 01f70fc97b887dcbab8d859d2fc6ded28a2c3718 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 15:07:38 +0100 Subject: [PATCH 170/573] Fixed issue with commit time not using timeago --- app/assets/javascripts/application.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 6f435e4c54..5bac8eef1c 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -174,7 +174,7 @@ $ -> $('.trigger-submit').on 'change', -> $(@).parents('form').submit() - gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false) + gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true) # Flash if (flash = $(".flash-container")).length > 0 From 7030ffb0e09dbcc0d03d377846a04fe6cf7d20b6 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 8 Apr 2016 14:43:21 -0700 Subject: [PATCH 171/573] Copying and pasting doesn't grab line numbers or +/- --- app/assets/stylesheets/pages/diff.scss | 20 ++++++++++++++++++++ app/helpers/diff_helper.rb | 1 + app/views/projects/blob/diff.html.haml | 6 +++--- app/views/projects/diffs/_line.html.haml | 6 +++--- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index d0855f6691..ca7fa2094b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -67,6 +67,14 @@ line-height: $code_line_height; font-size: $code_font_size; + &.noteable_line.old:before { + content: '-'; + } + + &.noteable_line.new:before { + content: '+'; + } + span { white-space: pre; } @@ -391,3 +399,15 @@ margin-bottom: 0; } } + +.diff-line-num:not(.js-unfold-bottom) { + a { + &:before { + content: attr(data-linenumber); + } + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + } +} diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index ff32e83449..f1e213b34e 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -44,6 +44,7 @@ module DiffHelper if line.blank? "  ".html_safe else + line[0] = '' line end end diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index abcfca4cd1..ea6d4df725 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -9,9 +9,9 @@ - line_old = line_new - @form.offset %tr.line_holder %td.old_line.diff-line-num{data: {linenumber: line_old}} - = link_to raw(line_old), "#" - %td.new_line.diff-line-num - = link_to raw(line_new) , "#" + / = link_to raw(line_old), "#" + %td.new_line.diff-line-num{data: {linenumber: line_old}} + / = link_to raw(line_new) , "#" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} - if @form.unfold? && @form.bottom? && @form.to < @blob.loc diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 9464c8dc99..2dc6f54843 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -9,12 +9,12 @@ %td.new_line.diff-line-num %td.line_content.match= line.text - else - %td.old_line.diff-line-num{class: type} + %td.old_line.diff-line-num{class: type, data: {linenumber: line.new_pos}} - link_text = raw(type == "new" ? " " : line.old_pos) - if defined?(plain) && plain = link_text - else - = link_to link_text, "##{line_code}", id: line_code + = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code) %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}} @@ -22,5 +22,5 @@ - if defined?(plain) && plain = link_text - else - = link_to link_text, "##{line_code}", id: line_code + = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text) From d176f873e1fdf23ebeeebf6d2d25927941016390 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 13 Apr 2016 12:54:21 -0500 Subject: [PATCH 172/573] Add content attributes to discussion diffs; change styling of add-note icon to prevent white spaces in paste --- app/assets/stylesheets/pages/diff.scss | 26 +++++++++++++------ app/assets/stylesheets/pages/notes.scss | 10 ++----- app/views/projects/blob/diff.html.haml | 4 +-- .../notes/discussions/_diff.html.haml | 6 ++--- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index ca7fa2094b..bd7640db3b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -69,10 +69,12 @@ &.noteable_line.old:before { content: '-'; + position: absolute; } &.noteable_line.new:before { content: '+'; + position: absolute; } span { @@ -400,14 +402,22 @@ } } -.diff-line-num:not(.js-unfold-bottom) { - a { - &:before { - content: attr(data-linenumber); +.file-holder { + .diff-line-num:not(.js-unfold-bottom) { + a { + &:before { + content: attr(data-linenumber); + } + } + } +} + +.discussion { + .diff-content { + .diff-line-num { + &:before { + content: attr(data-linenumber); + } } - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e421a31549..ce44f5aa13 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -276,8 +276,7 @@ ul.notes { .diff-file tr.line_holder { @mixin show-add-diff-note { - filter: alpha(opacity=100); - opacity: 1.0; + display: inline-block; } .add-diff-note { @@ -291,13 +290,8 @@ ul.notes { position: absolute; z-index: 10; width: 32px; - - transition: all 0.2s ease; - // "hide" it by default - opacity: 0.0; - filter: alpha(opacity=0); - + display: none; &:hover { background: $gl-info; color: #fff; diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index ea6d4df725..c6ed78aadf 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -9,9 +9,9 @@ - line_old = line_new - @form.offset %tr.line_holder %td.old_line.diff-line-num{data: {linenumber: line_old}} - / = link_to raw(line_old), "#" + = link_to raw(line_old), "#" %td.new_line.diff-line-num{data: {linenumber: line_old}} - / = link_to raw(line_new) , "#" + = link_to raw(line_new) , "#" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} - if @form.unfold? && @form.bottom? && @form.to < @blob.loc diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index 820e31ccd6..6abfb3abc3 100644 --- a/app/views/projects/notes/discussions/_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml @@ -20,10 +20,8 @@ %td.new_line.diff-line-num= "..." %td.line_content.match= line.text - else - %td.old_line.diff-line-num - = raw(type == "new" ? " " : line.old_pos) - %td.new_line.diff-line-num - = raw(type == "old" ? " " : line.new_pos) + %td.old_line.diff-line-num{data: {linenumber: raw(type == "new" ? " " : line.old_pos)}} + %td.new_line.diff-line-num{data: {linenumber: raw(type == "old" ? " " : line.new_pos)}} %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text) - if line_code == note.line_code From 82d0221b632fbb2e7711678b11e9ff26214d9d69 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 13 Apr 2016 15:33:44 -0500 Subject: [PATCH 173/573] Add line type conditional to diff line helper --- app/assets/stylesheets/pages/diff.scss | 18 +++++++++++------- app/helpers/diff_helper.rb | 6 ++++-- app/views/projects/diffs/_line.html.haml | 2 +- .../projects/notes/discussions/_diff.html.haml | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index bd7640db3b..77d7a3024d 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -67,14 +67,18 @@ line-height: $code_line_height; font-size: $code_font_size; - &.noteable_line.old:before { - content: '-'; - position: absolute; + &.noteable_line.old { + &:before { + content: '-'; + position: absolute; + } } - &.noteable_line.new:before { - content: '+'; - position: absolute; + &.noteable_line.new { + &:before { + content: '+'; + position: absolute; + } } span { @@ -406,7 +410,7 @@ .diff-line-num:not(.js-unfold-bottom) { a { &:before { - content: attr(data-linenumber); + content: attr(data-linenumber); } } } diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index f1e213b34e..0504cfb759 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -40,11 +40,13 @@ module DiffHelper (unfold) ? 'unfold js-unfold' : '' end - def diff_line_content(line) + def diff_line_content(line, line_type = nil) if line.blank? "  ".html_safe else - line[0] = '' + if line_type == 'new' || line_type == 'old' + line[0] = " " + end line end end diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 2dc6f54843..6c5602acd4 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -23,4 +23,4 @@ = link_text - else = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } - %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text) + %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text, type) diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index 6abfb3abc3..9fd9d5bb2a 100644 --- a/app/views/projects/notes/discussions/_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml @@ -22,7 +22,7 @@ - else %td.old_line.diff-line-num{data: {linenumber: raw(type == "new" ? " " : line.old_pos)}} %td.new_line.diff-line-num{data: {linenumber: raw(type == "old" ? " " : line.new_pos)}} - %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text) + %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text, type) - if line_code == note.line_code = render "projects/notes/diff_notes_with_reply", notes: discussion_notes From 2f81baf4767df7ce0144a63b4e451b7d8e09f86a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 13 Apr 2016 18:40:14 -0500 Subject: [PATCH 174/573] Update click_diff_line --- features/steps/shared/diff_note.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 1448c3f44c..e846c52d47 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -227,7 +227,7 @@ module SharedDiffNote end def click_diff_line(code) - find("button[data-line-code='#{code}']").click + find("button[data-line-code='#{code}']").trigger('click') end def click_parallel_diff_line(code, line_type) From 0c082d5e3a34d787f8b2fea0c22fa4256cf82be3 Mon Sep 17 00:00:00 2001 From: connorshea Date: Tue, 12 Apr 2016 20:38:14 -0600 Subject: [PATCH 175/573] Fix the improper delete form being rendered for an oauth_authorized_application This fixes the authorized applications not being revoked properly at `/profile/applications`. Fixes #14370. --- app/views/doorkeeper/applications/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 55f4a6f287..0aff79749e 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -68,7 +68,7 @@ %td= app.name %td= token.created_at %td= token.scopes - %td= render 'delete_form', application: app + %td= render 'doorkeeper/authorized_applications/delete_form', application: app - @authorized_anonymous_tokens.each do |token| %tr %td From c7e384aab23301ad0ee3559252324fa957d15db3 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 12 Apr 2016 22:39:18 -0700 Subject: [PATCH 176/573] Add spec for deletion of authorized OAuth2 application Closes #14370 Move gon function into its own helper --- CHANGELOG | 1 + app/controllers/application_controller.rb | 14 ------- .../oauth/applications_controller.rb | 1 + app/models/oauth_access_token.rb | 19 +++++++++ lib/gitlab/current_settings.rb | 2 + lib/gitlab/gon_helper.rb | 17 ++++++++ spec/factories/oauth_access_tokens.rb | 23 +++++++++++ spec/factories/oauth_applications.rb | 9 +++++ spec/factories/users.rb | 2 +- .../profiles/oauth_applications_spec.rb | 39 +++++++++++++++++++ 10 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 app/models/oauth_access_token.rb create mode 100644 lib/gitlab/gon_helper.rb create mode 100644 spec/factories/oauth_access_tokens.rb create mode 100644 spec/factories/oauth_applications.rb create mode 100644 spec/features/profiles/oauth_applications_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 21f24b5b61..4db4f01bcb 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.7.0 (unreleased) - The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse) + - Fix revoking of authorized OAuth applications (Connor Shea) - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Developers can now add custom tags to transactions (Yorick Peterse) - Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 97d53acde9..bdf2dd6853 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -158,20 +158,6 @@ class ApplicationController < ActionController::Base end end - def add_gon_variables - gon.api_version = API::API.version - gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s - gon.default_issues_tracker = Project.new.default_issue_tracker.to_param - gon.max_file_size = current_application_settings.max_attachment_size - gon.relative_url_root = Gitlab.config.gitlab.relative_url_root - gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - - if current_user - gon.current_user_id = current_user.id - gon.api_token = current_user.private_token - end - end - def validate_user_service_ticket! return unless signed_in? && session[:service_tickets] diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb index d1e4ac10f6..377fef65a9 100644 --- a/app/controllers/oauth/applications_controller.rb +++ b/app/controllers/oauth/applications_controller.rb @@ -4,6 +4,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController before_action :verify_user_oauth_applications_enabled before_action :authenticate_user! + before_action :add_gon_variables layout 'profile' diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb new file mode 100644 index 0000000000..c78c7f4aa0 --- /dev/null +++ b/app/models/oauth_access_token.rb @@ -0,0 +1,19 @@ +# == Schema Information +# +# Table name: oauth_access_tokens +# +# id :integer not null, primary key +# resource_owner_id :integer +# application_id :integer +# token :string not null +# refresh_token :string +# expires_in :integer +# revoked_at :datetime +# created_at :datetime not null +# scopes :string +# + +class OauthAccessToken < ActiveRecord::Base + belongs_to :resource_owner, class_name: 'User' + belongs_to :application, class_name: 'Doorkeeper::Application' +end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 1acc22fe5b..71b53136ed 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -1,5 +1,7 @@ module Gitlab module CurrentSettings + include ::Gitlab::GonHelper + def current_application_settings key = :current_application_settings diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb new file mode 100644 index 0000000000..5ebaad6ca6 --- /dev/null +++ b/lib/gitlab/gon_helper.rb @@ -0,0 +1,17 @@ +module Gitlab + module GonHelper + def add_gon_variables + gon.api_version = API::API.version + gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s + gon.default_issues_tracker = Project.new.default_issue_tracker.to_param + gon.max_file_size = current_application_settings.max_attachment_size + gon.relative_url_root = Gitlab.config.gitlab.relative_url_root + gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class + + if current_user + gon.current_user_id = current_user.id + gon.api_token = current_user.private_token + end + end + end +end diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb new file mode 100644 index 0000000000..4bbc7d3a55 --- /dev/null +++ b/spec/factories/oauth_access_tokens.rb @@ -0,0 +1,23 @@ +# == Schema Information +# +# Table name: oauth_access_tokens +# +# id :integer not null, primary key +# resource_owner_id :integer +# application_id :integer +# token :string not null +# refresh_token :string +# expires_in :integer +# revoked_at :datetime +# created_at :datetime not null +# scopes :string +# + +FactoryGirl.define do + factory :oauth_access_token do + resource_owner + application + token '123456' + created_at :datetime + end +end diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb new file mode 100644 index 0000000000..d116a57383 --- /dev/null +++ b/spec/factories/oauth_applications.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do + name { FFaker::Name.name } + uid { FFaker::Name.name } + redirect_uri { FFaker::Internet.uri('http') } + owner + owner_type 'User' + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index a5c60c51c5..a9b2148bd2 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -1,7 +1,7 @@ FactoryGirl.define do sequence(:name) { FFaker::Name.name } - factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator] do + factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator, :resource_owner] do email { FFaker::Internet.email } name sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" } diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb new file mode 100644 index 0000000000..1a5a9059db --- /dev/null +++ b/spec/features/profiles/oauth_applications_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe 'Profile > Applications', feature: true do + let(:user) { create(:user) } + + before do + login_as(user) + end + + describe 'User manages applications', js: true do + it 'deletes an application' do + create(:oauth_application, owner: user) + visit oauth_applications_path + + page.within('.oauth-applications') do + expect(page).to have_content('Your applications (1)') + click_button 'Destroy' + end + + expect(page).to have_content('The application was deleted successfully') + expect(page).to have_content('Your applications (0)') + expect(page).to have_content('Authorized applications (0)') + end + + it 'deletes an authorized application' do + create(:oauth_access_token, resource_owner: user) + visit oauth_applications_path + + page.within('.oauth-authorized-applications') do + expect(page).to have_content('Authorized applications (1)') + click_button 'Revoke' + end + + expect(page).to have_content('The application was revoked access.') + expect(page).to have_content('Your applications (0)') + expect(page).to have_content('Authorized applications (0)') + end + end +end From e450892f5688529b8a49e3ae7598f00dbdda7161 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 14 Apr 2016 05:28:46 -0700 Subject: [PATCH 177/573] Include GonHelper separately and remove created_at in factory --- app/controllers/application_controller.rb | 1 + app/controllers/oauth/applications_controller.rb | 1 + lib/gitlab/current_settings.rb | 2 -- spec/factories/oauth_access_tokens.rb | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bdf2dd6853..ce5c84ee9b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,6 +3,7 @@ require 'fogbugz' class ApplicationController < ActionController::Base include Gitlab::CurrentSettings + include Gitlab::GonHelper include GitlabRoutingHelper include PageLayoutHelper diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb index 377fef65a9..c6bdd0602c 100644 --- a/app/controllers/oauth/applications_controller.rb +++ b/app/controllers/oauth/applications_controller.rb @@ -1,5 +1,6 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController include Gitlab::CurrentSettings + include Gitlab::GonHelper include PageLayoutHelper before_action :verify_user_oauth_applications_enabled diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 71b53136ed..1acc22fe5b 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -1,7 +1,5 @@ module Gitlab module CurrentSettings - include ::Gitlab::GonHelper - def current_application_settings key = :current_application_settings diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb index 4bbc7d3a55..7700b15d53 100644 --- a/spec/factories/oauth_access_tokens.rb +++ b/spec/factories/oauth_access_tokens.rb @@ -18,6 +18,5 @@ FactoryGirl.define do resource_owner application token '123456' - created_at :datetime end end From 0debd9c7d3db445efc89e72d7a1192e5a40d5044 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 14 Apr 2016 15:26:53 +0100 Subject: [PATCH 178/573] fix labels button dropdown not showing how many labels clicked --- app/assets/javascripts/labels_select.js.coffee | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 97a813577e..e70c3cdd19 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -220,9 +220,19 @@ class @LabelsSelect fields: ['title'] selectable: true - toggleLabel: (selected) -> + toggleLabel: (selected, el) -> + selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active') + if selected and selected.title? - selected.title + if selected_labels and selected_labels.length > 1 + "#{selected.title} +#{selected_labels.length - 1} more" + else + selected.title + else if not selected and selected_labels.length isnt 0 + if selected_labels.length > 1 + "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more" + else if selected_labels.length is 1 + $(selected_labels).text() else defaultLabel fieldName: $dropdown.data('field-name') @@ -238,7 +248,7 @@ class @LabelsSelect page = $('body').data 'page' isIssueIndex = page is 'projects:issues:index' isMRIndex = page is page is 'projects:merge_requests:index' - + $selectbox.hide() # display:block overrides the hide-collapse rule $value.removeAttr('style') From a8ea2c18959c700913132d8a2d7ce3a211303c10 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 14 Apr 2016 11:34:42 -0300 Subject: [PATCH 179/573] Change transfer service to use existing methods --- app/services/projects/transfer_service.rb | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 0d8f8c6fbe..79a27f4af7 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -35,11 +35,9 @@ module Projects end # Apply new namespace id and visibility level - project.tap do |p| - p.namespace = new_namespace - setup_visibility_level(p, new_namespace) - p.save! - end + project.namespace = new_namespace + project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group? + project.save! # Notifications project.send_move_instructions(old_path) @@ -71,15 +69,5 @@ module Projects namespace.id != project.namespace_id && current_user.can?(:create_projects, namespace) end - - private - - def setup_visibility_level(project, new_namespace) - return unless new_namespace.is_a?(Group) - - if project.visibility_level > new_namespace.visibility_level - project.visibility_level = new_namespace.visibility_level - end - end end end From 91cebb7289df872ae4887f7103f68a6f948ee61c Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 14 Apr 2016 17:04:18 +0200 Subject: [PATCH 180/573] Documentation / help improvements --- app/views/admin/projects/show.html.haml | 4 +++- doc/README.md | 1 + doc/administration/repository_checks.md | 7 +++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 1e44172f06..73986d21bc 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -10,7 +10,7 @@ .col-md-12 .panel .panel-heading.alert.alert-danger - Last repository check + Last repository check = "(#{time_ago_in_words(@project.last_repository_check_at)} ago)" failed. See = link_to 'repocheck.log', admin_logs_path @@ -126,6 +126,8 @@ - else passed. + = link_to icon('question-circle'), help_page_path('administration', 'repository_checks') + .form-group = f.submit 'Trigger repository check', class: 'btn btn-primary' diff --git a/doc/README.md b/doc/README.md index d266093065..e6ac479482 100644 --- a/doc/README.md +++ b/doc/README.md @@ -31,6 +31,7 @@ - [Environment Variables](administration/environment_variables.md) to configure GitLab. - [Operations](operations/README.md) Keeping GitLab up and running - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. +- [Repository checks](administration/repository_checks.md) Periodic Git repository checks - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md index 16f4627a59..61bf8ce616 100644 --- a/doc/administration/repository_checks.md +++ b/doc/administration/repository_checks.md @@ -1,10 +1,9 @@ # Repository checks -_**Note:** This feature was [introduced][ce-3232] in GitLab 8.7_ +>**Note:** +This feature was [introduced][ce-3232] in GitLab 8.7. ---- - -Git has a built-in mechanism, \[git fsck\]\[git-fsck\], to verify the +Git has a built-in mechanism, [git fsck][git-fsck], to verify the integrity of all data commited to a repository. GitLab administrators can trigger such a check for a project via the project page under the admin panel. The checks run asynchronously so it may take a few minutes From 77daebe660eb3f4189ca6339e0fcd634abffe3fa Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 14 Apr 2016 17:05:41 +0200 Subject: [PATCH 181/573] More create_list --- spec/mailers/repository_check_mailer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb index 6ae9a93aaa..583bf15176 100644 --- a/spec/mailers/repository_check_mailer_spec.rb +++ b/spec/mailers/repository_check_mailer_spec.rb @@ -5,7 +5,7 @@ describe RepositoryCheckMailer do describe '.notify' do it 'emails all admins' do - admins = 3.times.map { create(:admin) } + admins = create_list(:admin, 3) mail = described_class.notify(1) From b49069b72cbfa73b6f7bb7195659ba2958f02f7e Mon Sep 17 00:00:00 2001 From: Jamie Neubert Pedersen Date: Thu, 14 Apr 2016 16:04:18 +0000 Subject: [PATCH 182/573] change: "very demand" changed to "specific demand" This must be an error as the sentence doesn't make sense otherwise --- doc/ci/runners/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 295d953db1..c7df0713a3 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -19,7 +19,7 @@ many projects, you can have a single or a small number of runners that handle multiple projects. This makes it easier to maintain and update runners. **Specific runners** are useful for jobs that have special requirements or for -projects with a very demand. If a job has certain requirements, you can set +projects with a specific demand. If a job has certain requirements, you can set up the specific runner with this in mind, while not having to do this for all runners. For example, if you want to deploy a certain project, you can setup a specific runner to have the right credentials for this. From 46d1cf43a43a4d7a25f25be97d1fee79cefdc773 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 14 Apr 2016 18:05:53 +0200 Subject: [PATCH 183/573] update changelog [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 1d1e541e65..cfe47a4e56 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.7.0 (unreleased) - Fix admin/projects when using visibility levels on search (PotHix) - Build status notifications - API: Expose user location (Robert Schilling) + - Add encrypted credentials for imported projects and migrate old ones v 8.6.5 (unreleased) - Only update repository language if it is not set to improve performance From 038e623042af614fb739f49a9302ec42cab9812a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 17:26:32 +0100 Subject: [PATCH 184/573] Fixed tests --- .../projects/notes/_discussion.html.haml | 23 ++++++++++--------- app/views/projects/notes/_notes.html.haml | 3 +-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index b66fead09b..572b00a38c 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -1,12 +1,13 @@ - note = discussion_notes.first -.timeline-entry-inner - .timeline-icon - = link_to user_path(note.author) do - = image_tag avatar_icon(note.author_email), class: "avatar s40" - .timeline-content - - if note.for_merge_request? - - (active_notes, outdated_notes) = discussion_notes.partition(&:active?) - = render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0 - = render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0 - - else - = render "projects/notes/discussions/commit", discussion_notes: discussion_notes +%li.note.note-discussion.timeline-entry + .timeline-entry-inner + .timeline-icon + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: "avatar s40" + .timeline-content + - if note.for_merge_request? + - (active_notes, outdated_notes) = discussion_notes.partition(&:active?) + = render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0 + = render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0 + - else + = render "projects/notes/discussions/commit", discussion_notes: discussion_notes diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml index 4eeaf70e98..62db86fb18 100644 --- a/app/views/projects/notes/_notes.html.haml +++ b/app/views/projects/notes/_notes.html.haml @@ -6,8 +6,7 @@ = render discussion_notes - else - %li.note.note-discussion.timeline-entry - = render 'projects/notes/discussion', discussion_notes: discussion_notes + = render 'projects/notes/discussion', discussion_notes: discussion_notes - else - @notes.each do |note| - next unless note.author From 7ef35b9c61b61b6cf03f20ce4ab34272404dca40 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 Apr 2016 12:28:48 -0400 Subject: [PATCH 185/573] Shut up, Rubocop --- app/workers/repository_check/clear_worker.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb index 9c3347a704..b7202ddff3 100644 --- a/app/workers/repository_check/clear_worker.rb +++ b/app/workers/repository_check/clear_worker.rb @@ -1,9 +1,9 @@ module RepositoryCheck class ClearWorker include Sidekiq::Worker - + sidekiq_options retry: false - + def perform # Do small batched updates because these updates will be slow and locking Project.select(:id).find_in_batches(batch_size: 100) do |batch| @@ -14,4 +14,4 @@ module RepositoryCheck end end end -end \ No newline at end of file +end From cce21e7490d8684fb8fbaa6c9d5a8ce76c36d975 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 Apr 2016 12:32:33 -0400 Subject: [PATCH 186/573] Update CHANGELOG for "Auto git fsck" --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 41b6e2c67f..1f94a574d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.7.0 (unreleased) - Add endpoints to archive or unarchive a project !3372 - Add links to CI setup documentation from project settings and builds pages - Handle nil descriptions in Slack issue messages (Stan Hu) + - Add automated repository integrity checks - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) - API: Ability to star and unstar a project (Robert Schilling) - Add default scope to projects to exclude projects pending deletion From 2165bbc7853016ea68f36b44ad0590623add7bcf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 14 Apr 2016 20:31:19 +0300 Subject: [PATCH 187/573] Remove deprecated NGINX CI config --- lib/support/nginx/gitlab_ci | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 lib/support/nginx/gitlab_ci diff --git a/lib/support/nginx/gitlab_ci b/lib/support/nginx/gitlab_ci deleted file mode 100644 index bf05edfd78..0000000000 --- a/lib/support/nginx/gitlab_ci +++ /dev/null @@ -1,29 +0,0 @@ -# GITLAB CI -server { - listen 80 default_server; # e.g., listen 192.168.1.1:80; - server_name YOUR_CI_SERVER_FQDN; # e.g., server_name source.example.com; - - access_log /var/log/nginx/gitlab_ci_access.log; - error_log /var/log/nginx/gitlab_ci_error.log; - - # expose API to fix runners - location /api { - proxy_read_timeout 300; - proxy_connect_timeout 300; - proxy_redirect off; - proxy_set_header X-Real-IP $remote_addr; - - # You need to specify your DNS servers that are able to resolve YOUR_GITLAB_SERVER_FQDN - resolver 8.8.8.8 8.8.4.4; - proxy_pass $scheme://YOUR_GITLAB_SERVER_FQDN/ci$request_uri; - } - - # redirect all other CI requests - location / { - return 301 $scheme://YOUR_GITLAB_SERVER_FQDN/ci$request_uri; - } - - # adjust this to match the largest build log your runners might submit, - # set to 0 to disable limit - client_max_body_size 10m; -} \ No newline at end of file From 80d8f8b87609ffb9b0fcc1f74fbeb4520a193e07 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 14 Apr 2016 09:10:36 -0500 Subject: [PATCH 188/573] Syntax & style updates --- app/assets/stylesheets/pages/diff.scss | 24 +++++++++++-------- app/helpers/diff_helper.rb | 4 +--- app/views/projects/blob/diff.html.haml | 12 +++++----- app/views/projects/diffs/_line.html.haml | 16 ++++++------- .../notes/discussions/_diff.html.haml | 6 ++--- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 77d7a3024d..183f22a1b2 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -67,17 +67,21 @@ line-height: $code_line_height; font-size: $code_font_size; - &.noteable_line.old { - &:before { - content: '-'; - position: absolute; - } - } + &.noteable_line { + position: relative; - &.noteable_line.new { - &:before { - content: '+'; - position: absolute; + &.old { + &:before { + content: '-'; + position: absolute; + } + } + + &.new { + &:before { + content: '+'; + position: absolute; + } } } diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 0504cfb759..6a3ec83b8c 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -44,9 +44,7 @@ module DiffHelper if line.blank? "  ".html_safe else - if line_type == 'new' || line_type == 'old' - line[0] = " " - end + line[0] = ' ' if %w[new old].include?(line_type) line end end diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index c6ed78aadf..38e62c81fe 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -1,20 +1,20 @@ - if @lines.present? - if @form.unfold? && @form.since != 1 && !@form.bottom? %tr.line_holder{ id: @form.since } - = render "projects/diffs/match_line", {line: @match_line, - line_old: @form.since, line_new: @form.since, bottom: false, new_file: false} + = render "projects/diffs/match_line", { line: @match_line, + line_old: @form.since, line_new: @form.since, bottom: false, new_file: false } - @lines.each_with_index do |line, index| - line_new = index + @form.since - line_old = line_new - @form.offset %tr.line_holder - %td.old_line.diff-line-num{data: {linenumber: line_old}} + %td.old_line.diff-line-num{ data: { linenumber: line_old } } = link_to raw(line_old), "#" - %td.new_line.diff-line-num{data: {linenumber: line_old}} + %td.new_line.diff-line-num{ data: { linenumber: line_old } } = link_to raw(line_new) , "#" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} - if @form.unfold? && @form.bottom? && @form.to < @blob.loc %tr.line_holder{ id: @form.to } - = render "projects/diffs/match_line", {line: @match_line, - line_old: @form.to, line_new: @form.to, bottom: true, new_file: false} + = render "projects/diffs/match_line", { line: @match_line, + line_old: @form.to, line_new: @form.to, bottom: true, new_file: false } diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 6c5602acd4..107097ad96 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -1,26 +1,26 @@ - type = line.type -%tr.line_holder{id: line_code, class: type} +%tr.line_holder{ id: line_code, class: type } - case type - when 'match' - = render "projects/diffs/match_line", {line: line.text, - line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file} + = render "projects/diffs/match_line", { line: line.text, + line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file } - when 'nonewline' %td.old_line.diff-line-num %td.new_line.diff-line-num %td.line_content.match= line.text - else - %td.old_line.diff-line-num{class: type, data: {linenumber: line.new_pos}} - - link_text = raw(type == "new" ? " " : line.old_pos) + %td.old_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } } + - link_text = type == "new" ? " ".html_safe : line.old_pos - if defined?(plain) && plain = link_text - else = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code) - %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}} - - link_text = raw(type == "old" ? " " : line.new_pos) + %td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } } + - link_text = type == "old" ? " ".html_safe : line.new_pos - if defined?(plain) && plain = link_text - else = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text } - %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text, type) + %td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code } }= diff_line_content(line.text, type) diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index 9fd9d5bb2a..d46aab000c 100644 --- a/app/views/projects/notes/discussions/_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml @@ -20,9 +20,9 @@ %td.new_line.diff-line-num= "..." %td.line_content.match= line.text - else - %td.old_line.diff-line-num{data: {linenumber: raw(type == "new" ? " " : line.old_pos)}} - %td.new_line.diff-line-num{data: {linenumber: raw(type == "old" ? " " : line.new_pos)}} - %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text, type) + %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? " ".html_safe : line.old_pos } } + %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? " ".html_safe : line.new_pos } } + %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type) - if line_code == note.line_code = render "projects/notes/diff_notes_with_reply", notes: discussion_notes From 6ae2680ce7159eedd37e89c7aa99688949139f11 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 14 Apr 2016 19:38:17 +0300 Subject: [PATCH 189/573] Emoji categories fix --- CHANGELOG | 1 + lib/award_emoji.rb | 16 +++++++++++++--- spec/lib/award_emoji_spec.rb | 7 +++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9aa40fae18..4a9a5c1bc3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -66,6 +66,7 @@ v 8.7.0 (unreleased) - Improved markdown forms - Diffs load at the correct point when linking from from number - Selected diff rows highlight + - Fix emoji catgories in the emoji picker v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb index 4fc3443ac6..5f8ff01b0a 100644 --- a/lib/award_emoji.rb +++ b/lib/award_emoji.rb @@ -14,19 +14,29 @@ class AwardEmoji 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 = {} + @emoji_by_category = Hash.new { |h, key| h[key] = [] } emojis.each do |emoji_name, data| data["name"] = emoji_name - @emoji_by_category[data["category"]] ||= [] - @emoji_by_category[data["category"]] << data + # 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 diff --git a/spec/lib/award_emoji_spec.rb b/spec/lib/award_emoji_spec.rb index 330678f7f1..88c2291295 100644 --- a/spec/lib/award_emoji_spec.rb +++ b/spec/lib/award_emoji_spec.rb @@ -16,4 +16,11 @@ describe AwardEmoji do end end end + + describe '.emoji_by_category' do + it "only contains known categories" do + undefined_categories = AwardEmoji.emoji_by_category.keys - AwardEmoji::CATEGORIES.keys + expect(undefined_categories).to be_empty + end + end end From d0ad566972dc5c47390df1720cdee8088fda6c73 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 14 Apr 2016 18:52:40 +0100 Subject: [PATCH 190/573] rename the test file --- app/helpers/issuables_helper.rb | 2 +- .../{filter_by_labels.spec.rb => filter_by_labels_spec.rb} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename spec/features/issues/{filter_by_labels.spec.rb => filter_by_labels_spec.rb} (100%) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 14e624cb7c..b363ed3076 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -29,7 +29,7 @@ module IssuablesHelper default_label end else - if current_labels.nil? + if current_labels.nil? || current_labels.empty? default_label else current_labels diff --git a/spec/features/issues/filter_by_labels.spec.rb b/spec/features/issues/filter_by_labels_spec.rb similarity index 100% rename from spec/features/issues/filter_by_labels.spec.rb rename to spec/features/issues/filter_by_labels_spec.rb From f58312976733fadb2b0cbf4b734f8d94220cb501 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 Apr 2016 13:56:30 -0400 Subject: [PATCH 191/573] Add Sentry program context even without a current user --- app/controllers/application_controller.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ce5c84ee9b..1c53b0b21a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base before_action :check_password_expiration before_action :check_2fa_requirement before_action :ldap_security_check - before_action :sentry_user_context + before_action :sentry_context before_action :default_headers before_action :add_gon_variables before_action :configure_permitted_parameters, if: :devise_controller? @@ -41,13 +41,15 @@ class ApplicationController < ActionController::Base protected - def sentry_user_context - if Rails.env.production? && current_application_settings.sentry_enabled && current_user - Raven.user_context( - id: current_user.id, - email: current_user.email, - username: current_user.username, - ) + def sentry_context + if Rails.env.production? && current_application_settings.sentry_enabled + if current_user + Raven.user_context( + id: current_user.id, + email: current_user.email, + username: current_user.username, + ) + end Raven.tags_context(program: sentry_program_context) end From 5bf47f14b962b0e676e399dddc238d7bf764304d Mon Sep 17 00:00:00 2001 From: theoretick Date: Thu, 7 Apr 2016 15:49:35 -0700 Subject: [PATCH 192/573] Do not include award emojis in issue view comment_count Fixes Issue #14431 --- CHANGELOG | 1 + app/views/projects/issues/_issue.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 2 +- .../projects/merge_requests/_show.html.haml | 2 +- spec/features/issues_spec.rb | 16 ++++++++++-- spec/features/notes_on_merge_requests_spec.rb | 25 +++++++++++++++++++ 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ec210867e7..b67a12eaee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.7.0 (unreleased) - Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) + - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) - Add setting for customizing the list of trusted proxies !3524 diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 4aa92d0b39..7a8009f6da 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.count + - note_count = issue.notes.user.nonawards.count - if note_count > 0 %li = link_to issue_path(issue) + "#notes" do diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 391193eed6..e740fe8c84 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.count + - note_count = merge_request.mr_and_commit_notes.user.nonawards.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 1dd8f721f7..07037a14f5 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -51,7 +51,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.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 diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 79000666cc..1ce0024e93 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -45,7 +45,7 @@ describe 'Issues', feature: true do project: project) end - it 'allows user to select unasigned', js: true do + it 'allows user to select unassigned', js: true do visit edit_namespace_project_issue_path(project.namespace, project, issue) expect(page).to have_content "Assignee #{@user.name}" @@ -64,6 +64,18 @@ describe 'Issues', feature: true do end end + describe 'Issue info' do + it 'excludes award_emoji from comment count' do + issue = create(:issue, author: @user, assignee: @user, project: project, title: 'foobar') + create(:upvote_note, noteable: issue) + + visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id) + + expect(page).to have_content 'foobar' + expect(page.all('.issue-no-comments').first.text).to eq "0" + end + end + describe 'Filter issue' do before do ['foobar', 'barbaz', 'gitlab'].each do |title| @@ -187,7 +199,7 @@ describe 'Issues', feature: true do describe 'update assignee from issue#show' do let(:issue) { create(:issue, project: project, author: @user, assignee: @user) } - context 'by autorized user' do + context 'by authorized user' do it 'allows user to select unassigned', js: true do visit namespace_project_issue_path(project.namespace, project, issue) diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 5f855ccc70..389812ff7e 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -4,6 +4,20 @@ describe 'Comments', feature: true do include RepoHelpers include WaitForAjax + describe 'On merge requests page', feature: true do + it 'excludes award_emoji from comment count' do + merge_request = create(:merge_request) + project = merge_request.source_project + create(:upvote_note, noteable: merge_request, project: project) + + login_as :admin + visit namespace_project_merge_requests_path(project.namespace, project) + + expect(merge_request.mr_and_commit_notes.count).to eq 1 + expect(page.all('.merge-request-no-comments').first.text).to eq "0" + end + end + describe 'On a merge request', js: true, feature: true do let!(:merge_request) { create(:merge_request) } let!(:project) { merge_request.source_project } @@ -129,6 +143,17 @@ describe 'Comments', feature: true do end end end + + describe 'comment info' do + it 'excludes award_emoji from comment count' do + create(:upvote_note, noteable: merge_request, project: project) + + 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" + end + end end describe 'On a merge request diff', js: true, feature: true do From 101f57d9170f5ea8e736f17c2fd8294bbc15e142 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 14 Apr 2016 16:26:48 -0500 Subject: [PATCH 193/573] Wrap code in parallel view --- app/assets/stylesheets/pages/notes.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e421a31549..21eca0a036 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -171,6 +171,11 @@ ul.notes { &.parallel { border-width: 1px; + + .code, + code { + white-space: pre-wrap; + } } .notes { From 90a67a76d5b852c62b59dd52b9dafd58722f2237 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 Apr 2016 17:32:46 -0400 Subject: [PATCH 194/573] Always read diff_view setting from the cookie Prior, when the user had their view set to "parallel" and then visited a merge request's changes tab _without_ passing the `view` parameter via query string, the view would be parallel but the `Notes` class was always instantiated with the default value from `diff_view` ("inline"), resulting in broken markup when the form to add a line note was dynamically inserted. The cookie is set whenever the view is changed, so this value should always be up-to-date. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/14557 --- app/helpers/diff_helper.rb | 8 +++++++- .../projects/merge_requests/_show.html.haml | 2 +- spec/helpers/diff_helper_spec.rb | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 6a3ec83b8c..97466d532f 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -9,7 +9,13 @@ module DiffHelper end def diff_view - params[:view] == 'parallel' ? 'parallel' : 'inline' + diff_views = %w(inline parallel) + + if diff_views.include?(cookies[:diff_view]) + cookies[:diff_view] + else + diff_views.first + end end def diff_hard_limit_enabled? diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 1dd8f721f7..1733622e9b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -4,7 +4,7 @@ = render "header_title" -- if params[:view] == 'parallel' +- if diff_view == 'parallel' - fluid_layout true .merge-request{'data-url' => merge_request_path(@merge_request)} diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 982c113e84..b7810185d1 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -11,6 +11,26 @@ describe DiffHelper do let(:diff_refs) { [commit.parent, commit] } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) } + describe 'diff_view' do + it 'returns a valid value when cookie is set' do + helper.request.cookies[:diff_view] = 'parallel' + + expect(helper.diff_view).to eq 'parallel' + end + + it 'returns a default value when cookie is invalid' do + helper.request.cookies[:diff_view] = 'invalid' + + expect(helper.diff_view).to eq 'inline' + end + + it 'returns a default value when cookie is nil' do + expect(helper.request.cookies).to be_empty + + expect(helper.diff_view).to eq 'inline' + end + end + describe 'diff_hard_limit_enabled?' do it 'should return true if param is provided' do allow(controller).to receive(:params) { { force_show_diff: true } } From 70c9ceb7b1bd815f4a3e79a809a9279f841cee63 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 14 Apr 2016 18:46:18 -0300 Subject: [PATCH 195/573] Fix datetime format when migrating new notification settings on MySQL --- db/migrate/20160328115649_migrate_new_notification_setting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160328115649_migrate_new_notification_setting.rb b/db/migrate/20160328115649_migrate_new_notification_setting.rb index 0a11086902..3c81b2c37b 100644 --- a/db/migrate/20160328115649_migrate_new_notification_setting.rb +++ b/db/migrate/20160328115649_migrate_new_notification_setting.rb @@ -7,7 +7,7 @@ # class MigrateNewNotificationSetting < ActiveRecord::Migration def up - timestamp = Time.now + timestamp = Time.now.strftime('%F %T') execute "INSERT INTO notification_settings ( user_id, source_id, source_type, level, created_at, updated_at ) SELECT user_id, source_id, source_type, notification_level, '#{timestamp}', '#{timestamp}' FROM members WHERE user_id IS NOT NULL" end From d2f490c8f3601915cae0e125a0d88871c7c2b7e8 Mon Sep 17 00:00:00 2001 From: lurdan Date: Fri, 15 Apr 2016 12:41:26 +0900 Subject: [PATCH 196/573] fix required gitlab-shell version. Under the procedure, I've warned about gitlab-shell version, which should be 2.6.12 (may be some minor revisions introduces that). --- doc/update/8.5-to-8.6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md index b9abcbd2c1..6267f14eba 100644 --- a/doc/update/8.5-to-8.6.md +++ b/doc/update/8.5-to-8.6.md @@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-6-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all -sudo -u git -H git checkout v2.6.11 +sudo -u git -H git checkout v2.6.12 ``` ### 5. Update gitlab-workhorse From f5ce601c2b052b18398d2207c64ce8828b628521 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 15 Apr 2016 09:31:26 +0530 Subject: [PATCH 197/573] Make a few style changes based on MR feedback. --- spec/models/issue_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 202ce846dc..ed982d8a9d 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -191,9 +191,9 @@ describe Issue, models: true do end describe '#related_branches' do - let(:user) { build(:user, :admin) } + let(:user) { build(:admin) } - before(:each) do + before do allow(subject.project.repository).to receive(:branch_names). and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "branch-#{subject.iid}"]) From cfcd95b0c3c0c3699de8724e60cc2bce687a9132 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 15 Apr 2016 10:03:21 +0530 Subject: [PATCH 198/573] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 071e35167f..963d2ad681 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.7.0 (unreleased) - Update number of Todos in the sidebar when it's marked as "Done". !3600 - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - API: User can leave a project through the API when not master or owner. !3613 + - While signing up, don't persist the user password across form redisplays v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) From ca725aaf4ca7cb300c50d539fe27efc45a32dfd6 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 15 Apr 2016 08:52:26 +0200 Subject: [PATCH 199/573] resolve merge conflict --- CHANGELOG | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d0638c880c..6de5a9b938 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,11 +57,6 @@ v 8.7.0 (unreleased) - Fix admin/projects when using visibility levels on search (PotHix) - Build status notifications - API: Expose user location (Robert Schilling) - - Add encrypted credentials for imported projects and migrate old ones - -v 8.6.5 (unreleased) - - Only update repository language if it is not set to improve performance - - Check permissions when user attempts to import members from another project - API: Do not leak group existence via return code (Robert Schilling) - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - Update number of Todos in the sidebar when it's marked as "Done". !3600 @@ -73,6 +68,7 @@ v 8.6.5 (unreleased) - Diffs load at the correct point when linking from from number - Selected diff rows highlight - Fix emoji catgories in the emoji picker + - Add encrypted credentials for imported projects and migrate old ones v 8.6.6 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) From a434ffd3b3a895bd75daed76000def23f4002f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 14 Apr 2016 12:20:16 +0200 Subject: [PATCH 200/573] Make /profile/keys/new redirects to /profile/keys for back-compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report: https://github.com/gitlabhq/gitlabhq/issues/10138 Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/controllers/profiles/keys_controller.rb | 5 +++++ config/routes.rb | 2 +- spec/controllers/profiles/keys_controller_spec.rb | 12 +++++++++++- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e5c8620beb..da4f9b0fb6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.7.0 (unreleased) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - Expose project badges in project settings + - Make /profile/keys/new redirect to /profile/keys for back-compat. !3717 - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) - Expose label description in API (Mariusz Jachimowicz) diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index b88c080352..a12549d6bc 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController @key = current_user.keys.find(params[:id]) end + # Back-compat: We need to support this URL since git-annex webapp points to it + def new + redirect_to profile_keys_path + end + def create @key = current_user.keys.new(key_params) diff --git a/config/routes.rb b/config/routes.rb index 688b83d2c9..f32f7ea855 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -326,7 +326,7 @@ Rails.application.routes.draw do end end resource :preferences, only: [:show, :update] - resources :keys, except: [:new] + resources :keys resources :emails, only: [:index, :create, :destroy] resource :avatar, only: [:destroy] resource :two_factor_auth, only: [:new, :create, :destroy] do diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb index b6573f105d..3a82083717 100644 --- a/spec/controllers/profiles/keys_controller_spec.rb +++ b/spec/controllers/profiles/keys_controller_spec.rb @@ -1,7 +1,17 @@ require 'spec_helper' describe Profiles::KeysController do - let(:user) { create(:user) } + let(:user) { create(:user) } + + describe '#new' do + before { sign_in(user) } + + it 'redirect to #index' do + get :new + + expect(response).to redirect_to(profile_keys_path) + end + end describe "#get_keys" do describe "non existant user" do From fcf0612f38b719212a7b40cdaf6b026f5b5f59e7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 15 Apr 2016 09:47:46 +0100 Subject: [PATCH 201/573] Updated note form focus to better match form-control focus --- app/assets/stylesheets/pages/note_form.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 07c707e7b7..c2371d7998 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -61,11 +61,11 @@ padding: $gl-padding-top $gl-padding; border: 1px solid $note-form-border-color; border-radius: $border-radius-base; + transition: border-color ease-in-out 0.15s, + box-shadow ease-in-out 0.15s; &.is-focused { - border-color: $focus-border-color; - box-shadow: 0 0 2px $black-transparent, - 0 0 4px rgba($focus-border-color, .4); + @extend .form-control:focus; .comment-toolbar, .nav-links { From f8e8a61c9ce6d90347960a3fda8abd15c32ddc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 15 Apr 2016 11:38:59 +0200 Subject: [PATCH 202/573] Remove Gitlab::Shell#update_repository_head since it's not used anymore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/backend/shell.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index b9bb6e7608..db44a9a44d 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -54,19 +54,6 @@ module Gitlab "#{path}.git", "#{new_path}.git"]) end - # Update HEAD for repository - # - # path - project path with namespace - # branch - repository branch name - # - # Ex. - # update_repository_head("gitlab/gitlab-ci", "3-1-stable") - # - def update_repository_head(path, branch) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head', - "#{path}.git", branch]) - end - # Fork repository to new namespace # # path - project path with namespace From 106c443e76fe143ef3d7ef504b404fa4a2667e93 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 15 Apr 2016 13:30:33 +0200 Subject: [PATCH 203/573] Remove unused backend methods --- lib/gitlab/backend/shell.rb | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index b9bb6e7608..0c3dbc744b 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -92,33 +92,6 @@ module Gitlab 'rm-project', "#{name}.git"]) end - # Add repository branch from passed ref - # - # path - project path with namespace - # branch_name - new branch name - # ref - HEAD for new branch - # - # Ex. - # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") - # - def add_branch(path, branch_name, ref) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch', - "#{path}.git", branch_name, ref]) - end - - # Remove repository branch - # - # path - project path with namespace - # branch_name - branch name to remove - # - # Ex. - # rm_branch("gitlab/gitlab-ci", "4-0-stable") - # - def rm_branch(path, branch_name) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch', - "#{path}.git", branch_name]) - end - # Add repository tag from passed ref # # path - project path with namespace From 3c704c33e0d6c91ecc156d8bcdf260b0c4c23a27 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 15 Apr 2016 14:21:28 +0200 Subject: [PATCH 204/573] Delete tags via rugged --- CHANGELOG | 1 + app/models/repository.rb | 7 ++++++- lib/gitlab/backend/shell.rb | 13 ------------- spec/models/repository_spec.rb | 6 ++---- spec/services/delete_tag_service_spec.rb | 11 +---------- 5 files changed, 10 insertions(+), 28 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 69b464bdc6..453d0ba1ac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -67,6 +67,7 @@ v 8.7.0 (unreleased) - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - Improved markdown forms + - Delete tags using Rugged for performance reasons (Robert Schilling) - Diffs load at the correct point when linking from from number - Selected diff rows highlight - Fix emoji catgories in the emoji picker diff --git a/app/models/repository.rb b/app/models/repository.rb index 8906217048..308c590e3f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -169,7 +169,12 @@ class Repository def rm_tag(tag_name) before_remove_tag - gitlab_shell.rm_tag(path_with_namespace, tag_name) + begin + rugged.tags.delete(tag_name) + true + rescue Rugged::ReferenceError + false + end end def branch_names diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index b9bb6e7608..4c6f0d3d68 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -137,19 +137,6 @@ module Gitlab Gitlab::Utils.system_silent(cmd) end - # Remove repository tag - # - # path - project path with namespace - # tag_name - tag name to remove - # - # Ex. - # rm_tag("gitlab/gitlab-ci", "v4.0") - # - def rm_tag(path, tag_name) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag', - "#{path}.git", tag_name]) - end - # Gc repository # # path - project path with namespace diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 86f68b3a0a..c163001b7c 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -770,11 +770,9 @@ describe Repository, models: true do describe '#rm_tag' do it 'removes a tag' do expect(repository).to receive(:before_remove_tag) + expect(repository.rugged.tags).to receive(:delete).with('v1.1.0') - expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag). - with(repository.path_with_namespace, '8.5') - - repository.rm_tag('8.5') + repository.rm_tag('v1.1.0') end end diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/delete_tag_service_spec.rb index 5b7ba52181..477551f503 100644 --- a/spec/services/delete_tag_service_spec.rb +++ b/spec/services/delete_tag_service_spec.rb @@ -6,21 +6,12 @@ describe DeleteTagService, services: true do let(:user) { create(:user) } let(:service) { described_class.new(project, user) } - let(:tag) { double(:tag, name: '8.5', target: 'abc123') } - describe '#execute' do - before do - allow(repository).to receive(:find_tag).and_return(tag) - end - it 'removes the tag' do - expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag). - and_return(true) - expect(repository).to receive(:before_remove_tag) expect(service).to receive(:success) - service.execute('8.5') + service.execute('v1.1.0') end end end From 5b374226a76b7369876bad84671be58bcf6e1b4c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 15 Apr 2016 16:36:07 +0100 Subject: [PATCH 205/573] Indented code --- .../merge_requests/_new_compare.html.haml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 3e2a37e050..b08524574e 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -17,8 +17,8 @@ = dropdown_filter("Search projects") = dropdown_content do = render 'projects/merge_requests/dropdowns/project', - projects: [@merge_request.source_project], - selected: f.object.source_project_id + projects: [@merge_request.source_project], + selected: f.object.source_project_id .merge-request-select.dropdown = f.hidden_field :source_branch = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } @@ -27,8 +27,8 @@ = dropdown_filter("Search branches") = dropdown_content do = render 'projects/merge_requests/dropdowns/branch', - branches: @merge_request.source_branches, - selected: f.object.source_branch + branches: @merge_request.source_branches, + selected: f.object.source_branch .panel-footer = icon('spinner spin', class: 'js-source-loading') %ul.list-unstyled.mr_source_commit @@ -47,8 +47,8 @@ = dropdown_filter("Search projects") = dropdown_content do = render 'projects/merge_requests/dropdowns/project', - projects: projects, - selected: f.object.target_project_id + projects: projects, + selected: f.object.target_project_id .merge-request-select.dropdown = f.hidden_field :target_branch = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } @@ -57,8 +57,8 @@ = dropdown_filter("Search branches") = dropdown_content do = render 'projects/merge_requests/dropdowns/branch', - branches: @merge_request.target_branches, - selected: f.object.target_branch + branches: @merge_request.target_branches, + selected: f.object.target_branch .panel-footer = icon('spinner spin', class: "js-target-loading") %ul.list-unstyled.mr_target_commit From 50998e69bf743bda5d053262237145bd240bca8b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 23:05:49 -0500 Subject: [PATCH 206/573] Restore broken code --- app/assets/javascripts/labels_select.js.coffee | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index bc80980acb..c1fcd764a6 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -20,15 +20,6 @@ class @LabelsSelect $value = $block.find('.value') $loading = $block.find('.block-loading').fadeOut() - if newLabelField.length - $newLabelCreateButton = $('.js-new-label-btn') - $colorPreview = $('.js-dropdown-label-color-preview') - $newLabelError = $dropdown.parent().find('.js-label-error') - $newLabelError.hide() - - # Suggested colors in the dropdown to chose from pre-chosen colors - $('.suggest-colors-dropdown a').on 'click', (e) -> - issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL? if issueUpdateURL labelHTMLTemplate = _.template( @@ -43,6 +34,12 @@ class @LabelsSelect labelNoneHTMLTemplate = _.template('
None
') if newLabelField.length and $dropdown.hasClass 'js-extra-options' + $newLabelCreateButton = $('.js-new-label-btn') + $colorPreview = $('.js-dropdown-label-color-preview') + $newLabelError = $dropdown.parent().find('.js-label-error') + $newLabelError.hide() + + # Suggested colors in the dropdown to chose from pre-chosen colors $('.suggest-colors-dropdown a').on "click", (e) -> e.preventDefault() e.stopPropagation() From f396b2e6d09fbfe47201ceb0f897c508dabb1b1a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 23:19:47 -0500 Subject: [PATCH 207/573] Reuse partial for Create Label page --- .../shared/issuable/_label_dropdown.html.haml | 18 +----------------- .../issuable/_label_page_create.html.haml | 17 +++++++++++++++++ app/views/shared/issuable/_sidebar.html.haml | 4 +++- 3 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 app/views/shared/issuable/_label_page_create.html.haml diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index f722e61eea..371acc46c5 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -24,21 +24,5 @@ - else View labels - if can? current_user, :admin_label, @project and @project - .dropdown-page-two.dropdown-new-label - = dropdown_title("Create new label", back: true) - = dropdown_content do - .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} - .suggest-colors.suggest-colors-dropdown - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - .dropdown-label-color-input - .dropdown-label-color-preview.js-dropdown-label-color-preview - %input#new_label_color.dropdown-input-field{ type: "text" } - .clearfix - %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"} - Create - %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"} - Cancel + = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml new file mode 100644 index 0000000000..89bc836826 --- /dev/null +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -0,0 +1,17 @@ +.dropdown-page-two.dropdown-new-label + = dropdown_title("Create new label", back: true) + = dropdown_content do + .dropdown-labels-error.js-label-error + %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} + .suggest-colors.suggest-colors-dropdown + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + .dropdown-label-color-input + .dropdown-label-color-preview.js-dropdown-label-color-preview + %input#new_label_color.dropdown-input-field{ type: "text" } + .clearfix + %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"} + Create + %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"} + Cancel diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 08bfd93f4e..2dc5c0c19e 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -96,7 +96,7 @@ - issuable.labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text Label = icon('chevron-down') @@ -118,6 +118,8 @@ Manage labels - else View labels + - if can? current_user, :admin_label, @project and @project + = render "shared/issuable/milestone_dropdown" = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user From a2550392100422a00db8f9baabfded7e2e71d046 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 13 Apr 2016 14:12:40 -0500 Subject: [PATCH 208/573] Add spaces --- app/views/shared/issuable/_label_page_create.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 89bc836826..9179069f78 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -2,7 +2,7 @@ = dropdown_title("Create new label", back: true) = dropdown_content do .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} + %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label"} .suggest-colors.suggest-colors-dropdown - suggested_colors.each do |color| = link_to '#', style: "background-color: #{color}", data: { color: color } do @@ -11,7 +11,7 @@ .dropdown-label-color-preview.js-dropdown-label-color-preview %input#new_label_color.dropdown-input-field{ type: "text" } .clearfix - %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"} + %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" } Create - %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"} + %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" } Cancel From 88ddce257d90114c3981c5c07d621331a683bd05 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 14 Apr 2016 12:57:54 -0500 Subject: [PATCH 209/573] Render proper partial --- app/views/shared/issuable/_label_page_create.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 9179069f78..26ccade425 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -2,7 +2,7 @@ = dropdown_title("Create new label", back: true) = dropdown_content do .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label"} + %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label" } .suggest-colors.suggest-colors-dropdown - suggested_colors.each do |color| = link_to '#', style: "background-color: #{color}", data: { color: color } do diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 2dc5c0c19e..6108239aed 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -119,7 +119,7 @@ - else View labels - if can? current_user, :admin_label, @project and @project - = render "shared/issuable/milestone_dropdown" + = render partial: "shared/issuable/label_page_create" = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user From c659c836f068b4cb1a7b48df0dad9d2fe5749ee4 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 15 Apr 2016 12:05:07 -0500 Subject: [PATCH 210/573] Discussion note icon shows up when you click on diff on touch screen --- app/assets/stylesheets/pages/diff.scss | 1 + app/assets/stylesheets/pages/notes.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 183f22a1b2..9dd87ba206 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -34,6 +34,7 @@ background: #fff; color: #333; border-radius: 0 0 3px 3px; + -webkit-overflow-scrolling: auto; .unfold { cursor: pointer; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index ce44f5aa13..60559cce8a 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -286,7 +286,7 @@ ul.notes { padding: 4px; font-size: 16px; color: $gl-link-color; - margin-left: -60px; + margin-left: -56px; position: absolute; z-index: 10; width: 32px; From 91dca9a883729d3ba65bb37c4c5d052aba38d405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 15 Apr 2016 19:13:31 +0200 Subject: [PATCH 211/573] Add 8.6.6 CHANGELOG entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8c0e0f2f0d..df4e24f112 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,12 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) - - The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse) - - Fix revoking of authorized OAuth applications (Connor Shea) - - All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - - Developers can now add custom tags to transactions (Yorick Peterse) - - Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse) + - The Projects::HousekeepingService class has extra instrumentation + - All service classes (those residing in app/services) are now instrumented + - Developers can now add custom tags to transactions + - Loading of an issue's referenced merge requests and related branches is now done asynchronously - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) + - Project switcher uses new dropdown styling - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) - All images in discussions and wikis now link to their source files !3464 (Connor Shea). @@ -14,7 +14,7 @@ v 8.7.0 (unreleased) - Add setting for customizing the list of trusted proxies !3524 - Allow projects to be transfered to a lower visibility level group - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - - Improved Markdown rendering performance !3389 (Yorick Peterse) + - Improved Markdown rendering performance !3389 - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - Expose project badges in project settings @@ -75,8 +75,9 @@ v 8.7.0 (unreleased) - Fix emoji catgories in the emoji picker v 8.6.6 - - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) - - Project switcher uses new dropdown styling + - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 + - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 + - Fix revoking of authorized OAuth applications (Connor Shea). !3690 v 8.6.5 - Fix importing from GitHub Enterprise. !3529 @@ -276,7 +277,7 @@ v 8.5.1 v 8.5.0 - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - - Cache various Repository methods to improve performance (Yorick Peterse) + - Cache various Repository methods to improve performance - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) - Ensure rake tasks that don't need a DB connection can be run without one - Update New Relic gem to 3.14.1.311 (Stan Hu) From a0a423fee76d8cbc50cdb2478b05ccb751bc2be8 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Fri, 15 Apr 2016 18:18:31 +0100 Subject: [PATCH 212/573] fix failing tests --- app/assets/javascripts/issues.js.coffee | 27 ++++++++++++++++--- app/helpers/application_helper.rb | 9 ++++++- app/views/shared/issuable/_nav.html.haml | 10 +++---- spec/features/issues/filter_issues_spec.rb | 9 ++++++- .../filter_by_milestone_spec.rb | 6 +++++ 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 320b92d2a6..1148531c06 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -36,19 +36,37 @@ $(".selected_issue").bind "change", Issues.checkChanged + getLabelsQueryString: -> + pageURL = decodeURIComponent(window.location.search.substring(1)) + urlVariables = pageURL.split('&') + labelParams = ( + variables for variables in urlVariables when variables.indexOf('label_name[]') > -1 + ).join('&') + + removeLabelsQueryString: (url) -> + pageURL = decodeURIComponent(url) + urlVariables = pageURL.split('&') + Params = ( + variables for variables in urlVariables when variables.indexOf('label_name[]') is -1 + ).join('&') + # Update state filters if present in page updateStateFilters: -> stateFilters = $('.issues-state-filters') newParams = {} - paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search'] + paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search'] for paramKey in paramKeys newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or '' if stateFilters.length stateFilters.find('a').each -> - initialUrl = $(this).attr 'href' - $(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl) + initialUrl = Issues.removeLabelsQueryString($(this).attr 'href') + if Issues.getLabelsQueryString() + newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{Issues.getLabelsQueryString()}" + else + newUrl = gl.utils.mergeUrlParams(newParams, initialUrl) + $(this).attr 'href', newUrl # Make sure we trigger ajax request only after user stop typing initSearch: -> @@ -91,7 +109,8 @@ opacity: 0 } }).then( -> - $filteredLabels.html(Issue.labelRow(data)) + if typeof Issue.labelRow is 'function' + $filteredLabels.html(Issue.labelRow(data)) $spans = $filteredLabels.find('span') $spans.css('opacity',0) return gl.animate.animateEach($spans, 'fadeInUp', 20, { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 16e5b8ac22..3e0074da39 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -254,11 +254,11 @@ module ApplicationHelper def page_filter_path(options = {}) without = options.delete(:without) + add_label = options.delete(:label) exist_opts = { state: params[:state], scope: params[:scope], - label_name: params[:label_name], milestone_title: params[:milestone_title], assignee_id: params[:assignee_id], author_id: params[:author_id], @@ -275,6 +275,13 @@ module ApplicationHelper path = request.path path << "?#{options.to_param}" + if add_label + if params[:label_name].present? and params[:label_name].respond_to?('any?') + params[:label_name].each do |label| + path << "&label_name[]=#{label}" + end + end + end path end diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index a6970b7eeb..1d9b09a5ef 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -4,22 +4,22 @@ - else - page_context_word = 'issues' %li{class: ("active" if params[:state] == 'opened')} - = link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do + = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do #{state_filters_text_for(:opened, @project)} - if defined?(type) && type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} - = link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do + = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do #{state_filters_text_for(:merged, @project)} %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do + = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do #{state_filters_text_for(:closed, @project)} - else %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do + = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do #{state_filters_text_for(:closed, @project)} %li{class: ("active" if params[:state] == 'all')} - = link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do + = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do #{state_filters_text_for(:all, @project)} diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 69b22232f1..192e361937 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -84,14 +84,20 @@ describe 'Filter issues', feature: true do it 'should filter by any label' do find('.dropdown-menu-labels a', text: 'Any Label').click + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + page.within '.labels-filter' do expect(page).to have_content 'Any Label' end - expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Label') + expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Any Label') end it 'should filter by no label' do find('.dropdown-menu-labels a', text: 'No Label').click + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + page.within '.labels-filter' do expect(page).to have_content 'No Label' end @@ -121,6 +127,7 @@ describe 'Filter issues', feature: true do find('.js-label-select').click find('.dropdown-menu-labels .dropdown-content a', text: label.title).click + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click sleep 2 end diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index c57ab5f3b0..e3ecd60a5f 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -2,8 +2,14 @@ require 'rails_helper' feature 'Merge Request filtering by Milestone', feature: true do let(:project) { create(:project, :public) } + let!(:user) { create(:user)} let(:milestone) { create(:milestone, project: project) } + before do + project.team << [user, :master] + login_as(user) + end + scenario 'filters by no Milestone', js: true do create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :simple, source_project: project, milestone: milestone) From 0556d661e0027068aa7bee45a5c61017344505f3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 14 Apr 2016 19:47:53 -0500 Subject: [PATCH 213/573] Fix Label dropdown and organize code # Conflicts: # app/assets/javascripts/labels_select.js.coffee --- app/assets/javascripts/gl_dropdown.js.coffee | 7 +++++-- app/assets/stylesheets/framework/dropdowns.scss | 2 +- app/assets/stylesheets/pages/issuable.scss | 4 ++++ app/views/shared/issuable/_label_page_create.html.haml | 4 ++-- app/views/shared/issuable/_sidebar.html.haml | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 2dc37257e2..fa1f765095 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -224,6 +224,9 @@ class GitLabDropdown menu.toggleClass PAGE_TWO_CLASS + # Focus first visible input on active page + @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus() + parseData: (data) -> @renderedData = data @@ -243,7 +246,8 @@ class GitLabDropdown shouldPropagate: (e) => if @options.multiSelect $target = $(e.target) - if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') + + if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and ($target.attr('href') is '#') e.stopPropagation() return false else @@ -378,7 +382,6 @@ class GitLabDropdown selectedObject = @renderedData[selectedIndex] value = if @options.id then @options.id(selectedObject, el) else selectedObject.id field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") - if el.hasClass(ACTIVE_CLASS) el.removeClass(ACTIVE_CLASS) field.remove() diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index ba6c7930cd..719703b1af 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -320,7 +320,7 @@ } } -.dropdown-input-field { +.dropdown-input-field, .default-dropdown-input { width: 100%; padding: 0 7px; color: $dropdown-input-color; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 6bd90a2362..152b60d1e8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -248,6 +248,10 @@ background: $gray-dark; border: 1px solid $border-gray-dark; } + + &.btn-primary { + @extend .btn-primary + } } a:not(.btn) { diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 26ccade425..3bc57d3d2a 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -2,14 +2,14 @@ = dropdown_title("Create new label", back: true) = dropdown_content do .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label" } + %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" } .suggest-colors.suggest-colors-dropdown - suggested_colors.each do |color| = link_to '#', style: "background-color: #{color}", data: { color: color } do   .dropdown-label-color-input .dropdown-label-color-preview.js-dropdown-label-color-preview - %input#new_label_color.dropdown-input-field{ type: "text" } + %input#new_label_color.default-dropdown-input{ type: "text" } .clearfix %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" } Create diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 6108239aed..ef670bc9ba 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -96,7 +96,7 @@ - issuable.labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} + %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text Label = icon('chevron-down') From 2c5ab1b61f3b75298381e3258e303322c58f4808 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 15 Apr 2016 18:24:16 +0100 Subject: [PATCH 214/573] Removes dropzone focus class after dropping file --- app/assets/javascripts/dropzone_input.js.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index 6eb8d27ee2..e2194589b3 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -61,6 +61,7 @@ class @DropzoneInput return drop: -> + $mdArea.removeClass 'is-dropzone-hover' form.find(".div-dropzone-hover").css "opacity", 0 form_textarea.focus() return From 9066ac0da51bd2b74249185c79ca278942a40241 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 15 Apr 2016 13:01:46 -0500 Subject: [PATCH 215/573] Fix create label functionality --- .../javascripts/labels_select.js.coffee | 66 +++++++------------ 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index c1fcd764a6..ddf7d84a38 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -2,14 +2,15 @@ class @LabelsSelect constructor: -> $('.js-label-select').each (i, dropdown) -> $dropdown = $(dropdown) + $parent = $dropdown.parent() projectId = $dropdown.data('project-id') labelUrl = $dropdown.data('labels') issueUpdateURL = $dropdown.data('issueUpdate') selectedLabel = $dropdown.data('selected') if selectedLabel? selectedLabel = selectedLabel.split(',') - newLabelField = $('#new_label_name') - newColorField = $('#new_label_color') + newLabelField = $('#new_label_name', $parent) + newColorField = $('#new_label_color', $parent) showNo = $dropdown.data('show-no') showAny = $dropdown.data('show-any') defaultLabel = $dropdown.data('default-label') @@ -18,6 +19,11 @@ class @LabelsSelect $block = $selectbox.closest('.block') $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span') $value = $block.find('.value') + $newLabelError = $('.js-label-error', $parent) + $colorPreview = $('.js-dropdown-label-color-preview', $parent) + $newLabelCreateButton = $('.js-new-label-btn', $parent) + + $newLabelError.hide() $loading = $block.find('.block-loading').fadeOut() issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL? @@ -33,11 +39,7 @@ class @LabelsSelect ); labelNoneHTMLTemplate = _.template('
None
') - if newLabelField.length and $dropdown.hasClass 'js-extra-options' - $newLabelCreateButton = $('.js-new-label-btn') - $colorPreview = $('.js-dropdown-label-color-preview') - $newLabelError = $dropdown.parent().find('.js-label-error') - $newLabelError.hide() + if newLabelField.length # Suggested colors in the dropdown to chose from pre-chosen colors $('.suggest-colors-dropdown a').on "click", (e) -> @@ -78,26 +80,25 @@ class @LabelsSelect enableLabelCreateButton = -> if newLabelField.val() isnt '' and newColorField.val() isnt '' $newLabelError.hide() - $('.js-new-label-btn').disable() - - # Create new label with API - Api.newLabel projectId, { - name: newLabelField.val() - color: newColorField.val() - }, (label) -> - $('.js-new-label-btn').enable() - - if label.message? - $newLabelError - .text label.message - .show() - else - $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' - $newLabelCreateButton.enable() else $newLabelCreateButton.disable() + saveLabel = -> + # Create new label with API + Api.newLabel projectId, { + name: newLabelField.val() + color: newColorField.val() + }, (label) -> + $newLabelCreateButton.enable() + + if label.message? + $newLabelError + .text label.message + .show() + else + $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' + newLabelField.on 'keyup change', enableLabelCreateButton newColorField.on 'keyup change', enableLabelCreateButton @@ -108,24 +109,7 @@ class @LabelsSelect .on 'click', (e) -> e.preventDefault() e.stopPropagation() - - if newLabelField.val() isnt '' and newColorField.val() isnt '' - $newLabelError.hide() - $('.js-new-label-btn').disable() - - # Create new label with API - Api.newLabel projectId, { - name: newLabelField.val() - color: newColorField.val() - }, (label) -> - $('.js-new-label-btn').enable() - - if label.message? - $newLabelError - .text label.message - .show() - else - $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' + saveLabel() saveLabelData = -> selected = $dropdown From 6c29e1ef69d4ee7e656d0b3b203cf43ff68c637b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 15 Apr 2016 14:25:36 -0500 Subject: [PATCH 216/573] Add tests to check if a new label is added --- spec/features/issues/issue_sidebar_spec.rb | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 spec/features/issues/issue_sidebar_spec.rb diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb new file mode 100644 index 0000000000..5739bc64df --- /dev/null +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -0,0 +1,79 @@ +require 'rails_helper' + +feature 'Issue Sidebar', feature: true do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + let!(:user) { create(:user)} + + before do + create(:label, project: project, title: 'bug') + login_as(user) + end + + context 'as a allowed user' do + before do + project.team << [user, :developer] + visit_issue(project, issue) + end + + describe 'when clicking on edit labels', js: true do + it 'dropdown has an option to create a new label' do + find('.block.labels .edit-link').click + + page.within('.block.labels') do + expect(page).to have_content 'Create new' + end + end + end + + context 'creating a new label', js: true do + it 'option to crate a new label is present' do + page.within('.block.labels') do + find('.edit-link').click + + expect(page).to have_content 'Create new' + end + end + + it 'dropdown switches to "create label" section' do + page.within('.block.labels') do + find('.edit-link').click + click_link 'Create new' + + expect(page).to have_content 'Create new label' + end + end + + it 'new label is added' do + page.within('.block.labels') do + find('.edit-link').click + sleep 1 + click_link 'Create new' + + fill_in 'new_label_name', with: 'wontfix' + page.find(".suggest-colors a", match: :first).click + click_button 'Create' + + page.within('.dropdown-page-one') do + expect(page).to have_content 'wontfix' + end + end + end + end + end + + context 'as a guest' do + before do + project.team << [user, :guest] + visit_issue(project, issue) + end + + it 'does not have a option to edit labels' do + expect(page).not_to have_selector('.block.labels .edit-link') + end + end + + def visit_issue(project, issue) + visit namespace_project_issue_path(project.namespace, project, issue) + end +end From f2d94c5d8627adee985e605a18cf147003ef16de Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Fri, 15 Apr 2016 16:05:37 -0400 Subject: [PATCH 217/573] Scroll to the last comment I made and edit it. --- app/assets/javascripts/notes.js.coffee | 27 +++++++++++++++++++++--- app/views/projects/notes/_note.html.haml | 9 ++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index fa91baa07c..a3d0d212be 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -75,6 +75,9 @@ class @Notes # when issue status changes, we need to refresh data $(document).on "issuable:change", @refresh + # when a key is clicked on the notes + $(document).on "keydown", ".js-note-text", @keydownNoteText + cleanBinding: -> $(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-discussion-note-form" @@ -92,10 +95,19 @@ class @Notes $(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-close" $(document).off "click", ".js-note-discard" + $(document).off "keydown", ".js-note-text" $('.note .js-task-list-container').taskList('disable') $(document).off 'tasklist:changed', '.note .js-task-list-container' + keydownNoteText: (e) -> + $this = $(this) + if $this.val() is '' and e.which is 38 #aka the up key + myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") + if myLastNote.length + myLastNoteEditBtn = myLastNote.find('.js-note-edit') + myLastNoteEditBtn.trigger('click', [true, myLastNote]) + initRefresh: -> clearInterval(Notes.interval) Notes.interval = setInterval => @@ -343,7 +355,7 @@ class @Notes Adds a hidden div with the original content of the note to fill the edit note form with if the user cancels ### - showEditForm: (e) -> + showEditForm: (e, scrollTo, myLastNote) -> e.preventDefault() note = $(this).closest(".note") note.addClass "is-editting" @@ -355,8 +367,17 @@ class @Notes note.find(".js-note-attachment-delete").show() new GLForm form - - form.find(".js-note-text").focus() + if scrollTo? and myLastNote? + # scroll to the bottom + # so the open of the last element doesn't make a jump + $('html, body').scrollTop($(document).height()); + $('html, body').animate({ + scrollTop: myLastNote.offset().top - 150 + }, 500, -> + form.find(".js-note-text").focus() + ); + else + form.find(".js-note-text").focus() ### Called in response to clicking the edit note link diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 03a44ca99c..6e9ecdf764 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,4 +1,5 @@ -%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] } +- note_editable = note_editable?(note) +%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} } .timeline-entry-inner .timeline-icon %a{href: user_path(note.author)} @@ -15,16 +16,16 @@ - if access %span.note-role = access - - if note_editable?(note) + - if note_editable = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = icon('pencil') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do = icon('trash-o') - .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} + .note-body{class: note_editable ? 'js-task-list-container' : ''} .note-text = preserve do = markdown(note.note, pipeline: :note, cache_key: [note, "note"]) - - if note_editable?(note) + - if note_editable = render 'projects/notes/edit_form', note: note = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) From 262ca7b651037e02d0de662b6462fe4edfa77e81 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Fri, 15 Apr 2016 16:36:25 -0400 Subject: [PATCH 218/573] When editing put the cursor at the end of the textarea --- app/assets/javascripts/notes.js.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index a3d0d212be..c2e103ff85 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -374,7 +374,11 @@ class @Notes $('html, body').animate({ scrollTop: myLastNote.offset().top - 150 }, 500, -> - form.find(".js-note-text").focus() + $noteText = form.find(".js-note-text") + $noteText.focus() + # Neat little trick to put the cursor at the end + noteTextVal = $noteText.val() + $noteText.val('').val(noteTextVal); ); else form.find(".js-note-text").focus() From 47c6111546c32ec0b1ec4f1720e0e29c455cb438 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 15 Apr 2016 17:17:48 -0400 Subject: [PATCH 219/573] Remove extra spacing from merge request target branch name --- app/views/projects/merge_requests/_show.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 07037a14f5..2c34f9c454 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -32,8 +32,7 @@ %span Request to merge %span.label-branch= source_branch_with_namespace(@merge_request) %span into - = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do - = @merge_request.target_branch + = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" - if @merge_request.open? && @merge_request.diverged_from_target_branch? %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) From 0fcdcadd75514fd8f1526bff2f1becb43c6d25f6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 15 Apr 2016 23:00:32 +0100 Subject: [PATCH 220/573] Fixed issue with dropzone not working on new issuable Closes #15295 --- app/views/shared/issuable/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index aed2622a6d..bae15b7f84 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -4,7 +4,7 @@ = f.label :title, class: 'control-label' .col-sm-10 = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off', - class: 'form-control pad js-gfm-input', required: true + class: 'form-control pad', required: true - if issuable.is_a?(MergeRequest) %p.help-block From eceada9a6868d16d9a11ce8bc646623f20f58c95 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Apr 2016 09:43:49 +0100 Subject: [PATCH 221/573] Fix issue with go full screen triggering attach file --- app/views/projects/_md_preview.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 7a78d61a61..8de44a6c91 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -1,6 +1,6 @@ .md-area .md-header - %ul.nav-links + %ul.nav-links.clearfix %li.active %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 } Write From d744a04a5b4b46d970cf3c6c4030fc61718c59c0 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Sat, 16 Apr 2016 09:23:25 +0000 Subject: [PATCH 222/573] fix TYPO in the changelog --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c948e8e546..f1320b6a28 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -72,7 +72,7 @@ v 8.7.0 (unreleased) - Delete tags using Rugged for performance reasons (Robert Schilling) - Diffs load at the correct point when linking from from number - Selected diff rows highlight - - Fix emoji catgories in the emoji picker + - Fix emoji categories in the emoji picker - Add encrypted credentials for imported projects and migrate old ones v 8.6.6 From 562c9652d687bb03d0aa5b744016946d7a293a82 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 29 Mar 2016 18:39:25 -0500 Subject: [PATCH 223/573] Put owner and participating people first --- app/services/projects/participants_service.rb | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 0004a399f4..ba88ba73c3 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -1,6 +1,7 @@ module Projects class ParticipantsService < BaseService def execute(note_type, note_id) + @target = get_target(note_type, note_id) participating = if note_type && note_id participants_in(note_type, note_id) @@ -8,35 +9,43 @@ module Projects [] end project_members = sorted(project.team.members) - participants = all_members + groups + project_members + participating + participants = target_owner + participating + all_members + groups + project_members participants.uniq end - def participants_in(type, id) - target = - case type - when "Issue" - project.issues.find_by_iid(id) - when "MergeRequest" - project.merge_requests.find_by_iid(id) - when "Commit" - project.commit(id) - end - - return [] unless target + def get_target(type, id) + case type + when "Issue" + project.issues.find_by_iid(id) + when "MergeRequest" + project.merge_requests.find_by_iid(id) + when "Commit" + project.commit(id) + end + end - users = target.participants(current_user) + def target_owner + [{ + name: @target.author.name, + username: @target.author.username + }] + end + + def participants_in(type, id) + return [] unless @target + + users = @target.participants(current_user) sorted(users) end def sorted(users) - users.uniq.to_a.compact.sort_by(&:username).map do |user| + users.uniq.to_a.compact.sort_by(&:username).map do |user| { username: user.username, name: user.name } end end def groups - current_user.authorized_groups.sort_by(&:path).map do |group| + current_user.authorized_groups.sort_by(&:path).map do |group| count = group.users.count { username: group.path, name: group.name, count: count } end From ad48ecacaef508139117ceaea1a9eeaeb7046b48 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 29 Mar 2016 18:53:01 -0500 Subject: [PATCH 224/573] Update method name and remove unneeded params --- app/services/projects/participants_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index ba88ba73c3..800747b7ef 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -4,7 +4,7 @@ module Projects @target = get_target(note_type, note_id) participating = if note_type && note_id - participants_in(note_type, note_id) + participants_in_target else [] end @@ -31,7 +31,7 @@ module Projects }] end - def participants_in(type, id) + def participants_in_target return [] unless @target users = @target.participants(current_user) From 16459fddd4857bc18a917e13989b755468cfd7e8 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 30 Mar 2016 15:19:56 -0500 Subject: [PATCH 225/573] Fixes multiple ajax request and incorrect data being set for the current issuable --- .../javascripts/gfm_auto_complete.js.coffee | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 4718bcf7a1..61e3f811e7 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -2,6 +2,8 @@ window.GitLab ?= {} GitLab.GfmAutoComplete = + dataLoading: false + dataSource: '' # Emoji @@ -17,17 +19,41 @@ GitLab.GfmAutoComplete = template: '
  • ${id} ${title}
  • ' # Add GFM auto-completion to all input fields, that accept GFM input. - setup: -> - input = $('.js-gfm-input') + setup: (wrap) -> + @input = $('.js-gfm-input') + # destroy previous instances + @destroyAtWho() + + # set up instances + @setupAtWho() + + if @dataSource + if !@dataLoading + @dataLoading = true + + # We should wait until initializations are done + # and only trigger the last .setup since + # The previous .dataSource belongs to the previous issuable + # and the last one will have the **proper** .dataSource property + # TODO: Make this a singleton and turn off events when moving to another page + setTimeout( => + fetch = @fetchData(@dataSource) + fetch.done (data) => + @dataLoading = false + @loadData(data) + , 1000) + + + setupAtWho: -> # Emoji - input.atwho + @input.atwho at: ':' displayTpl: @Emoji.template insertTpl: ':${name}:' # Team Members - input.atwho + @input.atwho at: '@' displayTpl: @Members.template insertTpl: '${atwho-at}${username}' @@ -42,7 +68,7 @@ GitLab.GfmAutoComplete = title: sanitize(title) search: sanitize("#{m.username} #{m.name}") - input.atwho + @input.atwho at: '#' alias: 'issues' searchKey: 'search' @@ -55,7 +81,7 @@ GitLab.GfmAutoComplete = title: sanitize(i.title) search: "#{i.iid} #{i.title}" - input.atwho + @input.atwho at: '!' alias: 'mergerequests' searchKey: 'search' @@ -68,13 +94,18 @@ GitLab.GfmAutoComplete = title: sanitize(m.title) search: "#{m.iid} #{m.title}" - if @dataSource - $.getJSON(@dataSource).done (data) -> - # load members - input.atwho 'load', '@', data.members - # load issues - input.atwho 'load', 'issues', data.issues - # load merge requests - input.atwho 'load', 'mergerequests', data.mergerequests - # load emojis - input.atwho 'load', ':', data.emojis + destroyAtWho: -> + @input.atwho('destroy') + + fetchData: (dataSource) -> + $.getJSON(dataSource) + + loadData: (data) -> + # load members + @input.atwho 'load', '@', data.members + # load issues + @input.atwho 'load', 'issues', data.issues + # load merge requests + @input.atwho 'load', 'mergerequests', data.mergerequests + # load emojis + @input.atwho 'load', ':', data.emojis From 05628e0c2ffc5a3378ad4d89a212b1bd652a9d60 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 30 Mar 2016 16:29:42 -0500 Subject: [PATCH 226/573] Fixes failing spec --- app/services/projects/participants_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 800747b7ef..1eaecc0d27 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -25,6 +25,8 @@ module Projects end def target_owner + return [] unless @target && @target.author.present? + [{ name: @target.author.name, username: @target.author.username From 1eeabdc6a5c5ad751891d725a0957a24282f7a9c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 8 Apr 2016 12:23:44 -0500 Subject: [PATCH 227/573] Change variable name --- app/services/projects/participants_service.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 1eaecc0d27..11dc3542fa 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -1,9 +1,9 @@ module Projects class ParticipantsService < BaseService - def execute(note_type, note_id) - @target = get_target(note_type, note_id) + def execute(noteable_type, noteable_id) + @target = get_target(noteable_type, noteable_id) participating = - if note_type && note_id + if noteable_type && noteable_id participants_in_target else [] From a96dc944289e01f7b41112343cf20ca2791860ca Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 8 Apr 2016 13:36:55 -0500 Subject: [PATCH 228/573] Memoize target --- app/services/projects/participants_service.rb | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 11dc3542fa..02c4eee3d0 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -1,42 +1,40 @@ module Projects class ParticipantsService < BaseService def execute(noteable_type, noteable_id) - @target = get_target(noteable_type, noteable_id) - participating = - if noteable_type && noteable_id - participants_in_target - else - [] - end + @noteable_type = noteable_type + @noteable_id = noteable_id project_members = sorted(project.team.members) - participants = target_owner + participating + all_members + groups + project_members + participants = target_owner + participants_in_target + all_members + groups + project_members participants.uniq end - def get_target(type, id) - case type - when "Issue" - project.issues.find_by_iid(id) - when "MergeRequest" - project.merge_requests.find_by_iid(id) - when "Commit" - project.commit(id) - end + def target + @target ||= + case @noteable_type + when "Issue" + project.issues.find_by_iid(@noteable_id) + when "MergeRequest" + project.merge_requests.find_by_iid(@noteable_id) + when "Commit" + project.commit(@noteable_id) + else + nil + end end def target_owner - return [] unless @target && @target.author.present? + return [] unless target && target.author.present? [{ - name: @target.author.name, - username: @target.author.username + name: target.author.name, + username: target.author.username }] end def participants_in_target - return [] unless @target + return [] unless target - users = @target.participants(current_user) + users = target.participants(current_user) sorted(users) end From 42848f2684c093c114abf20dd5d73a195ec6a451 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 8 Apr 2016 13:45:24 -0500 Subject: [PATCH 229/573] Update CHANGELOG --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f1320b6a28..7754c3fb9f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -79,6 +79,9 @@ v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 - Fix revoking of authorized OAuth applications (Connor Shea). !3690 + - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) + - Project switcher uses new dropdown styling + - Author and participants are displayed first on users autocompletion v 8.6.5 - Fix importing from GitHub Enterprise. !3529 From a740f0bc99a67d25ed156353055de3b4fe0064a6 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 8 Apr 2016 19:35:43 -0500 Subject: [PATCH 230/573] Add tests for autocomplete on a Issue --- .../participants_autocomplete_spec.rb | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 spec/features/participants_autocomplete_spec.rb diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb new file mode 100644 index 0000000000..f966fcbbfa --- /dev/null +++ b/spec/features/participants_autocomplete_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +feature 'Member autocomplete', feature: true do + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + let(:participant) { create(:user) } + let(:author) { create(:user) } + let(:issue) { create(:issue, author: author, project: project) } + + before do + login_as user + end + + describe 'On a Issue', js: true do + before do + create(:note, note: 'ultralight beam', noteable: issue, author: participant) + visit_issue(project, issue) + end + + describe 'adding a new note' do + describe 'when typing @' do + + before do + sleep 1 + page.within('.new-note') do + sleep 1 + find('#note_note').native.send_keys('@') + end + end + + it 'suggestions are displayed' do + expect(page).to have_selector('.atwho-view', visible: true) + end + + it 'author is a suggestion' do + page.within('.atwho-view', visible: true) do + expect(page).to have_content(author.username) + end + end + + it 'participant is a suggestion' do + page.within('.atwho-view', visible: true) do + expect(page).to have_content(participant.username) + end + end + end + end + end + + def visit_issue(project, issue) + visit namespace_project_issue_path(project.namespace, project, issue) + end +end From 40efb4eb1fb3a1863f16488c7a468d4ca140500a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sat, 9 Apr 2016 01:34:07 -0500 Subject: [PATCH 231/573] Add tests for autocomplete on a Merge Request --- .../participants_autocomplete_spec.rb | 81 ++++++++++++------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index f966fcbbfa..5f1b5fd6b5 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -5,49 +5,72 @@ feature 'Member autocomplete', feature: true do let(:user) { create(:user) } let(:participant) { create(:user) } let(:author) { create(:user) } - let(:issue) { create(:issue, author: author, project: project) } before do login_as user end - describe 'On a Issue', js: true do + shared_examples "open suggestions" do + it 'suggestions are displayed' do + expect(page).to have_selector('.atwho-view', visible: true) + end + + it 'author is suggested' do + page.within('.atwho-view', visible: true) do + expect(page).to have_content(author.username) + end + end + + it 'participant is suggested' do + page.within('.atwho-view', visible: true) do + expect(page).to have_content(participant.username) + end + end + end + + context 'On a Issue adding a new note', js: true do before do - create(:note, note: 'ultralight beam', noteable: issue, author: participant) + issue = create(:issue, author: author, project: project) + create(:note, note: 'Ultralight Beam', noteable: issue, author: participant) visit_issue(project, issue) end - describe 'adding a new note' do - describe 'when typing @' do - - before do - sleep 1 - page.within('.new-note') do - sleep 1 - find('#note_note').native.send_keys('@') - end - end - - it 'suggestions are displayed' do - expect(page).to have_selector('.atwho-view', visible: true) - end - - it 'author is a suggestion' do - page.within('.atwho-view', visible: true) do - expect(page).to have_content(author.username) - end - end - - it 'participant is a suggestion' do - page.within('.atwho-view', visible: true) do - expect(page).to have_content(participant.username) - end - end + context 'when typing @' do + include_examples "open suggestions" + before do + open_member_suggestions end end end + context 'On a Merge Request adding a new note', js: true do + before do + merge = create(:merge_request, source_project: project, target_project: project, author: author) + create(:note, note: 'Ultralight Beam', noteable: merge, author: participant) + visit_merge_request(project, merge) + end + + context 'when typing @' do + include_examples "open suggestions" + before do + open_member_suggestions + end + end + end + + def open_member_suggestions + sleep 1 + page.within('.new-note') do + sleep 1 + find('#note_note').native.send_keys('@') + end + end + def visit_issue(project, issue) visit namespace_project_issue_path(project.namespace, project, issue) end + + def visit_merge_request(project, merge) + visit namespace_project_merge_request_path(project.namespace, project, merge) + end end From a53e912bc0578febf87f722887a100f2c6e4670f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 14:02:00 -0500 Subject: [PATCH 232/573] Change context description --- spec/features/participants_autocomplete_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index 5f1b5fd6b5..b566a036a4 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -28,7 +28,7 @@ feature 'Member autocomplete', feature: true do end end - context 'On a Issue adding a new note', js: true do + context 'adding a new note on a Issue', js: true do before do issue = create(:issue, author: author, project: project) create(:note, note: 'Ultralight Beam', noteable: issue, author: participant) @@ -43,7 +43,7 @@ feature 'Member autocomplete', feature: true do end end - context 'On a Merge Request adding a new note', js: true do + context 'adding a new note on a Merge Request ', js: true do before do merge = create(:merge_request, source_project: project, target_project: project, author: author) create(:note, note: 'Ultralight Beam', noteable: merge, author: participant) From 18b4291181ba126a21137320a9f7b7f503c1d776 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 13 Apr 2016 13:50:17 -0500 Subject: [PATCH 233/573] Add tests for autocomplete on a Commit --- .../participants_autocomplete_spec.rb | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index b566a036a4..1adab7e9c6 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -7,6 +7,7 @@ feature 'Member autocomplete', feature: true do let(:author) { create(:user) } before do + allow_any_instance_of(Commit).to receive(:author).and_return(author) login_as user end @@ -58,6 +59,23 @@ feature 'Member autocomplete', feature: true do end end + context 'adding a new note on a Commit ', js: true do + let(:commit) { project.commit } + + before do + allow(commit).to receive(:author).and_return(author) + create(:note_on_commit, author: participant, project: project, commit_id: project.repository.commit.id, note: 'No More Parties in LA') + visit_commit(project, commit) + end + + context 'when typing @' do + include_examples "open suggestions" + before do + open_member_suggestions + end + end + end + def open_member_suggestions sleep 1 page.within('.new-note') do @@ -73,4 +91,8 @@ feature 'Member autocomplete', feature: true do def visit_merge_request(project, merge) visit namespace_project_merge_request_path(project.namespace, project, merge) end + + def visit_commit(project, commit) + visit namespace_project_commit_path(project.namespace, project, commit) + end end From 6e00c3635bfdf10ef2ada7a5b8af3fbc88f7f58a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 14 Apr 2016 12:19:33 -0500 Subject: [PATCH 234/573] Update CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7754c3fb9f..bc523533ee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -74,6 +74,7 @@ v 8.7.0 (unreleased) - Selected diff rows highlight - Fix emoji categories in the emoji picker - Add encrypted credentials for imported projects and migrate old ones + - Author and participants are displayed first on users autocompletion v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 @@ -81,7 +82,6 @@ v 8.6.6 - Fix revoking of authorized OAuth applications (Connor Shea). !3690 - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) - Project switcher uses new dropdown styling - - Author and participants are displayed first on users autocompletion v 8.6.5 - Fix importing from GitHub Enterprise. !3529 From 5ddda30c353569d3d0af52c0112be4e66f890586 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Sat, 16 Apr 2016 08:59:56 -0400 Subject: [PATCH 235/573] Remove errors from CHANGELOG --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bc523533ee..5c375fcdb3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -80,8 +80,6 @@ v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 - Fix revoking of authorized OAuth applications (Connor Shea). !3690 - - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) - - Project switcher uses new dropdown styling v 8.6.5 - Fix importing from GitHub Enterprise. !3529 From ffd2416c5636917c0da0a3376342d6c465bb1954 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Sat, 16 Apr 2016 09:28:04 -0400 Subject: [PATCH 236/573] Make a user available for the user variable --- app/controllers/help_controller.rb | 6 ++++++ app/views/help/ui.html.haml | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 5505061547..406142f29c 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -51,6 +51,12 @@ class HelpController < ApplicationController end def ui + # this will work on gitlab.com + @some_user = User.find_by(username: 'dzaporozhets') + if @some_user.nil? + # this will work in dev + @some_user = User.find(1) + end end private diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index d084559abc..64414f5d8b 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -345,11 +345,11 @@ %ul %li %a.dropdown-menu-user-link.is-active{href: "#"} - = link_to_member_avatar(current_user, size: 30) + = link_to_member_avatar(@some_user, size: 30) %strong.dropdown-menu-user-full-name - = current_user.name + = @some_user.name .dropdown-menu-user-username - = current_user.to_reference + = @some_user.to_reference .example %div @@ -372,11 +372,11 @@ %ul %li %a.dropdown-menu-user-link.is-active{href: "#"} - = link_to_member_avatar(current_user, size: 30) + = link_to_member_avatar(@some_user, size: 30) %strong.dropdown-menu-user-full-name - = current_user.name + = @some_user.name .dropdown-menu-user-username - = current_user.to_reference + = @some_user.to_reference .dropdown-page-two .dropdown-title %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}} From b340b59743e8cd47fc1f4fa2020b400d82bfd86e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 18:46:18 +0200 Subject: [PATCH 237/573] Implement finally_script which allows to do cleanups as part of the build process --- lib/ci/gitlab_ci_yaml_processor.rb | 10 +++++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 29 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index b7209c1414..be2462949f 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -4,12 +4,12 @@ module Ci DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' - ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache] + ALLOWED_YAML_KEYS = [:before_script, :finally_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache, :dependencies] - attr_reader :before_script, :image, :services, :variables, :path, :cache + attr_reader :before_script, :finally_script, :image, :services, :variables, :path, :cache def initialize(config, path = nil) @config = YAML.safe_load(config, [Symbol], [], true) @@ -44,6 +44,7 @@ module Ci def initial_parsing @before_script = @config[:before_script] || [] + @finally_script = @config[:finally_script] @image = @config[:image] @services = @config[:services] @stages = @config[:stages] || @config[:types] @@ -85,6 +86,7 @@ module Ci artifacts: job[:artifacts], cache: job[:cache] || @cache, dependencies: job[:dependencies], + finally_script: @finally_script, }.compact } end @@ -102,6 +104,10 @@ module Ci raise ValidationError, "before_script should be an array of strings" end + unless @finally_script.nil? || validate_array_of_strings(@finally_script) + raise ValidationError, "finally_script should be an array of strings" + end + unless @image.nil? || @image.is_a?(String) raise ValidationError, "image should be a string" end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index dcb8a3451b..8e373ae55b 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -286,6 +286,28 @@ module Ci end end + + describe "Scripts handling" do + let(:config_data) { YAML.dump(config) } + let(:config_processor) { GitlabCiYamlProcessor.new(config_data, path) } + + subject { config_processor.builds_for_stage_and_ref("test", "master").first } + + describe "finally_script" do + context "in global context" do + let(:config) { + { + finally_script: ["finally_script"], + test: { script: ["script"] } + } + } + + it "return finally_script in options" do + expect(subject[:options][:finally_script]).to eq(["finally_script"]) + end + end + end + end describe "Image and service handling" do it "returns image and service when defined" do @@ -607,6 +629,13 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings") end + it "returns errors if finally_script parameter is invalid" do + config = YAML.dump({ finally_script: "bundle update", rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config, path) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "finally_script should be an array of strings") + end + it "returns errors if image parameter is invalid" do config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) expect do From 4cc9a02ee033564b62b45977571319d129df465b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 18:49:15 +0200 Subject: [PATCH 238/573] Allow the before_script and finally_script to be overwritten in context of job --- lib/ci/gitlab_ci_yaml_processor.rb | 22 +++--- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 73 +++++++++++++++++++- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index be2462949f..2e5b84a57d 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -7,7 +7,7 @@ module Ci ALLOWED_YAML_KEYS = [:before_script, :finally_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache, - :dependencies] + :dependencies, :before_script, :finally_script] attr_reader :before_script, :finally_script, :image, :services, :variables, :path, :cache @@ -73,7 +73,7 @@ module Ci { stage_idx: stages.index(job[:stage]), stage: job[:stage], - commands: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}", + commands: [job[:before_script] || @before_script, job[:script]].flatten.join("\n"), tag_list: job[:tags] || [], name: name, only: job[:only], @@ -86,19 +86,11 @@ module Ci artifacts: job[:artifacts], cache: job[:cache] || @cache, dependencies: job[:dependencies], - finally_script: @finally_script, + finally_script: job[:finally_script] || @finally_script, }.compact } end - def normalize_script(script) - if script.is_a? Array - script.join("\n") - else - script - end - end - def validate! unless validate_array_of_strings(@before_script) raise ValidationError, "before_script should be an array of strings" @@ -177,6 +169,14 @@ module Ci raise ValidationError, "#{name} job: script should be a string or an array of a strings" end + if job[:before_script] && !validate_array_of_strings(job[:before_script]) + raise ValidationError, "#{name} job: before_script should be an array of strings" + end + + if job[:finally_script] && !validate_array_of_strings(job[:finally_script]) + raise ValidationError, "#{name} job: finally_script should be an array of strings" + end + if job[:image] && !validate_string(job[:image]) raise ValidationError, "#{name} job: image should be a string" end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 8e373ae55b..a6a1a5e3be 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -293,6 +293,46 @@ module Ci subject { config_processor.builds_for_stage_and_ref("test", "master").first } + describe "before_script" do + context "in global context" do + let(:config) { + { + before_script: ["global script"], + test: { script: ["script"] } + } + } + + it "return commands with scripts concencaced" do + expect(subject[:commands]).to eq("global script\nscript") + end + end + + context "overwritten in local context" do + let(:config) { + { + before_script: ["global script"], + test: { before_script: ["local script"], script: ["script"] } + } + } + + it "return commands with scripts concencaced" do + expect(subject[:commands]).to eq("local script\nscript") + end + end + end + + describe "script" do + let(:config) { + { + test: { script: ["script"] } + } + } + + it "return commands with scripts concencaced" do + expect(subject[:commands]).to eq("script") + end + end + describe "finally_script" do context "in global context" do let(:config) { @@ -306,6 +346,19 @@ module Ci expect(subject[:options][:finally_script]).to eq(["finally_script"]) end end + + context "overwritten in local context" do + let(:config) { + { + finally_script: ["local finally_script"], + test: { finally_script: ["local finally_script"], script: ["script"] } + } + } + + it "return finally_script in options" do + expect(subject[:options][:finally_script]).to eq(["local finally_script"]) + end + end end end @@ -558,7 +611,7 @@ module Ci stage_idx: 1, name: :normal_job, only: nil, - commands: "\ntest", + commands: "test", tag_list: [], options: {}, when: "on_success", @@ -585,7 +638,7 @@ EOT stage_idx: 1, name: :job1, only: nil, - commands: "\nexecute-script-for-job", + commands: "execute-script-for-job", tag_list: [], options: {}, when: "on_success", @@ -597,7 +650,7 @@ EOT stage_idx: 1, name: :job2, only: nil, - commands: "\nexecute-script-for-job", + commands: "execute-script-for-job", tag_list: [], options: {}, when: "on_success", @@ -629,6 +682,13 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings") end + it "returns errors if job before_script parameter is not an array of strings" do + config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) + expect do + GitlabCiYamlProcessor.new(config, path) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: before_script should be an array of strings") + end + it "returns errors if finally_script parameter is invalid" do config = YAML.dump({ finally_script: "bundle update", rspec: { script: "test" } }) expect do @@ -636,6 +696,13 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "finally_script should be an array of strings") end + it "returns errors if job finally_script parameter is not an array of strings" do + config = YAML.dump({ rspec: { script: "test", finally_script: [10, "test"] } }) + expect do + GitlabCiYamlProcessor.new(config, path) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: finally_script should be an array of strings") + end + it "returns errors if image parameter is invalid" do config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) expect do From 0b1655e7b2e2aa57cb7ea8401743d709bf246074 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 21:46:26 +0200 Subject: [PATCH 239/573] Rename CiStatus to Statusable --- app/models/ci/commit.rb | 2 +- app/models/commit_status.rb | 8 ++++++-- app/models/concerns/{ci_status.rb => statuseable.rb} | 4 ++-- app/models/project.rb | 2 +- app/views/projects/commit/_ci_commit.html.haml | 2 +- spec/models/commit_spec.rb | 8 ++++++++ .../concerns/statuseable_spec.rb} | 4 ++-- 7 files changed, 21 insertions(+), 9 deletions(-) rename app/models/concerns/{ci_status.rb => statuseable.rb} (97%) rename spec/{lib/ci/status_spec.rb => models/concerns/statuseable_spec.rb} (97%) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index ae30407bca..412ab44aaf 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,7 +19,7 @@ module Ci class Commit < ActiveRecord::Base extend Ci::Model - include CiStatus + include Statuseable belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id has_many :statuses, class_name: 'CommitStatus' diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 66eb5dcecf..06d296eef0 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -33,7 +33,7 @@ # class CommitStatus < ActiveRecord::Base - include CiStatus + include Statuseable self.table_name = 'ci_builds' @@ -81,7 +81,11 @@ class CommitStatus < ActiveRecord::Base end end - delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false + delegate :sha, :short_sha, to: :commit + + def before_sha + commit.before_sha || Gitlab::Git::BLANK_SHA + end def self.stages order_by = 'max(stage_idx)' diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/statuseable.rb similarity index 97% rename from app/models/concerns/ci_status.rb rename to app/models/concerns/statuseable.rb index 8190b2a20c..f34dca2912 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/statuseable.rb @@ -1,4 +1,4 @@ -module CiStatus +module Statuseable extend ActiveSupport::Concern AVAILABLE_STATUSES = %w(pending running success failed canceled skipped) @@ -41,7 +41,7 @@ module CiStatus end def finished_at - all.minimum(:finished_at) + all.maximum(:finished_at) end end diff --git a/app/models/project.rb b/app/models/project.rb index 95eb7c51b8..3a55e6c5dd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -958,7 +958,7 @@ class Project < ActiveRecord::Base end def ci_commit(sha, ref) - ci_commits.find_by(sha: sha, ref: ref) + ci_commits.order(id: :desc).find_by(sha: sha, ref: ref) end def ensure_ci_commit(sha, ref) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 25714e6cb4..0c01f2ed95 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,6 +1,6 @@ .gray-content-block.middle-block .pull-right - - if can?(current_user, :update_build, @project) + - if can?(current_user, :update_build, @ci_commit.project) - if ci_commit.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 0e9111c802..ad47e338a3 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -163,4 +163,12 @@ eos it { expect(commit.reverts_commit?(another_commit)).to be_truthy } end end + + describe '#ci_commits' do + # TODO: kamil + end + + describe '#status' do + # TODO: kamil + end end diff --git a/spec/lib/ci/status_spec.rb b/spec/models/concerns/statuseable_spec.rb similarity index 97% rename from spec/lib/ci/status_spec.rb rename to spec/models/concerns/statuseable_spec.rb index 886b82a7af..dacbd3034c 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/models/concerns/statuseable_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe CiStatus do +describe Statuseable do before do @object = Object.new - @object.extend(CiStatus::ClassMethods) + @object.extend(Statuseable::ClassMethods) end describe '.status' do From dc0d7f1a9b4018541596680c643cc5489fd8e625 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 21:47:10 +0200 Subject: [PATCH 240/573] Revert unneeded changes --- 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 0c01f2ed95..25714e6cb4 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,6 +1,6 @@ .gray-content-block.middle-block .pull-right - - if can?(current_user, :update_build, @ci_commit.project) + - 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, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post From ef9f5579d29ac4b72f463fabc6e0ace10078c009 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Sat, 16 Apr 2016 15:56:19 -0400 Subject: [PATCH 241/573] Fix coding style issues. Dashes to camelCase --- app/assets/javascripts/labels_select.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index e70c3cdd19..83e3062d22 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -203,7 +203,7 @@ class @LabelsSelect renderRow: (label) -> selectedClass = '' if $form.find("input[type='hidden']\ - [name='#{$dropdown.data('field-name')}']\ + [name='#{$dropdown.data('fieldName')}']\ [value='#{this.id(label)}']").length selectedClass = 'is-active' @@ -256,7 +256,7 @@ class @LabelsSelect if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) selectedLabels = $dropdown .closest('form') - .find("input[type='hidden'][name='#{$dropdown.data('field-name')}']") + .find("input[type='hidden'][name='#{$dropdown.data('fieldName')}']") Issues.filterResults( $dropdown.closest('form'), $dropdown.data('fieldName') From e79b0dd762719828203bd48c253a88d876bcf61a Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Sat, 16 Apr 2016 16:00:30 -0400 Subject: [PATCH 242/573] Simplify random user generation. --- app/controllers/help_controller.rb | 7 +------ app/views/help/ui.html.haml | 12 ++++++------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 406142f29c..9b5c43b17e 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -51,12 +51,7 @@ class HelpController < ApplicationController end def ui - # this will work on gitlab.com - @some_user = User.find_by(username: 'dzaporozhets') - if @some_user.nil? - # this will work in dev - @some_user = User.find(1) - end + @user = User.new(id: 0, name: 'John Doe', username: '@johndoe') end private diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 64414f5d8b..f12df5c8ff 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -345,11 +345,11 @@ %ul %li %a.dropdown-menu-user-link.is-active{href: "#"} - = link_to_member_avatar(@some_user, size: 30) + = link_to_member_avatar(@user, size: 30) %strong.dropdown-menu-user-full-name - = @some_user.name + = @user.name .dropdown-menu-user-username - = @some_user.to_reference + = @user.to_reference .example %div @@ -372,11 +372,11 @@ %ul %li %a.dropdown-menu-user-link.is-active{href: "#"} - = link_to_member_avatar(@some_user, size: 30) + = link_to_member_avatar(@user, size: 30) %strong.dropdown-menu-user-full-name - = @some_user.name + = @user.name .dropdown-menu-user-username - = @some_user.to_reference + = @user.to_reference .dropdown-page-two .dropdown-title %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}} From dd0478656d877bea84a313826258f1a35f9b1ab4 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Sat, 16 Apr 2016 16:08:53 -0400 Subject: [PATCH 243/573] Add move cursor to last character for editing comment also. --- app/assets/javascripts/notes.js.coffee | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index c2e103ff85..82e210fed7 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -366,6 +366,11 @@ class @Notes # Show the attachment delete link note.find(".js-note-attachment-delete").show() + done = ($noteText) -> + # Neat little trick to put the cursor at the end + noteTextVal = $noteText.val() + $noteText.val('').val(noteTextVal); + new GLForm form if scrollTo? and myLastNote? # scroll to the bottom @@ -376,12 +381,12 @@ class @Notes }, 500, -> $noteText = form.find(".js-note-text") $noteText.focus() - # Neat little trick to put the cursor at the end - noteTextVal = $noteText.val() - $noteText.val('').val(noteTextVal); + done($noteText) ); else - form.find(".js-note-text").focus() + $noteText = form.find('.js-note-text') + $noteText.focus() + done($noteText) ### Called in response to clicking the edit note link From fd42b0db02f7911c9aa1a1b0656b6547ad2c0841 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Apr 2016 11:55:26 +0100 Subject: [PATCH 244/573] Refactored issue header to work better for mobile This will also work better for when the title makes the buttons drop down a new line Closes #14228 --- .../stylesheets/framework/issue_box.scss | 2 +- app/assets/stylesheets/framework/mobile.scss | 7 -- app/assets/stylesheets/pages/detail_page.scss | 5 - app/assets/stylesheets/pages/issuable.scss | 51 +++++++- app/assets/stylesheets/pages/issues.scss | 40 ------- app/views/projects/issues/show.html.haml | 111 +++++++++--------- 6 files changed, 105 insertions(+), 111 deletions(-) diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 7f7b7c806e..8bfc0d583c 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -5,7 +5,7 @@ */ .status-box { - + /* Extra small devices (phones, less than 768px) */ /* No media query since this is the default in Bootstrap */ padding: 5px 11px; diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 66180f38a4..7eb451c124 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -70,13 +70,6 @@ display: none; } - .issue-details { - .creator, - .page-title .btn-close { - display: none; - } - } - %ul.notes .note-role, .note-actions { display: none; } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 5917f08972..89bdb9ae03 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -16,11 +16,6 @@ .issue_created_ago, .author_link { white-space: nowrap; } - - .issue-meta { - display: inline-block; - line-height: 20px; - } } .detail-page-description { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 6bd90a2362..5bf44c1cdb 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -273,10 +273,6 @@ } } -.btn-default.gutter-toggle { - margin-top: 4px; -} - .detail-page-description { small { color: $gray-darkest; @@ -322,3 +318,50 @@ padding-top: 7px; } } + +.issuable-status-box { + float: none; + display: inline-block; + margin-top: 0; + + @media (max-width: $screen-xs-max) { + position: absolute; + top: 0; + left: 0; + } +} + +.issuable-header { + position: relative; + padding-left: 45px; + padding-right: 45px; + line-height: 35px; + + @media (min-width: $screen-sm-min) { + float: left; + padding-left: 0; + padding-right: 0; + } +} + +.issuable-actions { + padding-top: 10px; + + @media (min-width: $screen-sm-min) { + float: right; + padding-top: 0; + } +} + +.issuable-gutter-toggle { + @media (max-width: $screen-sm-max) { + position: absolute; + top: 0; + right: 0; + } +} + +.issuable-meta { + display: inline-block; + line-height: 18px; +} diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 6a1d28590c..fc9db97132 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -86,41 +86,9 @@ form.edit-issue { @media (max-width: $screen-xs-max) { .issue-btn-group { width: 100%; - margin-top: 5px; - - .btn-group { - width: 100%; - - ul { - width: 100%; - text-align: center; - } - } .btn { width: 100%; - - &:first-child:not(:last-child) { - - } - - &:not(:first-child):not(:last-child) { - margin-top: 10px; - } - - &:last-child:not(:first-child) { - margin-top: 10px; - } - } - } - - .issue { - &:hover .issue-actions { - display: none !important; - } - - .issue-updated-at { - display: none; } } } @@ -133,11 +101,3 @@ form.edit-issue { color: $gl-text-color; margin-left: 52px; } - -.editor-details { - display: block; - - @media (min-width: $screen-sm-min) { - display: inline-block; - } -} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 5fe5ddc081..4f4e6c59d6 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,68 +1,73 @@ - page_title "#{@issue.title} (##{@issue.iid})", "Issues" - page_description @issue.description - page_card_attributes @issue.card_attributes +- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project)) -= render "header_title" +.clearfix.detail-page-header + .issuable-header + .issuable-status-box.status-box.status-box-closed{ class: "#{issue_button_visibility(@issue, false)}" } + = icon('check', class: "hidden-sm hidden-md hidden-lg") + %span.hidden-xs + Closed + .issuable-status-box.status-box.status-box-open{ class: "#{issue_button_visibility(@issue, true)}"} + = icon('circle-o', class: "hidden-sm hidden-md hidden-lg") + %span.hidden-xs Open -.issue - .detail-page-header.issuable-header - .pull-left - .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} - %span.hidden-xs - Closed - %span.hidden-sm.hidden-md.hidden-lg - = icon('check') - .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} - %span.hidden-xs - Open - %span.hidden-sm.hidden-md.hidden-lg - = icon('circle-o') - - %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" } + %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') - .issue-meta + .issuable-meta = confidential_icon(@issue) %strong.identifier - Issue ##{@issue.iid} - %span.creator - opened - .editor-details - .editor-details - = time_ago_with_tooltip(@issue.created_at) - by - %strong - = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs") - %strong - = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", - by_username: true, avatar: false) + Issue #{@issue.to_reference} + opened + = time_ago_with_tooltip(@issue.created_at) + by + %strong + = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs") + = link_to_member(@project, @issue.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") - .pull-right.issue-btn-group + .issuable-actions + .clearfix.issue-btn-group.dropdown + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } + %span.caret + Options + .dropdown-menu.dropdown-menu-align-right.hidden-lg + %ul + - if can?(current_user, :create_issue, @project) + %li + = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' + - if can?(current_user, :update_issue, @issue) + %li + = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: " #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + %li + = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "#{issue_button_visibility(@issue, true)}", title: 'Close issue' + %li + = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do = icon('plus') New issue - if can?(current_user, :update_issue, @issue) - = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' - = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do + = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do = icon('pencil-square-o') Edit - .issue-details.issuable-details - .detail-page-description.content-block - %h2.title - = markdown escape_once(@issue.title), pipeline: :single_line - %div - - if @issue.description.present? - .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} - .wiki - = preserve do - = markdown(@issue.description, cache_key: [@issue, "description"]) - %textarea.hidden.js-task-list-field - = @issue.description - = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') +.issue-details.issuable-details + .detail-page-description.content-block + %h2.title + = markdown escape_once(@issue.title), pipeline: :single_line + - if @issue.description.present? + .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} + .wiki + = preserve do + = markdown(@issue.description, cache_key: [@issue, "description"]) + %textarea.hidden.js-task-list-field + = @issue.description + = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)} // This element is filled in using JavaScript. @@ -70,13 +75,11 @@ #related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)} // This element is filled in using JavaScript. - .content-block.content-block-small - = render 'new_branch' - = render 'votes/votes_block', votable: @issue + .content-block.content-block-small + = render 'new_branch' + = render 'votes/votes_block', votable: @issue - .row - %section.col-md-12 - .issuable-discussion - = render 'projects/issues/discussion' + %section.issuable-discussion + = render 'projects/issues/discussion' = render 'shared/issuable/sidebar', issuable: @issue From 60bb7007c3a165543a1787aa7ee6543a0def2152 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Apr 2016 12:12:13 +0100 Subject: [PATCH 245/573] Merge request header markup now matches issue markup for consistancy --- .../projects/merge_requests/_show.html.haml | 3 +- .../merge_requests/show/_mr_title.html.haml | 74 ++++++++++--------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 2c34f9c454..285ad26316 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,8 +1,7 @@ - page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes - -= render "header_title" +- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project)) - if params[:view] == 'parallel' - fluid_layout true diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index ab4b1f14be..c4e5788325 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,35 +1,43 @@ -.detail-page-header - .status-box{ class: status_box_class(@merge_request) } - %span.hidden-xs - = @merge_request.state_human_name - %span.hidden-sm.hidden-md.hidden-lg - = icon(@merge_request.state_icon_name) - %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" } - = icon('angle-double-left') - .issue-meta - %strong.identifier - %span.hidden-sm.hidden-md.hidden-lg - MR +.clearfix.detail-page-header + .issuable-header + .issuable-status-box.status-box{ class: "#{status_box_class(@merge_request)}" } + = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg") %span.hidden-xs - Merge Request - !#{@merge_request.iid} - %span.creator - opened - .editor-details - = time_ago_with_tooltip(@merge_request.created_at) - by - %strong - = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs") - %strong - = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", - by_username: true, avatar: false) + = @merge_request.state_human_name - .issue-btn-group.pull-right - - if can?(current_user, :update_merge_request, @merge_request) - - if @merge_request.open? - = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request' - = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do - = icon('pencil-square-o') - Edit - - if @merge_request.closed? - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request' + %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } + = icon('angle-double-left') + + .issuable-meta + %strong.identifier + Merge Request #{@merge_request.to_reference} + opened + = time_ago_with_tooltip(@merge_request.created_at) + by + %strong + = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs") + = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", by_username: true, avatar: false) + + - if can?(current_user, :update_merge_request, @merge_request) + .issuable-actions + .clearfix.issue-btn-group.dropdown + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } + %span.caret + Options + .dropdown-menu.dropdown-menu-align-right.hidden-lg + %ul + - if @merge_request.open? + %li + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' + %li + = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request' + - if @merge_request.closed? + %li + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' + - if @merge_request.open? + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'hidden-xs hidden-sm btn btn-nr btn-grouped btn-close', title: 'Close merge request' + = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do + = icon('pencil-square-o') + Edit + - if @merge_request.closed? + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request' From 6c949b2bf1c9ee47fb5b181f44021ad6eeb1cd5a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Apr 2016 12:20:54 +0100 Subject: [PATCH 246/573] Moved meta information into an issuable helper method --- app/helpers/issuables_helper.rb | 11 +++++++++++ app/views/projects/issues/show.html.haml | 9 +-------- .../projects/merge_requests/show/_mr_title.html.haml | 11 ++--------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index b14b8218d0..94e1a4d2ec 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -55,6 +55,17 @@ module IssuablesHelper h(milestone_title.presence || default_label) end + def issuable_meta(issuable, project, text) + output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier" + output << " opened " + output << time_ago_with_tooltip(issuable.created_at) + output << " by " + output << content_tag(:strong) do + author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs") + author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") + end + end + private def sidebar_gutter_collapsed? diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 4f4e6c59d6..e3df2ec924 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -18,14 +18,7 @@ .issuable-meta = confidential_icon(@issue) - %strong.identifier - Issue #{@issue.to_reference} - opened - = time_ago_with_tooltip(@issue.created_at) - by - %strong - = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs") - = link_to_member(@project, @issue.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") + = issuable_meta(@issue, @project, "Issue") .issuable-actions .clearfix.issue-btn-group.dropdown diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index c4e5788325..e78fd2054c 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -7,16 +7,9 @@ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') - + .issuable-meta - %strong.identifier - Merge Request #{@merge_request.to_reference} - opened - = time_ago_with_tooltip(@merge_request.created_at) - by - %strong - = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs") - = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", by_username: true, avatar: false) + = issuable_meta(@merge_request, @project, "Merge Request") - if can?(current_user, :update_merge_request, @merge_request) .issuable-actions From ce2e37d446c113575b137f1a11d56df080d02148 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Apr 2016 13:44:22 +0100 Subject: [PATCH 247/573] Fixed tests --- features/steps/shared/issuable.rb | 2 +- spec/features/issues/move_spec.rb | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index 24b3fb6eac..a58b3cb7e1 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -2,7 +2,7 @@ module SharedIssuable include Spinach::DSL def edit_issuable - find(:css, '.issuable-edit').click + find('.issuable-edit', visible: true).click end step 'project "Community" has "Community issue" open issue' do diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 6fda0c3186..caa7b0ec48 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -42,11 +42,9 @@ feature 'issue move to another project' do expect(current_url).to include project_path(new_project) - page.within('.issue') do - expect(page).to have_content("Text with #{cross_reference}!1") - expect(page).to have_content("Moved from #{cross_reference}#1") - expect(page).to have_content(issue.title) - end + expect(page).to have_content("Text with #{cross_reference}!1") + expect(page).to have_content("Moved from #{cross_reference}#1") + expect(page).to have_content(issue.title) end context 'projects user does not have permission to move issue to exist' do @@ -74,7 +72,7 @@ feature 'issue move to another project' do def edit_issue(issue) visit issue_path(issue) - page.within('.issuable-header') { click_link 'Edit' } + page.within('.issuable-actions') { find('.issuable-edit').click } end def issue_path(issue) From 365f351a6c4679db8e7cda52c899d1ce14200097 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Apr 2016 14:43:42 +0100 Subject: [PATCH 248/573] Alignment with sidebar --- app/assets/stylesheets/pages/detail_page.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 89bdb9ae03..2c2ac903f2 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -1,5 +1,5 @@ .detail-page-header { - padding: 11px 0; + padding: $gl-padding-top 0; border-bottom: 1px solid $border-color; color: #5c5d5e; font-size: 16px; From 4b15a8ed262147ba38fcd2cddfda02fb481c37d1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 12 Apr 2016 16:27:16 +0100 Subject: [PATCH 249/573] Fixed up based on feedback --- app/helpers/issuables_helper.rb | 4 +--- app/views/projects/issues/show.html.haml | 12 ++++++------ .../projects/merge_requests/show/_mr_title.html.haml | 2 +- spec/features/issues/move_spec.rb | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 94e1a4d2ec..e1b5af242f 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -57,9 +57,7 @@ module IssuablesHelper def issuable_meta(issuable, project, text) output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier" - output << " opened " - output << time_ago_with_tooltip(issuable.created_at) - output << " by " + output << " opened #{time_ago_with_tooltip(issuable.created_at, skip_js: true)} by".html_safe output << content_tag(:strong) do author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs") author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index e3df2ec924..e07ac04baf 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -5,11 +5,11 @@ .clearfix.detail-page-header .issuable-header - .issuable-status-box.status-box.status-box-closed{ class: "#{issue_button_visibility(@issue, false)}" } + .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) } = icon('check', class: "hidden-sm hidden-md hidden-lg") %span.hidden-xs Closed - .issuable-status-box.status-box.status-box-open{ class: "#{issue_button_visibility(@issue, true)}"} + .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) } = icon('circle-o', class: "hidden-sm hidden-md hidden-lg") %span.hidden-xs Open @@ -32,9 +32,9 @@ = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' - if can?(current_user, :update_issue, @issue) %li - = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: " #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' %li - = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "#{issue_button_visibility(@issue, true)}", title: 'Close issue' + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' %li = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - if can?(current_user, :create_issue, @project) @@ -42,8 +42,8 @@ = icon('plus') New issue - if can?(current_user, :update_issue, @issue) - = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do = icon('pencil-square-o') Edit diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index e78fd2054c..42a9d463e9 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,6 +1,6 @@ .clearfix.detail-page-header .issuable-header - .issuable-status-box.status-box{ class: "#{status_box_class(@merge_request)}" } + .issuable-status-box.status-box{ class: status_box_class(@merge_request) } = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg") %span.hidden-xs = @merge_request.state_human_name diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index caa7b0ec48..84c8e20eba 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -72,7 +72,7 @@ feature 'issue move to another project' do def edit_issue(issue) visit issue_path(issue) - page.within('.issuable-actions') { find('.issuable-edit').click } + page.within('.issuable-actions') { first(:link, 'Edit').click } end def issue_path(issue) From a716e8eadcb93779f1a89f05c0ed1a6b72a8dc6b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 13 Apr 2016 09:13:47 +0100 Subject: [PATCH 250/573] Added CHANGELOG item --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 5c375fcdb3..d8fbfafb72 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -80,6 +80,10 @@ v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 - Fix revoking of authorized OAuth applications (Connor Shea). !3690 + - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) + - Project switcher uses new dropdown styling + - Issuable header is consistent between issues and merge requests + - Improved spacing in issuable header on mobile v 8.6.5 - Fix importing from GitHub Enterprise. !3529 From 2629ab4472c535416791cfc13b41c7f50d7d343c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 10:14:12 +0100 Subject: [PATCH 251/573] Fixed tests --- app/helpers/issuables_helper.rb | 2 +- app/views/projects/issues/show.html.haml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index e1b5af242f..49b6b79ce3 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -57,7 +57,7 @@ module IssuablesHelper def issuable_meta(issuable, project, text) output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier" - output << " opened #{time_ago_with_tooltip(issuable.created_at, skip_js: true)} by".html_safe + output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe output << content_tag(:strong) do author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs") author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index e07ac04baf..9fce9c3f33 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -62,11 +62,11 @@ = @issue.description = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') - #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)} - // This element is filled in using JavaScript. + #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } } + // This element is filled in using JavaScript. - #related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)} - // This element is filled in using JavaScript. + #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } } + // This element is filled in using JavaScript. .content-block.content-block-small = render 'new_branch' From 84fba4d16248de96dcea1ab7e6a56aae58668175 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 14:04:06 +0100 Subject: [PATCH 252/573] Updated some if statements --- app/views/projects/issues/show.html.haml | 57 ++++++++++--------- .../merge_requests/show/_mr_title.html.haml | 26 ++++----- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 9fce9c3f33..bde80bbb54 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -20,33 +20,34 @@ = confidential_icon(@issue) = issuable_meta(@issue, @project, "Issue") - .issuable-actions - .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } - %span.caret - Options - .dropdown-menu.dropdown-menu-align-right.hidden-lg - %ul - - if can?(current_user, :create_issue, @project) - %li - = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' - - if can?(current_user, :update_issue, @issue) - %li - = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - %li - = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' - %li - = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do - = icon('plus') - New issue - - if can?(current_user, :update_issue, @issue) - = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' - = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do - = icon('pencil-square-o') - Edit + - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue) + .issuable-actions + .clearfix.issue-btn-group.dropdown + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } } + %span.caret + Options + .dropdown-menu.dropdown-menu-align-right.hidden-lg + %ul + - if can?(current_user, :create_issue, @project) + %li + = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' + - if can?(current_user, :update_issue, @issue) + %li + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + %li + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + %li + = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) + - if can?(current_user, :create_issue, @project) + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do + = icon('plus') + New issue + - if can?(current_user, :update_issue, @issue) + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do + = icon('pencil-square-o') + Edit .issue-details.issuable-details @@ -54,7 +55,7 @@ %h2.title = markdown escape_once(@issue.title), pipeline: :single_line - if @issue.description.present? - .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} + .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' } .wiki = preserve do = markdown(@issue.description, cache_key: [@issue, "description"]) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 42a9d463e9..0a99e8c959 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -19,18 +19,14 @@ Options .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul - - if @merge_request.open? - %li - = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' - %li - = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request' - - if @merge_request.closed? - %li - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' - - if @merge_request.open? - = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'hidden-xs hidden-sm btn btn-nr btn-grouped btn-close', title: 'Close merge request' - = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do - = icon('pencil-square-o') - Edit - - if @merge_request.closed? - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request' + %li{ class: issue_button_visibility(@merge_request, true) } + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' + %li{ class: issue_button_visibility(@merge_request, false) } + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' + %li + = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request' + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request' + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request' + = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit", id: 'edit_merge_request' do + = icon('pencil-square-o') + Edit From 1c5b172abb1279a25731d35ee913daa91738606d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 22:43:40 +0200 Subject: [PATCH 253/573] Write specs for this feature --- app/models/ci/commit.rb | 9 ++-- app/models/commit_status.rb | 17 ++++--- app/models/concerns/statuseable.rb | 2 +- lib/api/commit_statuses.rb | 19 +++++--- spec/models/ci/commit_spec.rb | 71 ++++++++++++++++++++++++++++ spec/models/commit_status_spec.rb | 74 ++++++++++++++++++++++++++++++ spec/models/project_spec.rb | 15 +++++- 7 files changed, 188 insertions(+), 19 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 412ab44aaf..f2667e5476 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -26,7 +26,10 @@ module Ci has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + delegate :stages, to: :statuses + validates_presence_of :sha + validates_presence_of :status validate :valid_commit_sha # Invalidate object and save if when touched @@ -40,10 +43,6 @@ module Ci CommitStatus.where(commit: all).stages end - def stages - statuses.stages - end - def project_id project.id end @@ -82,7 +81,7 @@ module Ci def retryable? builds.latest.any? do |build| - build.failed? || build.retryable? + build.failed? && build.retryable? end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 06d296eef0..24a26b4be8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -93,7 +93,10 @@ class CommitStatus < ActiveRecord::Base end def self.stages_status - Hash[group(:stage).pluck(:stage, self.status_sql)] + all.stages.inject({}) do |h, stage| + h[stage] = all.where(stage: stage).status + h + end end def ignored? @@ -101,11 +104,13 @@ class CommitStatus < ActiveRecord::Base end def duration - if started_at && finished_at - finished_at - started_at - elsif started_at - Time.now - started_at - end + duration = + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.now - started_at + end + duration.to_i end def stuck? diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index f34dca2912..7b0c9789fb 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -33,7 +33,7 @@ module Statuseable def duration duration_array = all.map(&:duration).compact - duration_array.reduce(:+).to_i + duration_array.reduce(:+) end def started_at diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 0b52dd8a74..7388ed2f4e 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -50,12 +50,19 @@ module API commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit - ref = params[:ref] || - begin - branches = @project.repository.branch_names_contains(commit.sha) - not_found! 'Reference for commit' if branches.none? - branches.first - end + # Since the CommitStatus is attached to Ci::Commit (in the future Pipeline) + # We need to always have the pipeline object + # To have a valid pipeline object that can be attached to specific MR + # Other CI service needs to send `ref` + # If we don't receive it, we will attach the CommitStatus to + # the first found branch on that commit + + ref = params[:ref] + unless ref + branches = @project.repository.branch_names_contains(commit.sha) + not_found! 'References for commit' if branches.none? + ref = branches.first + end ci_commit = @project.ensure_ci_commit(commit.sha, ref) diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index fb3b61ad7c..aef4f00720 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -27,6 +27,8 @@ describe Ci::Commit, models: true do it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:builds) } it { is_expected.to validate_presence_of :sha } + it { is_expected.to validate_presence_of :status } + it { is_expected.to delegate_method(:stages).to(:statuses) } it { is_expected.to respond_to :git_author_name } it { is_expected.to respond_to :git_author_email } @@ -297,4 +299,73 @@ describe Ci::Commit, models: true do expect(commit.coverage).to be_nil end end + + describe '#retryable?' do + subject { commit.retryable? } + + context 'no failed builds' do + before do + FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'success' + end + + it 'be not retryable' do + is_expected.to be_falsey + end + end + + context 'with failed builds' do + before do + FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'running' + FactoryGirl.create :ci_build, name: "rubocop", commit: commit, status: 'failed' + end + + it 'be retryable' do + is_expected.to be_truthy + end + end + end + + describe '#stages' do + let(:commit2) { FactoryGirl.create :ci_commit, project: project } + subject { CommitStatus.where(commit: [commit, commit2]).stages } + + before do + FactoryGirl.create :ci_build, commit: commit2, stage: 'test', stage_idx: 1 + FactoryGirl.create :ci_build, commit: commit, stage: 'build', stage_idx: 0 + end + + it 'return all stages' do + is_expected.to eq(%w(build test)) + end + end + + describe '#update_state' do + it 'execute update_state after touching object' do + expect(commit).to receive(:update_state).and_return(true) + commit.touch + end + + context 'dependent objects' do + let(:commit_status) { build :commit_status, commit: commit } + + it 'execute update_state after saving dependent object' do + expect(commit).to receive(:update_state).and_return(true) + commit_status.save + end + end + + context 'update state' do + let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: Time.now - 120, finished_at: Time.now - 60 } + + before do + build + end + + [:status, :started_at, :finished_at, :duration].each do |param| + it "update #{param}" do + expect(commit.send(param)).to eq(build.send(param)) + end + end + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 1c64947f1f..31d546820c 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -182,4 +182,78 @@ describe CommitStatus, models: true do is_expected.to eq([@commit1, @commit2]) end end + + describe '#before_sha' do + subject { commit_status.before_sha } + + context 'when no before_sha is set for ci::commit' do + before { commit.before_sha = nil } + + it 'return blank sha' do + is_expected.to eq(Gitlab::Git::BLANK_SHA) + end + end + + context 'for before_sha set for ci::commit' do + let(:value) { '1234' } + before { commit.before_sha = value } + + it 'return the set value' do + is_expected.to eq(value) + end + end + end + + describe '#stages' do + before do + FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'success' + FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'failed' + FactoryGirl.create :commit_status, commit: commit, stage: 'deploy', stage_idx: 2, status: 'running' + FactoryGirl.create :commit_status, commit: commit, stage: 'test', stage_idx: 1, status: 'success' + end + + context 'stages list' do + subject { CommitStatus.where(commit: commit).stages } + + it 'return ordered list of stages' do + is_expected.to eq(%w(build test deploy)) + end + end + + context 'stages with statuses' do + subject { CommitStatus.where(commit: commit).stages_status } + + it 'return list of stages with statuses' do + is_expected.to eq({ + 'build' => 'failed', + 'test' => 'success', + 'deploy' => 'running' + }) + end + end + end + + describe '#branch?' do + subject { commit_status.branch? } + + context 'is not a tag' do + before do + commit_status.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + commit_status.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1688e91ca6..becc743de3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -443,7 +443,20 @@ describe Project, models: true do let(:project) { create :project } let(:commit) { create :ci_commit, project: project, ref: 'master' } - it { expect(project.ci_commit(commit.sha, 'master')).to eq(commit) } + subject { project.ci_commit(commit.sha, 'master') } + + it { is_expected.to eq(commit) } + + context 'return latest' do + let(:commit2) { create :ci_commit, project: project, ref: 'master' } + + before do + commit + commit2 + end + + it { is_expected.to eq(commit2) } + end end describe :builds_enabled do From 259970ca1b3118f3eb71751b33a3a53ff4a1fa59 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Sat, 16 Apr 2016 23:30:31 +0100 Subject: [PATCH 254/573] abstract code for removing or getting a param query string from url --- app/assets/javascripts/issues.js.coffee | 20 +++---------------- .../javascripts/lib/url_utility.js.coffee | 16 +++++++++++++++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 1148531c06..a3d9ce0387 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -36,20 +36,6 @@ $(".selected_issue").bind "change", Issues.checkChanged - getLabelsQueryString: -> - pageURL = decodeURIComponent(window.location.search.substring(1)) - urlVariables = pageURL.split('&') - labelParams = ( - variables for variables in urlVariables when variables.indexOf('label_name[]') > -1 - ).join('&') - - removeLabelsQueryString: (url) -> - pageURL = decodeURIComponent(url) - urlVariables = pageURL.split('&') - Params = ( - variables for variables in urlVariables when variables.indexOf('label_name[]') is -1 - ).join('&') - # Update state filters if present in page updateStateFilters: -> stateFilters = $('.issues-state-filters') @@ -61,9 +47,9 @@ if stateFilters.length stateFilters.find('a').each -> - initialUrl = Issues.removeLabelsQueryString($(this).attr 'href') - if Issues.getLabelsQueryString() - newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{Issues.getLabelsQueryString()}" + initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]') + if gl.utils.getParamQueryString('label_name[]') + newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{gl.utils.getParamQueryString('label_name[]')}" else newUrl = gl.utils.mergeUrlParams(newParams, initialUrl) $(this).attr 'href', newUrl diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/url_utility.js.coffee index abd556e0b4..c2e3c807e5 100644 --- a/app/assets/javascripts/lib/url_utility.js.coffee +++ b/app/assets/javascripts/lib/url_utility.js.coffee @@ -28,4 +28,20 @@ newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}" newUrl + # get parameter query string from url. + w.gl.utils.getParamQueryString = (param) -> + pageURL = decodeURIComponent(window.location.search.substring(1)) + urlVariables = pageURL.split('&') + ( + variables for variables in urlVariables when variables.indexOf(param) > -1 + ).join('&') + + # removes parameter query string from url. returns the modified url + w.gl.utils.removeParamQueryString = (url, param) -> + url = decodeURIComponent(url) + urlVariables = url.split('&') + ( + variables for variables in urlVariables when variables.indexOf(param) is -1 + ).join('&') + ) window From 37973bc1435f78f040d1e3f2c3398a4e2668b567 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sat, 16 Apr 2016 17:56:13 -0600 Subject: [PATCH 255/573] Enable the EmptyRule SCSS Linter. Prevents empty SCSS rules from being included in the SCSS. Also fixes the handful of lints caught by the linter. --- .scss-lint.yml | 2 +- app/assets/stylesheets/framework/files.scss | 4 ---- app/assets/stylesheets/framework/selects.scss | 3 --- app/assets/stylesheets/highlight/monokai.scss | 2 -- app/assets/stylesheets/pages/graph.scss | 3 --- 5 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.scss-lint.yml b/.scss-lint.yml index 835a4a88c4..9bfc18b969 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -65,7 +65,7 @@ linters: # Reports when you have an empty rule set. EmptyRule: - enabled: false + enabled: true # Reports when you have an @extend directive. ExtendDirective: diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 789df42fb6..98556460b5 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -82,10 +82,6 @@ } } - &.blob_file { - - } - &.blob-no-preview { background: #eee; text-shadow: 0 1px 2px #fff; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index b2fab387e1..eae5f062dd 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -121,9 +121,6 @@ } } -.select2-container-multi .select2-choices .select2-search-choice { -} - .select2-drop-active { margin-top: 6px; font-size: 14px; diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 28253d4ccb..80a509a7c1 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -111,8 +111,6 @@ .vg { color: #f8f8f2 } /* Name.Variable.Global */ .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .il { color: #ae81ff } /* Literal.Number.Integer.Long */ - - .gh { } /* Generic Heading & Diff Header */ .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss index 4e5c4ed84b..f7f9a9bb77 100644 --- a/app/assets/stylesheets/pages/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss @@ -18,9 +18,6 @@ } .graphs { - .graph-author-commits-count { - } - .graph-author-email { float: right; color: #777; From fa7d99ebd4cd86134e33f9f79e2b92f50de3af6c Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 16 Apr 2016 10:23:36 +0200 Subject: [PATCH 256/573] API: Present an array of Gitlab::Git::Tag instead of array of rugged tags --- CHANGELOG | 1 + lib/api/tags.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d8fbfafb72..7fa1fce1f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -73,6 +73,7 @@ v 8.7.0 (unreleased) - Diffs load at the correct point when linking from from number - Selected diff rows highlight - Fix emoji categories in the emoji picker + - API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling) - Add encrypted credentials for imported projects and migrate old ones - Author and participants are displayed first on users autocompletion diff --git a/lib/api/tags.rb b/lib/api/tags.rb index d1a10479e4..3e1ed3fe5c 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -12,7 +12,7 @@ module API # Example Request: # GET /projects/:id/repository/tags get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, + present user_project.repository.tags.sort_by(&:name).reverse, with: Entities::RepoTag, project: user_project end From 0be158a0725c254629e7e4e5de194021322e2fa9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sun, 17 Apr 2016 12:15:45 +0100 Subject: [PATCH 257/573] Added upload file test to new issue form --- spec/features/issues_spec.rb | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 1ce0024e93..35c8f93abc 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -292,6 +292,23 @@ describe 'Issues', feature: true do end end + describe 'new issue' do + context 'dropzone upload file', js: true do + before do + visit new_namespace_project_issue_path(project.namespace, project) + end + + it 'should upload file when dragging into textarea' do + drop_in_dropzone test_image_file + + # Wait for the file to upload + sleep 1 + + expect(page.find_field("issue_description").value).to have_content 'banana_sample' + end + end + end + def first_issue page.all('ul.issues-list > li').first.text end @@ -299,4 +316,25 @@ describe 'Issues', feature: true do def last_issue page.all('ul.issues-list > li').last.text end + + def drop_in_dropzone(file_path) + # Generate a fake input selector + page.execute_script <<-JS + var fakeFileInput = window.$('').attr( + {id: 'fakeFileInput', type: 'file'} + ).appendTo('body'); + JS + # Attach the file to the fake input selector with Capybara + attach_file("fakeFileInput", file_path) + # Add the file to a fileList array and trigger the fake drop event + page.execute_script <<-JS + var fileList = [$('#fakeFileInput')[0].files[0]]; + var e = jQuery.Event('drop', { dataTransfer : { files : fileList } }); + $('.div-dropzone')[0].dropzone.listeners[0].events.drop(e); + JS + end + + def test_image_file + File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') + end end From cc57d61023c6dd5ef274bac5d4e6cde1cae97d2c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 17 Apr 2016 08:59:57 -0400 Subject: [PATCH 258/573] Rename finally_script to after_script --- lib/ci/gitlab_ci_yaml_processor.rb | 12 ++++++------ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index be2462949f..a87329c296 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -4,12 +4,12 @@ module Ci DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' - ALLOWED_YAML_KEYS = [:before_script, :finally_script, :image, :services, :types, :stages, :variables, :cache] + ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache, :dependencies] - attr_reader :before_script, :finally_script, :image, :services, :variables, :path, :cache + attr_reader :before_script, :after_script, :image, :services, :variables, :path, :cache def initialize(config, path = nil) @config = YAML.safe_load(config, [Symbol], [], true) @@ -44,7 +44,7 @@ module Ci def initial_parsing @before_script = @config[:before_script] || [] - @finally_script = @config[:finally_script] + @after_script = @config[:after_script] @image = @config[:image] @services = @config[:services] @stages = @config[:stages] || @config[:types] @@ -86,7 +86,7 @@ module Ci artifacts: job[:artifacts], cache: job[:cache] || @cache, dependencies: job[:dependencies], - finally_script: @finally_script, + after_script: @after_script, }.compact } end @@ -104,8 +104,8 @@ module Ci raise ValidationError, "before_script should be an array of strings" end - unless @finally_script.nil? || validate_array_of_strings(@finally_script) - raise ValidationError, "finally_script should be an array of strings" + unless @after_script.nil? || validate_array_of_strings(@after_script) + raise ValidationError, "after_script should be an array of strings" end unless @image.nil? || @image.is_a?(String) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 8e373ae55b..2421d6eee8 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -293,17 +293,17 @@ module Ci subject { config_processor.builds_for_stage_and_ref("test", "master").first } - describe "finally_script" do + describe "after_script" do context "in global context" do let(:config) { { - finally_script: ["finally_script"], + after_script: ["after_script"], test: { script: ["script"] } } } - it "return finally_script in options" do - expect(subject[:options][:finally_script]).to eq(["finally_script"]) + it "return after_script in options" do + expect(subject[:options][:after_script]).to eq(["after_script"]) end end end @@ -629,11 +629,11 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings") end - it "returns errors if finally_script parameter is invalid" do - config = YAML.dump({ finally_script: "bundle update", rspec: { script: "test" } }) + it "returns errors if after_script parameter is invalid" do + config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "finally_script should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "after_script should be an array of strings") end it "returns errors if image parameter is invalid" do From a0afeefd76ebfacae59343f48e127828b27ba77a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 17 Apr 2016 09:02:42 -0400 Subject: [PATCH 259/573] Add CHANGELOG and documentation --- CHANGELOG | 1 + doc/ci/yaml/README.md | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c948e8e546..f52897892e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ v 8.7.0 (unreleased) - Make /profile/keys/new redirect to /profile/keys for back-compat. !3717 - Preserve time notes/comments have been updated at when moving issue - Make HTTP(s) label consistent on clone bar (Stan Hu) + - Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński) - Expose label description in API (Mariusz Jachimowicz) - API: Ability to update a group (Robert Schilling) - API: Ability to move issues (Robert Schilling) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index abb6e97e5e..54b06f10b9 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -15,6 +15,7 @@ If you want a quick introduction to GitLab CI, follow our - [.gitlab-ci.yml](#gitlab-ci-yml) - [image and services](#image-and-services) - [before_script](#before_script) + - [after_script](#after_script) - [stages](#stages) - [types](#types) - [variables](#variables) @@ -80,6 +81,9 @@ services: before_script: - bundle install +after_script: + - rm secrets + stages: - build - test @@ -104,6 +108,7 @@ There are a few reserved `keywords` that **cannot** be used as job names: | stages | no | Define build stages | | types | no | Alias for `stages` | | before_script | no | Define commands that run before each job's script | +| after_script | no | Define commands that run after each job's script | | variables | no | Define build variables | | cache | no | Define list of files that should be cached between subsequent runs | @@ -118,6 +123,11 @@ used for time of the build. The configuration of this feature is covered in `before_script` is used to define the command that should be run before all builds, including deploy builds. This can be an array or a multi-line string. +### after_script + +`after_script` is used to define the command that will be run after for all +builds. This has to be an array or a multi-line string. + ### stages `stages` is used to define build stages that can be used by jobs. From 38b15e35d48550a5621b8fc292cabc5670897a44 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 17 Apr 2016 09:10:47 -0400 Subject: [PATCH 260/573] Update CHANGELOG and add documentation --- CHANGELOG | 1 + doc/ci/yaml/README.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f52897892e..df1887f798 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,6 +46,7 @@ v 8.7.0 (unreleased) - Use rugged to change HEAD in Project#change_head (P.S.V.R) - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - API: Fix milestone filtering by `iid` (Robert Schilling) + - Make before_script and after_script overridable on per-job (Kamil Trzciński) - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Better errors handling when creating milestones inside groups diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 54b06f10b9..c626bee570 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -30,6 +30,7 @@ If you want a quick introduction to GitLab CI, follow our - [artifacts](#artifacts) - [artifacts:name](#artifacts-name) - [dependencies](#dependencies) + - [before_script and after_script](#before_script-and-after_script) - [Hidden jobs](#hidden-jobs) - [Special YAML features](#special-yaml-features) - [Anchors](#anchors) @@ -342,6 +343,8 @@ job_name: | dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them| | artifacts | no | Define list build artifacts | | cache | no | Define list of files that should be cached between subsequent runs | +| before_script | no | Override a set of commands that are executed before build | +| after_script | no | Override a set of commands that are executed after build | ### script @@ -686,6 +689,23 @@ deploy: script: make deploy ``` +### before_script and after_script + +It's possible to overwrite globally defined `before_script` and `after_script`: + +```yaml +before_script +- global before script + +job: + before_script: + - execute this instead of global before script + script: + - my command + after_script: + - execute this after my script +``` + ## Hidden jobs >**Note:** From c764b57f09ac55d181d17db018bdea9587fadea8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 17 Apr 2016 09:11:37 -0400 Subject: [PATCH 261/573] Add note about version --- doc/ci/yaml/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 54b06f10b9..697445bcee 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -125,6 +125,9 @@ builds, including deploy builds. This can be an array or a multi-line string. ### after_script +>**Note:** +Introduced in GitLab 8.7 and GitLab Runner v1.2. + `after_script` is used to define the command that will be run after for all builds. This has to be an array or a multi-line string. From 433ca7390d800a2fda3e3c5eb29272d0ba2d9df6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 17 Apr 2016 09:32:49 -0400 Subject: [PATCH 262/573] Make some logic less twistable --- CHANGELOG | 1 + app/models/concerns/statuseable.rb | 2 +- app/views/projects/builds/index.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 2 +- .../_generic_commit_status.html.haml | 13 +++++++------ spec/requests/api/commits_spec.rb | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5c375fcdb3..4b5564223f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,7 @@ v 8.7.0 (unreleased) - Hide `Create a group` help block when creating a new project in a group - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Allow issues and merge requests to be assigned to the author !2765 + - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) (Kamil Trzciński) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Decouple membership and notifications - Fix creation of merge requests for orphaned branches (Stan Hu) diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index 7b0c9789fb..8a293b7b76 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -7,7 +7,7 @@ module Statuseable def status_sql builds = all.select('count(*)').to_sql success = all.success.select('count(*)').to_sql - ignored = all.ignored.select('count(*)').to_sql if all.try(:ignored) + ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored) ignored ||= '0' pending = all.pending.select('count(*)').to_sql running = all.running.select('count(*)').to_sql diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index aa85f495e3..0406fc21d7 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -58,6 +58,6 @@ %th Coverage %th - = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? + = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? = paginate @builds, theme: 'gitlab' diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 218d396b89..e123eb1cc7 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -19,7 +19,7 @@ %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" - - if !defined?(ref) || ref + - if defined?(ref) && ref %td - if build.ref = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index c15386b488..f21c864e35 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -15,12 +15,13 @@ - if defined?(commit_sha) && commit_sha %td = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - %td - - if generic_commit_status.ref - = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - - else - .light none + + - if defined?(ref) && ref + %td + - if generic_commit_status.ref + = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) + - else + .light none - if defined?(runner) && runner %td diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 25377a4044..e28998d51b 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -48,7 +48,7 @@ describe API::API, api: true do expect(response.status).to eq(404) end - it "should return not_found for CI status" do + it "should return nil for commit without CI" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) expect(json_response['status']).to be_nil From a281f68b64c72bfad7796b8dc0ae3805583c66b6 Mon Sep 17 00:00:00 2001 From: BaldinoF Date: Sun, 17 Apr 2016 18:22:23 +0200 Subject: [PATCH 263/573] Add CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d8fbfafb72..de52033078 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -75,6 +75,7 @@ v 8.7.0 (unreleased) - Fix emoji categories in the emoji picker - Add encrypted credentials for imported projects and migrate old ones - Author and participants are displayed first on users autocompletion + - Show number sign on external issue reference text (Florent Baldino) v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 From 7cc239528ea7f4905e6d773771006ec661d628d6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 17 Apr 2016 17:48:51 -0400 Subject: [PATCH 264/573] Remove persistent XSS vulnerability in `commit_person_link` helper Because we were incorrectly supplying the tooltip title as `data-original-title` (which Bootstrap's Tooltip JS automatically applies based on the `title` attribute; we should never be setting it directly), the value was being passed through as-is. Instead, we should be supplying the normal `title` attribute and letting Rails escape the value, which also negates the need for us to call `sanitize` on it. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/15126 --- app/helpers/commits_helper.rb | 2 +- features/steps/project/commits/user_lookup.rb | 3 +- spec/helpers/commits_helper_spec.rb | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 spec/helpers/commits_helper_spec.rb diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 35ba543cef..5394347bd1 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -183,7 +183,7 @@ module CommitsHelper options = { class: "commit-#{options[:source]}-link has-tooltip", - data: { 'original-title'.to_sym => sanitize(source_email) } + title: source_email } if user.nil? diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb index 40cada6da4..2d43be5a38 100644 --- a/features/steps/project/commits/user_lookup.rb +++ b/features/steps/project/commits/user_lookup.rb @@ -29,8 +29,9 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps def check_author_link(email, user) author_link = find('.commit-author-link') + expect(author_link['href']).to eq user_path(user) - expect(author_link['data-original-title']).to eq email + expect(author_link['title']).to eq email expect(find('.commit-author-name').text).to eq user.name end diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb new file mode 100644 index 0000000000..727c25ff52 --- /dev/null +++ b/spec/helpers/commits_helper_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +describe CommitsHelper do + describe 'commit_author_link' do + it 'escapes the author email' do + commit = double( + author: nil, + author_name: 'Persistent XSS', + author_email: 'my@email.com" onmouseover="alert(1)' + ) + + expect(helper.commit_author_link(commit)). + not_to include('onmouseover="alert(1)"') + end + end + + describe 'commit_committer_link' do + it 'escapes the committer email' do + commit = double( + committer: nil, + committer_name: 'Persistent XSS', + committer_email: 'my@email.com" onmouseover="alert(1)' + ) + + expect(helper.commit_committer_link(commit)). + not_to include('onmouseover="alert(1)"') + end + end +end From 40bc8e7677cb27f87fef7d4362fbd17bee8980ef Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 18 Apr 2016 12:19:11 +0530 Subject: [PATCH 265/573] Add acceptance test to check if the user password persists after form redisplays. - While signing up. --- spec/features/signup_spec.rb | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 spec/features/signup_spec.rb diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb new file mode 100644 index 0000000000..01472743b2 --- /dev/null +++ b/spec/features/signup_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +feature 'Signup', feature: true do + describe 'signup with no errors' do + it 'creates the user account and sends a confirmation email' do + user = build(:user) + + 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 + click_button "Sign up" + + expect(current_path).to eq user_session_path + expect(page).to have_content("A message with a confirmation link has been sent to your email address.") + end + end + + describe 'signup with errors' do + it "displays the errors" do + existing_user = create(:user) + user = build(:user) + + 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 + click_button "Sign up" + + expect(current_path).to eq user_registration_path + expect(page).to have_content("error prohibited this user from being saved") + expect(page).to have_content("Email has already been taken") + end + + it 'does not redisplay the password' do + existing_user = create(:user) + user = build(:user) + + 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 + click_button "Sign up" + + expect(current_path).to eq user_registration_path + expect(page.body).not_to match(/#{user.password}/) + end + end +end From 38557ec400d8c28ea73df4bc5142e156c7ab8855 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 18 Apr 2016 12:30:04 +0530 Subject: [PATCH 266/573] Move CHANGELOG entry to a random place. --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 225751cbb0..ede0c00e90 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,7 @@ v 8.7.0 (unreleased) - Decouple membership and notifications - Fix creation of merge requests for orphaned branches (Stan Hu) - API: Ability to retrieve a single tag (Robert Schilling) + - While signing up, don't persist the user password across form redisplays - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - Fix admin/projects when using visibility levels on search (PotHix) @@ -66,7 +67,6 @@ v 8.7.0 (unreleased) - Update number of Todos in the sidebar when it's marked as "Done". !3600 - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - API: User can leave a project through the API when not master or owner. !3613 - - While signing up, don't persist the user password across form redisplays - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - Improved markdown forms From aa396ae5ee5715c1a2a8a82731cef6e3d7f73056 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 18 Apr 2016 13:01:23 +0530 Subject: [PATCH 267/573] Remove unused variable in `IssuesController`. --- app/controllers/projects/issues_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index f3056b2100..f2ae572cfe 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -128,8 +128,6 @@ class Projects::IssuesController < Projects::ApplicationController end def related_branches - merge_requests = @issue.referenced_merge_requests(current_user) - @related_branches = @issue.related_branches(current_user) respond_to do |format| From 3d6ba3b1076e68a67691d0e0de24ef97cc07f119 Mon Sep 17 00:00:00 2001 From: "P.S.V.R" Date: Mon, 18 Apr 2016 15:39:07 +0800 Subject: [PATCH 268/573] Add support to cherry-pick any commit Issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/12785 Merge Request: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 --- CHANGELOG | 1 + app/controllers/projects/commit_controller.rb | 30 ++++---- app/helpers/commits_helper.rb | 29 +++++--- app/helpers/tree_helper.rb | 2 +- app/models/commit.rb | 8 +++ app/models/repository.rb | 49 +++++++++++-- app/services/commits/change_service.rb | 47 ++++++++++++ app/services/commits/cherry_pick_service.rb | 19 +++++ app/services/commits/revert_service.rb | 46 +----------- .../{_revert.html.haml => _change.html.haml} | 20 ++++-- .../projects/commit/_commit_box.html.haml | 1 + app/views/projects/commit/show.html.haml | 3 +- .../projects/merge_requests/_show.html.haml | 4 +- .../widget/_merged_buttons.haml | 1 + config/routes.rb | 1 + doc/intro/README.md | 1 + doc/workflow/README.md | 1 + doc/workflow/cherry_pick_changes.md | 52 ++++++++++++++ .../img/cherry_pick_changes_commit.png | Bin 0 -> 353067 bytes .../img/cherry_pick_changes_commit_modal.png | Bin 0 -> 312659 bytes doc/workflow/img/cherry_pick_changes_mr.png | Bin 0 -> 252085 bytes .../img/cherry_pick_changes_mr_modal.png | Bin 0 -> 225569 bytes spec/controllers/commit_controller_spec.rb | 51 +++++++++++++ .../project/commits/cherry_pick_spec.rb | 67 ++++++++++++++++++ spec/models/repository_spec.rb | 35 +++++++++ spec/support/repo_helpers.rb | 2 +- 26 files changed, 390 insertions(+), 80 deletions(-) create mode 100644 app/services/commits/change_service.rb create mode 100644 app/services/commits/cherry_pick_service.rb rename app/views/projects/commit/{_revert.html.haml => _change.html.haml} (62%) create mode 100644 doc/workflow/cherry_pick_changes.md create mode 100644 doc/workflow/img/cherry_pick_changes_commit.png create mode 100644 doc/workflow/img/cherry_pick_changes_commit_modal.png create mode 100644 doc/workflow/img/cherry_pick_changes_mr.png create mode 100644 doc/workflow/img/cherry_pick_changes_mr_modal.png create mode 100644 spec/features/project/commits/cherry_pick_spec.rb diff --git a/CHANGELOG b/CHANGELOG index de52033078..fb08134229 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -76,6 +76,7 @@ v 8.7.0 (unreleased) - Add encrypted credentials for imported projects and migrate old ones - Author and participants are displayed first on users autocompletion - Show number sign on external issue reference text (Florent Baldino) + - Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan) v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 576fa3cedb..4d64a2d988 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController before_action :authorize_read_commit_status!, only: [:builds] before_action :commit before_action :define_show_vars, only: [:show, :builds] - before_action :authorize_edit_tree!, only: [:revert] + before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] def show apply_diff_view_cookie! @@ -60,27 +60,32 @@ class Projects::CommitController < Projects::ApplicationController end def revert - assign_revert_commit_vars + assign_change_commit_vars(@commit.revert_branch_name) return render_404 if @target_branch.blank? - create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.", - success_path: successful_revert_path, failure_path: failed_revert_path) + create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.", + success_path: successful_change_path, failure_path: failed_change_path) + end + + def cherry_pick + assign_change_commit_vars(@commit.cherry_pick_branch_name) + + return render_404 if @target_branch.blank? + + create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.", + success_path: successful_change_path, failure_path: failed_change_path) end private - def revert_type_title - @commit.merged_merge_request ? 'merge request' : 'commit' - end - - def successful_revert_path + def successful_change_path return referenced_merge_request_url if @commit.merged_merge_request namespace_project_commits_url(@project.namespace, @project, @target_branch) end - def failed_revert_path + def failed_change_path return referenced_merge_request_url if @commit.merged_merge_request namespace_project_commit_url(@project.namespace, @project, params[:id]) @@ -111,14 +116,13 @@ class Projects::CommitController < Projects::ApplicationController @statuses = ci_commit.statuses if ci_commit end - def assign_revert_commit_vars + def assign_change_commit_vars(mr_source_branch) @commit = project.commit(params[:id]) @target_branch = params[:target_branch] - @mr_source_branch = @commit.revert_branch_name + @mr_source_branch = mr_source_branch @mr_target_branch = @target_branch @commit_params = { commit: @commit, - revert_type_title: revert_type_title, create_merge_request: params[:create_merge_request].present? || different_project? } end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 35ba543cef..080928a4eb 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -126,12 +126,10 @@ module CommitsHelper def revert_commit_link(commit, continue_to_path, btn_class: nil) return unless current_user - tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request" + tooltip = "Revert this #{commit.change_type_title} in a new merge request" if can_collaborate_with_project? - content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do - link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}" - end + link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip" elsif can?(current_user, :fork_project, @project) continue_params = { to: continue_to_path, @@ -146,11 +144,24 @@ module CommitsHelper end end - def revert_commit_type(commit) - if commit.merged_merge_request - 'merge request' - else - 'commit' + def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil) + return unless current_user + + tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request" + + if can_collaborate_with_project? + link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip" + elsif can?(current_user, :fork_project, @project) + continue_params = { + to: continue_to_path, + notice: edit_in_new_fork_notice + ' Try to cherry-pick this commit again.', + notice_now: edit_in_new_fork_notice_now + } + fork_path = namespace_project_forks_path(@project.namespace, @project, + namespace_key: current_user.namespace.id, + continue: continue_params) + + link_to 'Cherry-pick', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip end end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 4920ca5af6..dbedf417fa 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -66,7 +66,7 @@ module TreeHelper ref else project = tree_edit_project(project) - project.repository.next_patch_branch + project.repository.next_branch('patch') end end diff --git a/app/models/commit.rb b/app/models/commit.rb index d1f07ccd55..b406a4dd8d 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -218,6 +218,10 @@ class Commit def revert_branch_name "revert-#{short_id}" end + + def cherry_pick_branch_name + project.repository.next_branch("cherry-pick-#{short_id}", mild: true) + end def revert_description if merged_merge_request @@ -253,6 +257,10 @@ class Commit end.any? { |commit_ref| commit_ref.reverts_commit?(self) } end + def change_type_title + merged_merge_request ? 'merge request' : 'commit' + end + private def repo_changes diff --git a/app/models/repository.rb b/app/models/repository.rb index 308c590e3f..f9ed90f0d6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -547,15 +547,18 @@ class Repository commit(sha) end - def next_patch_branch - patch_branch_ids = self.branch_names.map do |n| - result = n.match(/\Apatch-([0-9]+)\z/) + def next_branch(name, opts={}) + branch_ids = self.branch_names.map do |n| + next 1 if n == name + result = n.match(/\A#{name}-([0-9]+)\z/) result[1].to_i if result end.compact - highest_patch_branch_id = patch_branch_ids.max || 0 + highest_branch_id = branch_ids.max || 0 - "patch-#{highest_patch_branch_id + 1}" + return name if opts[:mild] && 0 == highest_branch_id + + "#{name}-#{highest_branch_id + 1}" end # Remove archives older than 2 hours @@ -758,6 +761,28 @@ class Repository end end + def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) + source_sha = find_branch(base_branch).target + cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) + + return false unless cherry_pick_tree_id + + commit_with_hooks(user, base_branch) do |ref| + committer = user_to_committer(user) + source_sha = Rugged::Commit.create(rugged, + message: commit.message, + author: { + email: commit.author_email, + name: commit.author_name, + time: commit.authored_date + }, + committer: committer, + tree: cherry_pick_tree_id, + parents: [rugged.lookup(source_sha)], + update_ref: ref) + end + end + def check_revert_content(commit, base_branch) source_sha = find_branch(base_branch).target args = [commit.id, source_sha] @@ -772,6 +797,20 @@ class Repository tree_id end + def check_cherry_pick_content(commit, base_branch) + source_sha = find_branch(base_branch).target + args = [commit.id, source_sha] + args << 1 if commit.merge_commit? + + cherry_pick_index = rugged.cherrypick_commit(*args) + return false if cherry_pick_index.conflicts? + + tree_id = cherry_pick_index.write_tree(rugged) + return false unless diff_exists?(source_sha, tree_id) + + tree_id + end + def diff_exists?(sha1, sha2) rugged.diff(sha1, sha2).size > 0 end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb new file mode 100644 index 0000000000..6b69cb53b2 --- /dev/null +++ b/app/services/commits/change_service.rb @@ -0,0 +1,47 @@ +module Commits + class ChangeService < ::BaseService + class ValidationError < StandardError; end + class ChangeError < StandardError; end + + def execute + @source_project = params[:source_project] || @project + @target_branch = params[:target_branch] + @commit = params[:commit] + @create_merge_request = params[:create_merge_request].present? + + check_push_permissions unless @create_merge_request + commit + rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, + ValidationError, ChangeError => ex + error(ex.message) + end + + def commit + raise NotImplementedError + end + + private + + def check_push_permissions + allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + + unless allowed + raise ValidationError.new('You are not allowed to push into this branch') + end + + true + end + + def create_target_branch(new_branch) + # Temporary branch exists and contains the change commit + return success if repository.find_branch(new_branch) + + result = CreateBranchService.new(@project, current_user) + .execute(new_branch, @target_branch, source_project: @source_project) + + if result[:status] == :error + raise ChangeError, "There was an error creating the source branch: #{result[:message]}" + end + end + end +end diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb new file mode 100644 index 0000000000..f9a4efa718 --- /dev/null +++ b/app/services/commits/cherry_pick_service.rb @@ -0,0 +1,19 @@ +module Commits + class CherryPickService < ChangeService + def commit + cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch + cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch) + + if cherry_pick_tree_id + create_target_branch(cherry_pick_into) if @create_merge_request + + repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id) + success + else + error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically. + It may have already been cherry-picked, or a more recent commit may have updated some of its content." + raise ChangeError, error_msg + end + end + end +end diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb index a3c950ede1..c7de9f6f35 100644 --- a/app/services/commits/revert_service.rb +++ b/app/services/commits/revert_service.rb @@ -1,21 +1,5 @@ module Commits - class RevertService < ::BaseService - class ValidationError < StandardError; end - class ReversionError < StandardError; end - - def execute - @source_project = params[:source_project] || @project - @target_branch = params[:target_branch] - @commit = params[:commit] - @create_merge_request = params[:create_merge_request].present? - - check_push_permissions unless @create_merge_request - commit - rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, - ValidationError, ReversionError => ex - error(ex.message) - end - + class RevertService < ChangeService def commit revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch revert_tree_id = repository.check_revert_content(@commit, @target_branch) @@ -26,34 +10,10 @@ module Commits repository.revert(current_user, @commit, revert_into, revert_tree_id) success else - error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically. + error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically. It may have already been reverted, or a more recent commit may have updated some of its content." - raise ReversionError, error_msg + raise ChangeError, error_msg end end - - private - - def create_target_branch(new_branch) - # Temporary branch exists and contains the revert commit - return success if repository.find_branch(new_branch) - - result = CreateBranchService.new(@project, current_user) - .execute(new_branch, @target_branch, source_project: @source_project) - - if result[:status] == :error - raise ReversionError, "There was an error creating the source branch: #{result[:message]}" - end - end - - def check_push_permissions - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) - - unless allowed - raise ValidationError.new('You are not allowed to push into this branch') - end - - true - end end end diff --git a/app/views/projects/commit/_revert.html.haml b/app/views/projects/commit/_change.html.haml similarity index 62% rename from app/views/projects/commit/_revert.html.haml rename to app/views/projects/commit/_change.html.haml index 52ca3ed5b1..44ef1fdbbe 100644 --- a/app/views/projects/commit/_revert.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -1,13 +1,21 @@ -#modal-revert-commit.modal +- case type.to_s +- when 'revert' + - label = 'Revert' + - target_label = 'Revert in branch' +- when 'cherry-pick' + - label = 'Cherry-pick' + - target_label = 'Pick into branch' + +.modal{id: "modal-#{type}-commit"} .modal-dialog .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3.page-title== Revert this #{revert_commit_type(commit)} + %h3.page-title== #{label} this #{commit.change_type_title} .modal-body - = form_tag revert_namespace_project_commit_path(@project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do + = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do .form-group.branch - = label_tag 'target_branch', 'Revert in branch', class: 'control-label' + = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 = select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch" - if can?(current_user, :push_code, @project) @@ -20,7 +28,7 @@ - else = hidden_field_tag 'create_merge_request', 1 .form-actions - = submit_tag "Revert", class: 'btn btn-create' + = submit_tag label, class: 'btn btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" - unless can?(current_user, :push_code, @project) @@ -28,4 +36,4 @@ = commit_in_fork_help :javascript - new NewCommitForm($('.js-create-dir-form')) + new NewCommitForm($('.js-#{type}-form')) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 71995fcc48..d6c9e54e65 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -18,6 +18,7 @@ Browse Files - unless @commit.has_been_reverted?(current_user) = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id)) + = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id)) %div %p diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 21e186120c..e550af7888 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -13,4 +13,5 @@ diff_refs: @diff_refs = render "projects/notes/notes_with_form" - if can_collaborate_with_project? - = render "projects/commit/revert", commit: @commit, title: @commit.title + - %w(revert cherry-pick).each do |type| + = render "projects/commit/change", type: type, commit: @commit, title: @commit.title diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 285ad26316..dcb10ea635 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -86,7 +86,9 @@ = render 'shared/issuable/sidebar', issuable: @merge_request - if @merge_request.can_be_reverted? - = render "projects/commit/revert", commit: @merge_request.merge_commit, title: @merge_request.title + = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title +- if @merge_request.merge_commit + = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title :javascript var merge_request; diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml index 85a3a6ba9e..361acf7d27 100644 --- a/app/views/projects/merge_requests/widget/_merged_buttons.haml +++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml @@ -9,3 +9,4 @@ Remove Source Branch - if mr_can_be_reverted = revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm') + = cherry_pick_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm') diff --git a/config/routes.rb b/config/routes.rb index 46a2526284..b9f30ede52 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -549,6 +549,7 @@ Rails.application.routes.draw do post :cancel_builds post :retry_builds post :revert + post :cherry_pick end end diff --git a/doc/intro/README.md b/doc/intro/README.md index fecbbe6317..ab298d3808 100644 --- a/doc/intro/README.md +++ b/doc/intro/README.md @@ -25,6 +25,7 @@ Create merge requests and review code. - [Automatically close issues from merge requests](../customization/issue_closing.md) - [Automatically merge when your builds succeed](../workflow/merge_when_build_succeeds.md) - [Revert any commit](../workflow/revert_changes.md) +- [Cherry-pick any commit](../workflow/cherry_pick_changes.md) ## Test and Deploy diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 25893f948e..9efe41308d 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -20,6 +20,7 @@ - [Milestones](milestones.md) - [Merge Requests](merge_requests.md) - [Revert changes](revert_changes.md) +- [Cherry-pick changes](cherry_pick_changes.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) - [Merge When Build Succeeds](merge_when_build_succeeds.md) - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md) diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md new file mode 100644 index 0000000000..b0ca087964 --- /dev/null +++ b/doc/workflow/cherry_pick_changes.md @@ -0,0 +1,52 @@ +# Cherry-pick changes + +_**Note:** This feature was [introduced][ce-3514] in GitLab 8.7._ + +--- + +GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick] +with introducing a **Cherry-pick** button in Merge Requests and commit details. + +## Cherry-picking a Merge Request + +After the Merge Request has been merged, a **Cherry-pick** button will be available +to cherry-pick the changes introduced by that Merge Request: + +![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png) + +--- + +You can cherry-pick the changes directly into the selected branch or you can opt to +create a new Merge Request with the cherry-pick changes: + +![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png) + +## Cherry-picking a Commit + +You can cherry-pick a Commit from the Commit details page: + +![Cherry-pick commit](img/cherry_pick_changes_commit.png) + +--- + +Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes +directly into the target branch or create a new Merge Request to cherry-pick the +changes: + +![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png) + +--- + +Please note that when cherry-picking merge commits, the mainline will always be the +first parent. If you want to use a different mainline then you need to do that +from the command line. + +Here is a quick example to cherry-pick a merge commit using the second parent as the +mainline: + +```bash +git cherry-pick -m 2 7a39eb0 +``` + +[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request" +[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation" diff --git a/doc/workflow/img/cherry_pick_changes_commit.png b/doc/workflow/img/cherry_pick_changes_commit.png new file mode 100644 index 0000000000000000000000000000000000000000..ae91d2cae53c5fe25b791ff415a6b436e33e2e0f GIT binary patch literal 353067 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 z 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%0AMamNP0P(fMOxLJFMiJir1C?^eKBx68yj_>Qyr( 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

    qXE#*EPQG5(SopdIKjx$Lr-Y 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 g6E-{i~Gd=YMr~SpsgOj)0Ya{Uo8p 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≷7?Zx%{uN=bf8XRtp!S;VP(tw6A6Zz%itJhRd|+*3eCscRFL z5-s(p@Mn3VEfZ*RIbcgE^dfHFhClUdu51r_<$8j%Nt1f6Fqi0sAQzo>z;c+~v)zx) zs7Y>_KFX^kFLRP~L{p@viniAD(x+3J)}M@gbeve8eC<=;V_bhwEmtz1k&NqJ-Wh*d zrGe2%3+S-F>PT@UEdwhs=691qHzw_K3SWD+O$J3?rW!FViNWroI(FA-X-c zW%NDR2O#D^3>D20_4S8^_4)o6ILDL?+&p&dySVB6)~irD?l($6xN(oA8tK05aNssV zw`AKMHD+1#WLEMt)Spv7iI@3s#_JZ}q3KPlCkJu)0$;KBrtqKa)v?m|^lC7h znrw+RGm<`MN$zB2x!?rIN64qyvg^6l2i=twBb0#Q?Kia!Y;ViZ%ZrKgZ6d4cyxVHH zIQT0kC(i~%!Bq;m$5Q0vYrA!>o+tZ`>GB@GE;O`0QsA|_Om_lcjPOktO_}TcYtpv6 zc7|{`w1;8tdm<4!YpZeeQ0>R3mdXcxn0*L!DIUEP1D9#F@VJ98wUzfOvU%yNX&DuU;IPnyH@1({G|!bun192Lg$A3x%>`0AL7UAz)jQy!ZtHDma1 zrgmU9k4sBR@y)vg3;tpkXTyo?DqR`|3n>dToUp&1QpV;$+aYgQjnQlZ@@PqJ^`Ju` z5H+$YK-?vq8g-FG8CzvGnCtB=4K*{fqAcTs>B?9JvCcBWlvGUq`Ael3dxq4i)&qpO ztLZb3fM>W^15thnRGK=n#CL$bB8;9i8k(=+46+^TtUQSLLyutX|fnekYnBx z5ZsoF2`LV03^mzt&yP=PLrM?0Cm9u+J5JXuH?|trY4JPGm%c*VAFS_$lqzJL*_|WG zXlD++LyI^;o>cUmMo7=#xPU=Jzp&$09dc zm54{*-m)`jOGzhT8#lm*d*?)%ARxos>#~P(Dx7vx1kbwV?xWQoot00$R;*Tvob@)| zJ*qAu-U2X7>}nTCCJLB9+u-Y~?fDumrO>&+5c$l&xk;ixnEnAjKg`Lg_a|Ie!}gbOykej57vgdzul8*ck9e`^^NM(n6B%5HEQEHiMJWG|*L9j_LRoC;42k8?@5KD=v z+;A$5bfW>t*A%gJi(^Kk5$P*BI_y@KBfb;k1CtYO3q7(lw(Kl#}lQm;6*OD>><-u-aFHF|MUr8)=OgEzTS6&z+h_oGRLx znWUnI=38X!t7?`Tdkzq+R}6E7tzET*INvR@380@&7C7m`ld&yx?$kV|Grq2AJ$e#fDCo_nG83`Indv z_<98D7Hi`gn~X}KcD-xv1D!<>TXLR)c<`d{I>A9Vz$Ys9xPnVoG(mdGw9oy!fx&ud zY|YvME$HGrycL;rBub={fk43BW+R3BIBi?F)|j$zE41d%I;Ff~qb_3)+WAsL{BidH zK7pt^^6gW;U1??UfV@Zhok3uO`E=Ulc(cT?KKDLekQS?nJMs*#(B!5asNO8c9&NkIvqwIaeN6 zff@G){n1)rz0-)QHZA7zhhcW|eD-^eEK=rIiW%;$*EjzVTitN!qc zO9ASg|7mzQaHt+KKYLJ7vO5>wR*y&z)taNb-~y*fz89(MtiEIhpA$?Ndbm#|W(zm; zW;b5xf`wkmsDH8*I|wOxFkjR_xhBg!RLSMVe0uI9Xi;4LWXa_yZk zg&hAR)%!MFvSRaT>pn+7=;Ab7D zCz+jcxX^3r2fCa`!u?`*Ky`XhlM~N?M<51ULcnJ{s(lNog7p`e z(YX*R`p~c{W;t7z0^=z3G&#K1_{=EPyPxH@%IP*Scs}=-)Yy4Tix}9`hnh0$*w>rF ze9`elwv!k0@PmruUlD!w1VADguBO)wxk$!BIpPg>rOLEAtc+d|Mi5H}unYUf{sVc7 znuIe3-nEQL=WQGq0Uhp72^+i22;ipS0zmUgpa!-$pM|`g@*&+>YykAln`_-?{Zm`s zn5t@6KtQ-`Mkq9sPrk^W&{A6H2p9r;p}*f#=u6lGr8UpH7sj z;EwPL!a1Lc^|r7+=*GZpS}g-Pi;$vByH+nzmf=(t<5h{0&Sj<9203=9*@y04m6HoS z_Y#@dMx-1W@*wqh&_*yQu3eE_%xONmJ+jK=@q%U0!Fr8jgkhflMf&(hCx58`DM9;W zmwopuWfvDjg)_5krB177_M+#KS>K%|om;m*)_mpiS(#1aqI=cy>Sext3jmuBn=P&o zwixUCyhuBS9|%ShgVlD|zTYcFwmncDp2mq;r!a2dLvPoJLc?>ImJbs}Pbxmk+ZE2+=MG|H# z5!naV{i@0GxTCMOz2i+iO{cF_pDyZh-&D(6QLP2YM5_DTXWP{dG8_pry7PX!hN-xQD6Zqh(mjPw<*p}_2R|j_*!m} z`;$&C33VC}98<5a&PDQLcUDV}o>d;BXL>r`njf0gl!4qh1re=4v z1})mGx`NbbkE4peJ(~N%*SP$}ZZ=0$A2)h{3+?>Ft?k@8@Gc%cO>S1tM#O zUGa(XwYY>w99=xE#Dv+(==}jzodvfhN@1IFV_P?@?UC=Uw!K>UyPS~`VQV*zMZ^Mk zJZ`jdF)Sa-Ppju7o#o4iTDIFsEH2U<q18ad)J(hfa(>o;_Ted{bX}>Ni@} z%N9!+icS3ztBH%7|(Re4OG*d%T0AR$T)8BXAt-q#H9Iy|#|_a(IFcSg8_7KhvuL z5(-iH*6%N1XOSxJbKab6O4zTYF7R$i=1Y(h92R9*1`P)Dh3C5m7IJc-2dN*%Ke%p^ z`7a!-;@3N^*)ic=pd@FE0XsZ-7H{VyI-McPxG7b{#rLdR|Qsk;b!;Y2Gapga)O&PX&p#Ga7h?QOg5Zlwxx8)8MH!LP(?|Ho zoT@I$h^JU>93QeC=-JXf&w+rUXjHxNkg7r4wKy@VI{;wGufaUQSHotx_PM;Ua86H> zfQ!vfc%l@Y^?2j(j*HCaPXBF__GOB%U?~$hz}U!c4bMGkHLZYTsaT{PtZi8{@5%h` z3a&gf5VMcz(jEECbKsz<$HzD{Lsdy@k4*8|0`HYnMWF4NQoyk*=t|A2er3V@5JtxF@XOKY3ed^ruh@1wiJ=DaJZK``nO zlWfh?JK80Z3P?$#RR|n#zIm%!1M`aZ+lxdYK-08u=we)hdhip>c__ZYd|P^fgjzG= zA=b+0)WGapOmF=ys|g+PBqlN9_gM+2ibP0JL#MrCLQx%QJmI;U-|~xMt5Y}MJzx$f@@spvxOH#*$5;Ez>zJ2>_wF_Hq7D%8S8PWTX18of29?(yukV^tjk0BFJ z)crQyw8-R!4@`n0^U11r*7|K(d`vJ~$u8-vwbG}-h6N#~<@K>FL)Gfo{BV}F?obos zDk1LxTr9KGAgg_8{7FFn#{4su_3syE00Wwv_Ljrv%Zw6>EwB#8{~(tNW!@wGj(jZ@ zbg7l#Y_>5q#$GCvI|p9{)CuOdO=lGkPD?dJO`U$i>d6W(@Rm^;$~*m3~G&XW?0G*He5buhGizm2xF#_W_waBbJ=Wh)d!Tq+k}p2TYPuT=pq zprPVd%Hy5wrBo!u*B-u3(!os@7U|MRrSMF!)>cc=b8Kq~sY)mhkGq63yPCRcjmzw4 zajL|wUAzmA-9~6KdUqM2W_T;~>g{PzWL`{Xv~7)U;9kK^n0$3m@XhD^+)2xcWkcpqFZy#3Kw!+@BOe-%MJlsCdk{LxH@sjM@eq?qKSrmsWq&6{^kvRgVK z34Gj)5wMb5v4)~4v#ntQbR=K=A8jJv{z;_7E4HiXH~mR=P{-KQmY=v(yVDZmCN-@N zUZ_wLrXlTJTfq$#S%+>2gZk~d|C`NQJar!{Z#UOS8*k{uMDC~IG;Fi`tb3_Bpq{02 z<8ev1vDD`{-b?2C%~Oa+1DOm)F3{}gnf*?H@|pj{(s*l;{58e{6$4}4r%=!TI#+mt zf2z~@uOeJ?7`JgH3v)AXs+fY6uspPLCdN+ZCcO3gu%j|NKb#_w4zBr$`IHa02?|Wgf z$|^AE0cj$3DLC1zA>@y(9?KZ>*v53w>{8?*%2qTFHn4&L94bi0d+_CS=hXX*DZ`zE zWmlzkTig8w)(r+Yq#D2|G+ zyaYVn?sj1M(^!s<2?ZkM7V{w|sb{xsAUkymC?8~oxO*K5 z$yo250(EvWQ^|sh?j_j1tpQxv-t)4%MtWSrr^OuEj?yyk{(aI3Vmr35!J0nU6r;^2 zlx0#{RqTpk{ErB{{5;sww_!5bB};3;7P{9&{y2L=B0xsHgGaNix32u5RL1E{8&;oi)K z>~3O;G=L!*NW#mo^iC!fR4av3Y(a5?r&lgVEAzz}q#$ly)8nne9(Sv zy>JS@sb{(*+-os6lX&7YzX5|B@M5P9na8ue*GFBo;ilapO(5@d0PmcbQ%L`@*{b~W zWreYOgL&U&?|9wZDzO@&V10d71lxzJQQuXcG8!2?nfDOPD{ck2oYmFZ{L|H4(8UiO zONl}O>~t~DXzu(bCM8rdHy5ybn)Gn~cM@ON`FO}rtYY}f*Pq5HnvM-{j$8$Y!^RkK zd^bGlDpjlhphQE~jL&$)5gr#t#DR%=sZPtL54R}9L1HpFY@pop1%FPBpRma(c3={lx61%WiS#@p;Itx5aSBQ@-5 zht<|6-&K3+-`cddlFE|vFvWC>l1NGroIaqk57Sh(wF;jmxUoGt05Pz2p8_jLa=F<;+vJt%cmLt z@AeWJuBgoC z%3UlJ$!{evOM8nReXx5-F6P{KLec1U?(3s2mwncDj1FSinuj2(Nmo+@{(>_*sX+?V zdv0H5<@Bv12G)5z=|Rz!KIo!FHSkJJH&nM6lixQnYYwi(5h-=eAwXO8gTFFDcM?qY zcBE@cEq2o^y}F^*DW7&L>w{3n|Q4A1G@FArLpvSngApn?l5rh~K3Ag}eN2@Zto8Z$#G*T0Vb64@p&T z5Ko|%{+q-?{4zTF?ZL4v)ORgkU`8F|Kj>Da0YX*NlN3=${2O8Td@nMNxS3qJYwuZI z!-RkJY`BcXiG}?aLHzIIr55itYnN(E1=@LDh*M*c_p^SLiKki#$xJ~jhM)Ci=V0`i5zZ&Mt*z@?Rvq%jC z`>Q&4fFYi(L=t&}=2z$Zbv$RD*y@s|cN39mh6$`kE~8s4nMru5qT1Ni!k)lxm6$zLYi4sS)UV`t63slHr%GV=D{=c_lcTz?}H z`81a_jLm(*xni=Yq95`>jh%8&D=_|d1N!7!?&~2W>?5KyfS!wLvq{M>g78Kdej$DT z@0KNgMpY>0E>Qst6w=TKdbNXhE%WjV3T`uBE#n5#aMsOkD~4a8rumQ1{e55QhQ(np zIEwoB-)Km~X(GPbr2J3}zfRI)MIZJHxMG(}Nn1wx?+*PUUGZ>I(=uXQe@zGI+Z)29 z%>UfUuXofk|Goh5#q$`|e=$ygFJ6kV-TgO*2$*U+A!{UM;y+X!E-?57p6Bep3i|u_ zG7=T}Z$bb6Lpp%6|L;ZY|6?IGyq>_B&(k?5)jzp$Q07xcy#1&ud+cb;S78Xe?ik%c z+mr*J|KY%V;rx%hnkci~7h%zFiKere0<_YtWIb8VD9Z#(O#J(QfI8ohPkza^$>Wyj zo@nLU%`w@rJF9-Hpkqkn$?7I7cw)R%0SNnT4^ zo@(EJM%QzWU;Up&&q1j?K4Z}mtxxk$=YL65{#ASB!U@3OqlVA?Q(hVMFEM(k81dF< z?GH$v;nF`iu9tZez`c~20<=~R4tYOWmO43>WINO5=Q_=gbuJv6tcD!2m416k_Tk|$ z=S8HL%Rhx0l13O#i~`KTjg1X_br2Q?gK=nVUo8HpWswUk3^>~ERuIFKKe_t&J~doX zzq}pSpEFcwRsKm6*ag<(xbt^GPUK;hosaHJ0%Y>7L&flG)XbFSWsV663G3f0RhId= zjVdev^K#62dbHj-^~TK`o&*{9BHR#T@De*dLK< zWzzssdUf^YZ{LuRMf6AN1g+uaeb4OHN%s|?qchpd4f#YT|7QG=kh*MVw3+s>{O~mu z5fRz-l?^2)d&u}}PiwN=y1;v9^21&wfV=&(uG@+pRf0-vGyL?5_``R9QA3b8UD`DE z_3KZn7@uMwOF2Y_rwGA*nVuC^5(p8YXl;hf|DP7<>AZ2 z)%l^umElc_A{NYM064GF)oZuo<~`O4T9I*^l9BZ^&_7j}hz9z0WPP-^_s%bUTGTKr z@$HsdovC!-&v#X-dlj@AhtRP{yCE@VE;Z!L@|vj_lJ_dd84|SYmThmA>gPS6A4HHm zTDH@1M8ks8KNg(1J-Ps*omYhX{|o?9*X@iO6rZm3sIbwB$HewCyGt@h9a{Ird)m@R z%@39!i@{!AeJNJgUKS&@Z+aK-khXKW5`6$a72QRkc0usv%>y~@gmv`1hbl+t?{2q~ zc~>>`|7ZHhqJQCf%}^?Z^K1Tdj4^(As@^7~4rReGmbZ6ny4rQTv-3O3YWfb7Eg8(! z`Tc@76IC3Oe&TkLLe?Z8|{Bl#xzhHqTl~e|F%yV9RH8C&be zr&;eqz+IT93VY?%Vx6m1YGL<1?2NDB#zz$}YfL47nv)RQ>Tvo5kGi6K3pxdb%)|An z{>$}9)V#umR9ioI5p7KK5EC@H_5@%<( z%OT(lVcC+q$eDu48f8{h?~Kh2*1A~8qNkORUmv>mn9DUaDQy=iT25nfPhWDf&I}&W zxxiiCF?U#uZ!;FKa|`*IG${n}!;R;vt^J)t&U4OHS+=BtO_{}##XL6f6V*yGH%rz> z>!v4{%q<&8#z6YkC`O3QStE%zpo{XMk_BHpTZ!5mn{}@66uL z+4(i8Q%0tYj1cFeLCO37RAgBRu(3&5Ko>T-o>Hltz42+7L`Nsax!EamLRyH7+zS&r z0rL@qui5pnW>dIavpnr6-~rjfI>@E>=GJ>_mzRYWa!`Twf<_^!ZWsnW&f*Mzn8YDk z*duX9WZc+JibGC`9SPjO%uTKLJr2t^Krx?^JNoF?v3Ehrl~jyfh~bT@PnNF#spdVo z5_h$Qk(FU!GAYDL?^Z$&Th~g7PQ>Sqd({7`FEtgQAi0b9@522;XJyTIBCV!OIlUHU zFq3c5@51~`912(S8Se#c?0`Ue!mAcq^Ow2`2OJ7fnqta#QMNs=%As~3Sr@9wtz}wZ@HO9oxyh`h{Y#TeQCXe2U@h7*K zVYG%5u4k&W->7==4nv&47uBWMlt;)o0{wOl2%itp(vQQaK_0Be$g9&p0)biLn;l;L zZ54mt2XsDZ|66Ku73zp*wka#a*PO2a^M_?k&$?&ll6RL?IbXnpgDA>ZR$xRlL6tq@ z`A3or>Zmrh)<-rNh2-MZc1%ZG02eOgqjsnw`lRpeFeOVG zOy=;FxlFJsKRCAG&2Ngx`%W~>Cs96ggU!tR-3niUgnZjtF+pd|IN5V8Zl=sQC;1Mi zrKSI|BHI3BiGzDODe7MUk7F+saNg2DXpR^>WE%SzVw`JD71^<*LHwzUxv$=UB`Tzs-yua;^4Qe3`gKg(e-c+zHRLyoCy)9D;4|E}+u zFSwOPFKadJLUJ#S-m>GfmoI@EweweuD@}sH)wI3A~Pf>_Kgar5+m(= z4Opt;QP)bVP)kEJ=_lp(>1=7b>?!YyJZ@e%-hxJ{%2iej{kW={1OzUeIg_7kBHM5E zNorC3HGldpz81N!oI7Nr7k^(3346rrm|qGQSzT{Mq9}#=^fbc&+XXnoPp*ziD5tVd zoH6Wv={UCe?lnVKph?y}yVpH={`kF@%Wg>Xz7Er`Fx2mBmYLNxHG-2Cva}cOmHprU(N{S6RV zcE)WB^y+IBei`pVT(MD}qnc@BK2G{B*~Wg}E3bF`oZ(_#Eh53IIoq0V_0{er7l|7! zHAnM6?=c9z^hB!9ktaY-Kd-MB1$XnJS8CQL_CIk-@JRlp{XoUfUpzzDR5I$d^d<*l z@RSuR#&6UzOP{jcPsQte6{We;zpV8&vds#?UNCaJeC>K!cA9npAfmvC0_@Pp-vA4c znv;_gu%VC@ySvJ#_gGoS0pAs^r-?S(*qc+F^1Qe2{~1A>>Mq)iA70Q60ojd2Jvo?d z{MPc>7TA^Sw;zt`@KM$Ozpv+x#dAr1=G;M^_^T5AuF9R}UD0|En{|Tfy)!k(nQFYwA zDV~%i)}-nF347D7(v3(r0b8jiS~ZQ6$!a;!)2Fu2Vl^ZbBz(&1DDxdrN5MNhY*R(A z>)j4-A86*2*D@crP5(xIXLc~NkvMerhj^-~M`mct-nr4Pjjk)4r{|u=r325EBF||N zlrh^0!H^G_(Cxns{XBz1@#3}xE#k-$cbBI3gPrFA_ox~ETg#UP)23d9HWgrh;jxkL zH80HR1gL-oRoySUZOPIi^Ty4wrsFy^wB(_vNaglY&?3{{aZQP7tXO8p)Ey^6VWPdo zh>R<)FwwLxBp}W4Uf@PT2+v1GEyoYueDq`4avdQhv5{waPNyykdQQQ=B}n^QHMv*I zZ#(Ol^t6Z;yh#WTx(kNj#lV$gjKcTS2VnJn`>Xfc64v=z)_!k_OfyKtIoCr_1#tGB3|)E-!^K zqduQ_O>1iUTRi(@H;{bC2R0gVnZXjP-LoK|?`$2l0aLdHs~-LUdC6pfJ8nOhlN&4! zKlLn=njA&?OzYxc{6jIR)~Mi>Ea?roy&&xa^AXkTmf!x{XWvJ=HC5FCsftunU)Q4S zKL^0UZEt`@2G5X#fkN7;xk{XzV2up6PA+qx0BR8sWVFP{z%WG6pc|L@N=6m+jGcp5 z*C;IyHiv=sVJl8=lg$o-tP!mYU;{m2u|75nE%f1N)e60bYR!O!296nveATf96G)ab z5|V{&20n{%RO;0{Av_dJ=WR&HJDTA>HV-y)_DHDQE@x|iNsM@J=EL)R^Y)yf<8f%?$#C-l^y)u$0p zSFi)S&=*m0ja=)7X0EC4l(9)EBvB4!-Pkc5^%-v;qZfR6KrhF9uzuZ7swE&AeRgax==*YfFfBMiH;{(_QKltR{fBsP}G~3{( zl>4A<rniW8cl{h?C4+?6Hv?2e4VI;3IWSB$B z0B=~}UaH}Z2LJFixLA-U)3PNKlHf>JI4<87twv^#a_{Nk$0xy{q7)*H8@i*0_7IXH6UP*NQ>2r11! zflBI>{J2Br0CV6_DiQ!x6|d~sV+AsWho1C*DbV+}<#wbsk^8^#rSSYlk|p^|Cke)@ zIygA!01jyc;wGNFK;Q1yQWNUo|JVNjeg^Nll#OW^xV$jzI?0zY!2J@b%@(gCNe#G` zNpuWpqYR=!Pu;Hk)p=id2&hQ#<}mMbT{6Juu?Q^e03SxDRXH`G>nEg6@~;W{TUSp} zSi+p#IMGVfCtYGM#kG6@*kA_n{@1fX8A_D;p%4g!SL}axAxJ>jJj#klaN}=9R)K#2CH?aLJ=U$Dhj6p-deZaS2^EEs zU-3KpxfHj1dJbpo`10kY%WQpK(CHx(hYLz)`)kbTRU41I)#G768=5ErEx`TDy{8k+ zM+-QjDD4^s&3v*{t*V<>NZ$X)7v#VI zNnKR__|Dc)YV+X@w4?D0fxEK|Da}|h~-F|nKM5l<4mO&ibYv{Qie7m=75*c>zQS!yN zm+bM;7}N5*=j)djn&a}wAc()QAj5|d? zn3}Oazklw@DFRsc1>ov%g^eEBJ%qn@{bPJt?{}_I8&c?;ni8 zS=j9NpM}AM+O?PXC>2FLRm5xIN`-`vXOfhH8QrfZ6*q9+P*S!2-Q7iv7GbXag3B!n zFP}r_Q;nukIF^T$arl`vYgur6@Ja4PM-7GDJcgKbNe}}Maz2eW4rImsyue91h5mxl zPw>2M&bo2qhNzHG`KBe{+iP!c7Zn%hjO)lvOr$*yY~-T1ef2co^8f-8p0xQ=yv0A; z?X$A6(^O(2*NXU@h!PNcIub`{jr@eL7suBo;V-FW0sLagKA{JrN|T{UE#WbsK3g5T0_olY*y8d*v<} zGQ~YX%|&sW7&CuDs7u0tJ^xeBrJnh9fA3O9Qs^tEpDW|iL4JRt1A3ary^1Q`m#zVx z{a>3akK-#1orU=lwTu`5$FEw8KPf@IUHFdD*4EbbSf@RPjdZOi$hqwLzH`HG2&?Q4 z(r21#VX}D~*A<3T=l7geh=FiQrr`tRau4&CS7?kajTP0kJvBpw=%J)(nW3JQ|0am@ z89jNNddAnKOvmbB`K&Uwji!S?w{R9BLP)FT>yt5NHEnsdiG7fXCfEPAGbxLFU}1A} zvslan`a2sdeW@rUQWp*$Xj zR8TS6M3~GGGVlZI&u3@3DLt(yXBSAseKm4FQ$&HaNS}u0oAHp*WONWx`%q6z6kj;A z1(Dq~%)J&T$`Z{Z2?Q)~1>mR85!#-3Ae?G{9>1e){wZV!>8x8UmNoav#uzeCH^4DH zEj?;bQT@J8?fiS!>V}p^5#SFcz43uLr*-_`*d9<7-UA9Y4zr&N@|GCX3=7KIac2K2dmo+Rc$+QH^QDVJSz>#nYZ|aFv$10IE2FZ4jFi^UZ}`*z2<>| zHJpF&zc5iwH|FTe8}OYyR1Et-%`nDwxqDMf&3!9K<#Nv>2IOLF;>41E<%C=Lj`FDcobJ4I5rcxO91-vJBuYS(%537ev)R?I4PP$l@Al2{5= zQk3iW@|0@7%7Ca?v`@)rJzFf#)-5&Ljo|H2F6!|Uk<3sP4)~=0vu=is#jo3_Ih= z41;+6%(auV3YU=|tpkO~UR+tzn*knCd@1jI2->_!H1Ht0QL_OxjztZt1Xe$A+7DvMj={rB* z^B(E$%Rgg%r^tz4MKHqF$(Xlfew!d)KYP+6BKDZQNS!(4lt#>ttSx_s*fW6WLGPSV zz+8j0FtVxSvIoB=oabo312D0v*OCuxj<8ws#H%{>s+F{{0ZtZRRS5aQc7=!(J)+jH zXO{7HaB|Yr*Vp&|{_WM_=A{11PVs_?udS1?#{$l-0v9V2MUc|Z+-lya0H=4KSmrW{ z^G6$TlBv%{Q2SFBmwHsMKCu^|VB*bGZ^E+cT-_tKx&%gxt0^8

    VDnNe5Js+yVuq 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 z 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 MujxO<`YuoY4^4jlA^-pY literal 0 HcmV?d00001 diff --git a/doc/workflow/img/cherry_pick_changes_mr_modal.png b/doc/workflow/img/cherry_pick_changes_mr_modal.png new file mode 100644 index 0000000000000000000000000000000000000000..96a80f4726de8be7ef0b78c15029484694738d87 GIT binary patch literal 225569 zcmeFZ_ghn4(>5HtqKJx7iWDg#2_Vut2ud$f5<-{WL3#%p2ug>5bm@=;LPz=qNEZTx zBB4o>66u{#zMboN@9*|G-e0~y;M)ftNp|*Ldu7(lnwfLXG3=$f0wo0<1qcM9R8oAd z1p-|&1A#6xUcC&Q!TtQ_I`DGQO;$+11{SSx#GMslF^61Y!m$J%6U-o%(0e$N#}@ zK*!m>O=Yb{&sH#%L)Vp%H|8m~t~UQx{K{VS^p$z!Ma74Qxk_c&B|zp3cC@v@@vT2HUQAPn5NXz$BpQkvwBuN33E z$)t7vMR8VpPeIbiqWzaVPGGwKJ!DoW_$2@Pk^HXH<6`OmI`hw?cZmOW;uZBXuK#tS zB#imq|2pyW`7N^S{(CsEgkJjZC*YmX?eFLR*NK#?X6ODs@kZ_cpT>XX@&8NXzp?;< z{y#`V+cfV=G({_~gX7we2w#Op{*m&iPv`f;-uO~?%zwvvQ`-7t2V@vuZ0>k5VcrJis6g2q^fAH0own_*+FSQ*42tEYac zu7%_ZWk!a95c1){%y6ENl0SPf%d7ZFlPWrB$F=|7+xg`1PPEcaO+y^4SZfv6^)i9F zL(YTi%7Wjmp59lgc^42#gpn>OSKFrl-UFKd>~Qo)GVNKKcZaR84pN!PQ%+C)Vi}(% z1Jt<{arof7E0HCp>sH+OxM*d=_{m>(`DP+o+4G;US5eg3w+Yv*#;#c|=rjL&6W#Z2 z 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�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%0DAj?jJ}k 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 eos ) end - + def another_sample_commit OpenStruct.new( id: "e56497bb5f03a90a51293fc6d516788730953899", From 6f31262f747b7c1ed0173b6615ef0990967a0db6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Apr 2016 08:48:41 +0100 Subject: [PATCH 269/573] Fixed overlap in header on mobile --- app/assets/stylesheets/framework/header.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 3f015427d0..c303380764 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -71,7 +71,7 @@ header { .header-content { position: relative; height: $header-height; - padding-right: 20px; + padding-right: 40px; @media (min-width: $screen-sm-min) { padding-right: 0; @@ -122,6 +122,10 @@ header { } } + .project-item-select-holder { + display: inline; + } + .impersonation i { color: $red-normal; } From fce97dfce7f7be141c48892a63e3fe95e8fb756b Mon Sep 17 00:00:00 2001 From: Con Vissenberg Date: Mon, 18 Apr 2016 08:15:15 +0000 Subject: [PATCH 270/573] "git lfs init" is deprecated, use "git lfs install" instead. --- 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 ba91685a20..31620044b1 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 ``` From d7a4a2fe7577f2ca17d28f10d312ed98d5253ae4 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 18 Apr 2016 10:52:01 +0200 Subject: [PATCH 271/573] When a project wiki is disabled skip it for fsck --- .../single_repository_worker.rb | 6 ++-- .../single_repository_worker_spec.rb | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 spec/workers/repository_check/single_repository_worker_spec.rb diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb index e54ae86d06..a76729e3c7 100644 --- a/app/workers/repository_check/single_repository_worker.rb +++ b/app/workers/repository_check/single_repository_worker.rb @@ -15,10 +15,10 @@ module RepositoryCheck 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 - [project.repository, project.wiki.repository].map do |repository| - git_fsck(repository.path_to_repo) - end.all? + repositories.map { |repository| git_fsck(repository.path_to_repo) }.all? end def git_fsck(path) diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb new file mode 100644 index 0000000000..3179dd70b0 --- /dev/null +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require 'fileutils' + +describe RepositoryCheck::SingleRepositoryWorker do + subject { described_class.new } + + it 'fails if the wiki repository is broken' do + project = create(:project_empty_repo, wiki_enabled: true) + project.create_wiki + + # Test sanity: everything should be fine before the wiki repo is broken + subject.perform(project.id) + expect(project.reload.last_repository_check_failed).to eq(false) + + FileUtils.rm_rf(project.wiki.repository.path_to_repo) + subject.perform(project.id) + + expect(project.reload.last_repository_check_failed).to eq(true) + end + + 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 + FileUtils.rm_rf(project.wiki.repository.path_to_repo) + + subject.perform(project.id) + + expect(project.reload.last_repository_check_failed).to eq(false) + end +end From fdf6ca6c01ec54bf86ee72d5adbaf4ae3cff8483 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 18 Apr 2016 10:58:40 +0200 Subject: [PATCH 272/573] Improve 'auto fsck' admin emails --- app/mailers/repository_check_mailer.rb | 2 +- app/views/repository_check_mailer/notify.html.haml | 3 +++ app/views/repository_check_mailer/notify.text.haml | 3 +++ spec/mailers/repository_check_mailer_spec.rb | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb index 2bff5b63cc..21db2fe04a 100644 --- a/app/mailers/repository_check_mailer.rb +++ b/app/mailers/repository_check_mailer.rb @@ -8,7 +8,7 @@ class RepositoryCheckMailer < BaseMailer mail( to: User.admins.pluck(:email), - subject: @message + subject: "GitLab Admin | #{@message}" ) end end diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml index df16f50357..a585147ddd 100644 --- a/app/views/repository_check_mailer/notify.html.haml +++ b/app/views/repository_check_mailer/notify.html.haml @@ -3,3 +3,6 @@ %p = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1) + +%p + You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}. diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml index 02f3f80288..93db151329 100644 --- a/app/views/repository_check_mailer/notify.text.haml +++ b/app/views/repository_check_mailer/notify.text.haml @@ -1,3 +1,6 @@ #{@message}. \ View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)} + +You are receiving this message because you are a GitLab administrator +for #{Gitlab.config.gitlab.url}. diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb index 583bf15176..00613c7b67 100644 --- a/spec/mailers/repository_check_mailer_spec.rb +++ b/spec/mailers/repository_check_mailer_spec.rb @@ -15,7 +15,7 @@ describe RepositoryCheckMailer do it 'mentions the number of failed checks' do mail = described_class.notify(3) - expect(mail).to have_subject '3 projects failed their last repository check' + expect(mail).to have_subject 'GitLab Admin | 3 projects failed their last repository check' end end end From 1394ccfe834c4e34bed701d206c77961a594fa23 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 18 Apr 2016 11:03:53 +0200 Subject: [PATCH 273/573] Destroy wikis uniformly --- .../repository_check/single_repository_worker_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index 3179dd70b0..087e4c667d 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) - FileUtils.rm_rf(project.wiki.repository.path_to_repo) + destroy_wiki(project) subject.perform(project.id) expect(project.reload.last_repository_check_failed).to eq(true) @@ -21,10 +21,14 @@ 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 - FileUtils.rm_rf(project.wiki.repository.path_to_repo) + destroy_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) + end end From 7b6785b3b1d03ef8512e098285744e9956ec0891 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 15 Apr 2016 18:31:01 +0200 Subject: [PATCH 274/573] Use Module#prepend for method instrumentation By using Module#prepend we can define a Module containing all proxy methods. This removes the need for setting up crazy method alias chains and in turn prevents us from having to deal with all that madness (e.g. methods calling each other recursively). Fixes gitlab-org/gitlab-ce#15281 --- CHANGELOG | 1 + lib/gitlab/metrics/instrumentation.rb | 37 ++++++++++---- .../gitlab/metrics/instrumentation_spec.rb | 51 ++++++++++++++----- 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ede0c00e90..c29c207ca7 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.7.0 (unreleased) + - Method instrumentation now uses Module#prepend instead of aliasing methods - The Projects::HousekeepingService class has extra instrumentation - All service classes (those residing in app/services) are now instrumented - Developers can now add custom tags to transactions diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index face1921d2..708ef79f30 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -11,6 +11,8 @@ module Gitlab module Instrumentation SERIES = 'method_calls' + PROXY_IVAR = :@__gitlab_instrumentation_proxy + def self.configure yield self end @@ -91,6 +93,18 @@ module Gitlab end end + # Returns true if a module is instrumented. + # + # mod - The module to check + def self.instrumented?(mod) + mod.instance_variable_defined?(PROXY_IVAR) + end + + # Returns the proxy module (if any) of `mod`. + def self.proxy_module(mod) + mod.instance_variable_get(PROXY_IVAR) + end + # Instruments a method. # # type - The type (:class or :instance) of method to instrument. @@ -99,9 +113,8 @@ module Gitlab def self.instrument(type, mod, name) return unless Metrics.enabled? - name = name.to_sym - alias_name = :"_original_#{name}" - target = type == :instance ? mod : mod.singleton_class + name = name.to_sym + target = type == :instance ? mod : mod.singleton_class if type == :instance target = mod @@ -113,6 +126,12 @@ module Gitlab method = mod.method(name) end + unless instrumented?(target) + target.instance_variable_set(PROXY_IVAR, Module.new) + end + + proxy_module = self.proxy_module(target) + # Some code out there (e.g. the "state_machine" Gem) checks the arity of # a method to make sure it only passes arguments when the method expects # any. If we were to always overwrite a method to take an `*args` @@ -125,17 +144,13 @@ module Gitlab args_signature = '*args, &block' end - send_signature = "__send__(#{alias_name.inspect}, #{args_signature})" - - target.class_eval <<-EOF, __FILE__, __LINE__ + 1 - alias_method #{alias_name.inspect}, #{name.inspect} - + proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{name}(#{args_signature}) trans = Gitlab::Metrics::Instrumentation.transaction if trans start = Time.now - retval = #{send_signature} + retval = super duration = (Time.now - start) * 1000.0 if duration >= Gitlab::Metrics.method_call_threshold @@ -148,10 +163,12 @@ module Gitlab retval else - #{send_signature} + super end end EOF + + target.prepend(proxy_module) end # Small layer of indirection to make it easier to stub out the current diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index ad4290c43b..5c885a7a98 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -33,8 +33,16 @@ describe Gitlab::Metrics::Instrumentation do described_class.instrument_method(@dummy, :foo) end - it 'renames the original method' do - expect(@dummy).to respond_to(:_original_foo) + it 'instruments the Class' do + target = @dummy.singleton_class + + expect(described_class.instrumented?(target)).to eq(true) + end + + it 'defines a proxy method' do + mod = described_class.proxy_module(@dummy.singleton_class) + + expect(mod.method_defined?(:foo)).to eq(true) end it 'calls the instrumented method with the correct arguments' do @@ -76,6 +84,14 @@ describe Gitlab::Metrics::Instrumentation do expect(dummy.method(:test).arity).to eq(0) end + + describe 'when a module is instrumented multiple times' do + it 'calls the instrumented method with the correct arguments' do + described_class.instrument_method(@dummy, :foo) + + expect(@dummy.foo).to eq('foo') + end + end end describe 'with metrics disabled' do @@ -86,7 +102,9 @@ describe Gitlab::Metrics::Instrumentation do it 'does not instrument the method' do described_class.instrument_method(@dummy, :foo) - expect(@dummy).to_not respond_to(:_original_foo) + target = @dummy.singleton_class + + expect(described_class.instrumented?(target)).to eq(false) end end end @@ -100,8 +118,14 @@ describe Gitlab::Metrics::Instrumentation do instrument_instance_method(@dummy, :bar) end - it 'renames the original method' do - expect(@dummy.method_defined?(:_original_bar)).to eq(true) + it 'instruments instances of the Class' do + expect(described_class.instrumented?(@dummy)).to eq(true) + end + + it 'defines a proxy method' do + mod = described_class.proxy_module(@dummy) + + expect(mod.method_defined?(:bar)).to eq(true) end it 'calls the instrumented method with the correct arguments' do @@ -144,7 +168,7 @@ describe Gitlab::Metrics::Instrumentation do described_class. instrument_instance_method(@dummy, :bar) - expect(@dummy.method_defined?(:_original_bar)).to eq(false) + expect(described_class.instrumented?(@dummy)).to eq(false) end end end @@ -167,18 +191,17 @@ describe Gitlab::Metrics::Instrumentation do it 'recursively instruments a class hierarchy' do described_class.instrument_class_hierarchy(@dummy) - expect(@child1).to respond_to(:_original_child1_foo) - expect(@child2).to respond_to(:_original_child2_foo) + expect(described_class.instrumented?(@child1.singleton_class)).to eq(true) + expect(described_class.instrumented?(@child2.singleton_class)).to eq(true) - expect(@child1.method_defined?(:_original_child1_bar)).to eq(true) - expect(@child2.method_defined?(:_original_child2_bar)).to eq(true) + expect(described_class.instrumented?(@child1)).to eq(true) + expect(described_class.instrumented?(@child2)).to eq(true) end it 'does not instrument the root module' do described_class.instrument_class_hierarchy(@dummy) - expect(@dummy).to_not respond_to(:_original_foo) - expect(@dummy.method_defined?(:_original_bar)).to eq(false) + expect(described_class.instrumented?(@dummy)).to eq(false) end end @@ -190,7 +213,7 @@ describe Gitlab::Metrics::Instrumentation do it 'instruments all public class methods' do described_class.instrument_methods(@dummy) - expect(@dummy).to respond_to(:_original_foo) + expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true) end it 'only instruments methods directly defined in the module' do @@ -223,7 +246,7 @@ describe Gitlab::Metrics::Instrumentation do it 'instruments all public instance methods' do described_class.instrument_instance_methods(@dummy) - expect(@dummy.method_defined?(:_original_bar)).to eq(true) + expect(described_class.instrumented?(@dummy)).to eq(true) end it 'only instruments methods directly defined in the module' do From c24640c721aed987a22c2e097cada73145cfb12b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Apr 2016 11:06:30 +0100 Subject: [PATCH 275/573] Fixed issue with tasklist toggling sidebar refresh Closes #15270 --- app/views/projects/issues/update.js.haml | 3 --- app/views/projects/merge_requests/update.js.haml | 3 --- 2 files changed, 6 deletions(-) diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml index 986d8c220d..e69de29bb2 100644 --- a/app/views/projects/issues/update.js.haml +++ b/app/views/projects/issues/update.js.haml @@ -1,3 +0,0 @@ -$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"; -$('aside.right-sidebar').effect('highlight'); -new IssuableContext(); diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml index 9cce5660e1..e69de29bb2 100644 --- a/app/views/projects/merge_requests/update.js.haml +++ b/app/views/projects/merge_requests/update.js.haml @@ -1,3 +0,0 @@ -$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"; -$('aside.right-sidebar').effect('highlight'); -new IssuableContext(); From 4b025ba02bb19c72a6872d0c6821fa0b10c58800 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 12:30:42 +0200 Subject: [PATCH 276/573] Document the feature freeze of stable branches Fixes gitlab-org/gitlab-ce#15183 --- PROCESS.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/PROCESS.md b/PROCESS.md index cad45d23df..e34f59c6bc 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide]. +## Feature Freeze + +5 working days before the 22nd the stable branches for the upcoming release will +be frozen for major changes. Merge requests may still be merged into master +during this period. By freezing the stable branches prior to a release there's +no need to worry about last minute merge requests potentially breaking a lot of +things. + +What is considered to be a major change is determined on a case by case basis as +this definition depends very much on the context of changes. For example, a 5 +line change might have a big impact on the entire application. Ultimately the +decision will be made by those reviewing a merge request and the release +manager. + +During the feature freeze all merge requests that are meant to go into the next +release should have the correct milestone assigned _and_ have the label +~"Pick into Stable" set. Merge requests without a milestone and this label will +not be merged into any stable branches. + ## Copy & paste responses ### Improperly formatted issue From 651c3e841d6b0ca46edb6aa5cc62d42051a46d61 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 15 Apr 2016 12:14:27 +0200 Subject: [PATCH 277/573] Instrument Repository.clean_old_archives --- CHANGELOG | 1 + app/models/repository.rb | 8 +++++--- spec/models/repository_spec.rb | 25 ++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c29c207ca7..7ef374b04d 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.7.0 (unreleased) - Method instrumentation now uses Module#prepend instead of aliasing methods + - Repository.clean_old_archives is now instrumented - The Projects::HousekeepingService class has extra instrumentation - All service classes (those residing in app/services) are now instrumented - Developers can now add custom tags to transactions diff --git a/app/models/repository.rb b/app/models/repository.rb index 308c590e3f..589756f853 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -12,11 +12,13 @@ class Repository attr_accessor :path_with_namespace, :project def self.clean_old_archives - repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path + Gitlab::Metrics.measure(:clean_old_archives) do + repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path - return unless File.directory?(repository_downloads_path) + return unless File.directory?(repository_downloads_path) - Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) + Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) + end end def initialize(path_with_namespace, project) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index c163001b7c..f30a21e79a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -910,9 +910,32 @@ describe Repository, models: true do end end + describe '.clean_old_archives' do + let(:path) { Gitlab.config.gitlab.repository_downloads_path } + + context 'when the downloads directory does not exist' do + it 'does not remove any archives' do + expect(File).to receive(:directory?).with(path).and_return(false) + + expect(Gitlab::Popen).not_to receive(:popen) + + described_class.clean_old_archives + end + end + + context 'when the downloads directory exists' do + it 'removes old archives' do + expect(File).to receive(:directory?).with(path).and_return(true) + + expect(Gitlab::Popen).to receive(:popen) + + described_class.clean_old_archives + end + end + end + def create_remote_branch(remote_name, branch_name, target) rugged = repository.rugged rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target) end - end From 1a57601d8cac0de35f301f8a8094c61fc94ed1cb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Apr 2016 12:00:20 +0100 Subject: [PATCH 278/573] Updated print style Closes #14201 --- app/assets/stylesheets/print.scss | 44 ++++++++++++++----- .../projects/notes/_notes_with_form.html.haml | 2 +- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss index 1be0551ad3..a30b649257 100644 --- a/app/assets/stylesheets/print.scss +++ b/app/assets/stylesheets/print.scss @@ -1,17 +1,37 @@ -/* Generic print styles */ -header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;} -.profiler-results {display: none;} - -/* Styles targeted specifically at printing files */ -.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;} -.file-title {display: none;} -.file-holder {border: none;} - .wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; } .wiki h1 {font-size: 30px;} .wiki h2 {font-size: 22px;} .wiki h3 {font-size: 18px; font-weight: bold; } -.sidebar-wrapper { display: none; } -.nav { display: none; } -.btn { display: none; } +header, +nav, +nav.main-nav, +nav.navbar-collapse, +nav.navbar-collapse.collapse, +.profiler-results, +.tree-ref-holder, +.tree-holder .breadcrumb, +.blob-commit-info, +.file-title, +.file-holder, +.sidebar-wrapper, +.nav, +.btn, +ul.notes-form, +.merge-request-ci-status .ci-status-link:after, +.issuable-gutter-toggle, +.gutter-toggle, +.issuable-details .content-block-small, +.edit-link, +.note-action-button { + display: none!important; +} + +.page-gutter { + padding-top: 0; + padding-left: 0; +} + +.right-sidebar { + top: 0; +} diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index cc42aab5c5..1c39ce897a 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -1,6 +1,6 @@ %ul#notes-list.notes.main-notes-list.timeline = render "projects/notes/notes" -%ul.notes.timeline +%ul.notes.notes-form.timeline %li.timeline-entry - if can? current_user, :create_note, @project .timeline-icon.hidden-xs.hidden-sm From 167fa5c459f275e17a4d8f7155f15004cc29c28d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Apr 2016 12:02:39 +0100 Subject: [PATCH 279/573] CHANGELOG item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index de52033078..9deb043787 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -76,6 +76,7 @@ v 8.7.0 (unreleased) - Add encrypted credentials for imported projects and migrate old ones - Author and participants are displayed first on users autocompletion - Show number sign on external issue reference text (Florent Baldino) + - Updated print style for issues v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 From a1363d39c6fe79d830dbce468c02880d2a5d7996 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 8 Apr 2016 11:02:26 +0200 Subject: [PATCH 280/573] Add `variables` keyword to job in CI config YAML --- app/models/ci/build.rb | 12 +++++- lib/ci/gitlab_ci_yaml_processor.rb | 3 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 44 ++++++++++++++------ spec/models/build_spec.rb | 16 +++++++ 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7d33838044..89a9eb7633 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -365,9 +365,11 @@ module Ci self.update(erased_by: user, erased_at: Time.now) end - private - def yaml_variables + global_yaml_variables + job_yaml_variables + end + + def global_yaml_variables if commit.config_processor commit.config_processor.variables.map do |key, value| { key: key, value: value, public: true } @@ -377,6 +379,12 @@ module Ci end end + def job_yaml_variables + options[:variables].to_h.map do |key, value| + { key: key, value: value, public: true } + end + end + def project_variables project.variables.map do |variable| { key: variable.key, value: variable.value, public: false } diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index b7209c1414..712dc4492c 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -7,7 +7,7 @@ module Ci ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache, - :dependencies] + :dependencies, :variables] attr_reader :before_script, :image, :services, :variables, :path, :cache @@ -85,6 +85,7 @@ module Ci artifacts: job[:artifacts], cache: job[:cache] || @cache, dependencies: job[:dependencies], + variables: job[:variables], }.compact } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index dcb8a3451b..a3a0d06e14 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -346,19 +346,39 @@ module Ci end describe "Variables" do - it "returns variables when defined" do - variables = { - var1: "value1", - var2: "value2", - } - config = YAML.dump({ - variables: variables, - before_script: ["pwd"], - rspec: { script: "rspec" } - }) + context 'when global variables are defined' do + it 'returns variables' do + variables = { + var1: "value1", + var2: "value2", + } + config = YAML.dump({ + variables: variables, + before_script: ["pwd"], + rspec: { script: "rspec" } + }) - config_processor = GitlabCiYamlProcessor.new(config, path) - expect(config_processor.variables).to eq(variables) + config_processor = GitlabCiYamlProcessor.new(config, path) + expect(config_processor.variables).to eq(variables) + end + end + + context 'when job variables are defined' do + let(:job_variables) { { KEY1: 'value1', SOME_KEY_2: 'value2'} } + let(:yaml_config) do + YAML.dump( + { before_script: ['pwd'], + rspec: { + variables: job_variables, + script: 'rspec' } + }) + end + + it 'appends job variable to job attributes' do + config = GitlabCiYamlProcessor.new(yaml_config, path) + + expect(config.builds.first[:options][:variables]).to eq job_variables + end end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index b745780804..ee44a4c5f1 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -238,6 +238,22 @@ describe Ci::Build, models: true do it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) } end + + context 'when job variables are defined' do + before { build.update_attribute(:options, variables: job_variables) } + + context 'when job variables are unique' do + let(:job_variables) { { KEY1: 'value1', KEY2: 'value2' } } + let(:resulting_variables) do + [{ key: :KEY1, value: 'value1', public: true }, + { key: :KEY2, value: 'value2', public: true }] + end + + it 'includes job variables' do + expect(subject).to include(*resulting_variables) + end + end + end end end end From b578fbfb8572860490cdfd0163bfbf5f999bb1e6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 11 Apr 2016 13:09:46 +0200 Subject: [PATCH 281/573] Make it possible to override build variables --- app/models/ci/build.rb | 3 ++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/models/build_spec.rb | 24 ++++++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 89a9eb7633..61d39caeb7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -147,7 +147,8 @@ module Ci end def variables - predefined_variables + yaml_variables + project_variables + trigger_variables + (predefined_variables + yaml_variables + project_variables + trigger_variables) + .reverse.uniq { |var| var[:key] }.reverse end def merge_request diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index a3a0d06e14..c2908f855e 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -364,7 +364,7 @@ module Ci end context 'when job variables are defined' do - let(:job_variables) { { KEY1: 'value1', SOME_KEY_2: 'value2'} } + let(:job_variables) { { KEY1: 'value1', SOME_KEY_2: 'value2' } } let(:yaml_config) do YAML.dump( { before_script: ['pwd'], diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index ee44a4c5f1..94d51435f3 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -240,17 +240,31 @@ describe Ci::Build, models: true do end context 'when job variables are defined' do + def result_variables + job_variables.map do |key, value| + { key: key, value: value, public: true } + end + end + before { build.update_attribute(:options, variables: job_variables) } context 'when job variables are unique' do let(:job_variables) { { KEY1: 'value1', KEY2: 'value2' } } - let(:resulting_variables) do - [{ key: :KEY1, value: 'value1', public: true }, - { key: :KEY2, value: 'value2', public: true }] - end it 'includes job variables' do - expect(subject).to include(*resulting_variables) + expect(subject).to include(*result_variables) + end + end + + context 'when job variable has same key other variable has' do + let(:job_variables) { { CI_BUILD_NAME: 'overridden' } } + + it 'contains job yaml variable' do + expect(subject).to include(*result_variables) + end + + it 'contains only one variable with this key' do + expect(subject.count { |var| var[:key] == :CI_BUILD_NAME } ).to eq 1 end end end From b7946b50fc1b23b1974f7d0306c06f6d92cc8466 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Apr 2016 12:18:46 +0200 Subject: [PATCH 282/573] Read job variables directly from gitlab CI config --- app/models/ci/build.rb | 8 +++++-- lib/ci/gitlab_ci_yaml_processor.rb | 12 +++++++++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- spec/models/build_spec.rb | 22 ++++++++------------ spec/support/gitlab_stubs/gitlab_ci.yml | 17 +++++++++------ 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61d39caeb7..6c4ee2a0d4 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -381,8 +381,12 @@ module Ci end def job_yaml_variables - options[:variables].to_h.map do |key, value| - { key: key, value: value, public: true } + if commit.config_processor + commit.config_processor.job_variables(name).map do |key, value| + { key: key, value: value, public: true } + end + else + [] end end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 712dc4492c..548c6250e0 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -9,7 +9,7 @@ module Ci :allow_failure, :type, :stage, :when, :artifacts, :cache, :dependencies, :variables] - attr_reader :before_script, :image, :services, :variables, :path, :cache + attr_reader :before_script, :image, :services, :path, :cache def initialize(config, path = nil) @config = YAML.safe_load(config, [Symbol], [], true) @@ -40,6 +40,15 @@ module Ci @stages || DEFAULT_STAGES end + def variables + @variables + end + + def job_variables(name) + job = @jobs[name.to_sym] + job ? job[:variables] : [] + end + private def initial_parsing @@ -85,7 +94,6 @@ module Ci artifacts: job[:artifacts], cache: job[:cache] || @cache, dependencies: job[:dependencies], - variables: job[:variables], }.compact } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index c2908f855e..04b1d8baeb 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -374,10 +374,10 @@ module Ci }) end - it 'appends job variable to job attributes' do + it 'returns job variables' do config = GitlabCiYamlProcessor.new(yaml_config, path) - expect(config.builds.first[:options][:variables]).to eq job_variables + expect(config.job_variables(:rspec)).to eq job_variables end end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 94d51435f3..26a063de1e 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -240,31 +240,27 @@ describe Ci::Build, models: true do end context 'when job variables are defined' do - def result_variables - job_variables.map do |key, value| - { key: key, value: value, public: true } - end - end - - before { build.update_attribute(:options, variables: job_variables) } - context 'when job variables are unique' do - let(:job_variables) { { KEY1: 'value1', KEY2: 'value2' } } + before { allow(build).to receive(:name) { 'staging' } } it 'includes job variables' do - expect(subject).to include(*result_variables) + expect(subject).to include( + { key: :KEY1, value: 'value1', public: true }, + { key: :KEY2, value: 'value2', public: true } + ) end end context 'when job variable has same key other variable has' do - let(:job_variables) { { CI_BUILD_NAME: 'overridden' } } + before { allow(build).to receive(:name) { 'production' } } it 'contains job yaml variable' do - expect(subject).to include(*result_variables) + expect(subject).to include(key: :DB_NAME, value: 'mysql', + public: true) end it 'contains only one variable with this key' do - expect(subject.count { |var| var[:key] == :CI_BUILD_NAME } ).to eq 1 + expect(subject.count { |var| var[:key] == :DB_NAME } ).to eq 1 end end end diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml index a5b256bd3e..e55a61b2b9 100644 --- a/spec/support/gitlab_stubs/gitlab_ci.yml +++ b/spec/support/gitlab_stubs/gitlab_ci.yml @@ -4,7 +4,7 @@ services: before_script: - gem install bundler - - bundle install + - bundle install - bundle exec rake db:create variables: @@ -17,7 +17,7 @@ types: rspec: script: "rake spec" - tags: + tags: - ruby - postgres only: @@ -26,27 +26,32 @@ rspec: spinach: script: "rake spinach" allow_failure: true - tags: + tags: - ruby - mysql except: - tags staging: + variables: + KEY1: value1 + KEY2: value2 script: "cap deploy stating" type: deploy - tags: + tags: - ruby - mysql except: - stable production: + variables: + DB_NAME: mysql type: deploy - script: + script: - cap deploy production - cap notify - tags: + tags: - ruby - mysql only: From 3dec6e262984faa18b235db4bbd669ccdc80fc3f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Apr 2016 12:28:27 +0200 Subject: [PATCH 283/573] Rename method that returns global envs in CI conf --- app/models/ci/build.rb | 2 +- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6c4ee2a0d4..1146028b9a 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -372,7 +372,7 @@ module Ci def global_yaml_variables if commit.config_processor - commit.config_processor.variables.map do |key, value| + commit.config_processor.global_variables.map do |key, value| { key: key, value: value, public: true } end else diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 548c6250e0..f69b861c98 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -40,7 +40,7 @@ module Ci @stages || DEFAULT_STAGES end - def variables + def global_variables @variables end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 04b1d8baeb..ba5f7b0e09 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -359,7 +359,7 @@ module Ci }) config_processor = GitlabCiYamlProcessor.new(config, path) - expect(config_processor.variables).to eq(variables) + expect(config_processor.global_variables).to eq(variables) end end From 1f3248644ef98879a8a4c31d5be0de9a774cc32a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Apr 2016 14:57:22 +0200 Subject: [PATCH 284/573] Make CI config return empty array if no job variables --- lib/ci/gitlab_ci_yaml_processor.rb | 7 +++++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index f69b861c98..e152de590d 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -45,8 +45,11 @@ module Ci end def job_variables(name) - job = @jobs[name.to_sym] - job ? job[:variables] : [] + if job = @jobs[name.to_sym] + job[:variables] || [] + else + [] + end end private diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ba5f7b0e09..45f08c95cc 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -380,6 +380,18 @@ module Ci expect(config.job_variables(:rspec)).to eq job_variables end end + + context 'when job variables are not defined' do + it 'returns empty array' do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec" } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + expect(config_processor.job_variables(:rspec)).to eq [] + end + end end describe "When" do From bdb06fa0437ebc1b03377d95763d1fefdcfd0441 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Apr 2016 20:43:23 +0200 Subject: [PATCH 285/573] Improve build specs for job environment variables --- spec/models/build_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 26a063de1e..5ee40c2253 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -240,8 +240,11 @@ describe Ci::Build, models: true do end context 'when job variables are defined' do + ## + # Job-level variables are defined in gitlab_ci.yml fixture + # context 'when job variables are unique' do - before { allow(build).to receive(:name) { 'staging' } } + let(:build) { create(:ci_build, name: 'staging') } it 'includes job variables' do expect(subject).to include( @@ -252,7 +255,7 @@ describe Ci::Build, models: true do end context 'when job variable has same key other variable has' do - before { allow(build).to receive(:name) { 'production' } } + let(:build) { create(:ci_build, name: 'production') } it 'contains job yaml variable' do expect(subject).to include(key: :DB_NAME, value: 'mysql', From 9a74b92d2afe028453c3fde7221fce20bcb073d9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Apr 2016 20:45:18 +0200 Subject: [PATCH 286/573] Add Changelog entry for job-level variables --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 7ef374b04d..b929f1fd7b 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.7.0 (unreleased) - Method instrumentation now uses Module#prepend instead of aliasing methods - Repository.clean_old_archives is now instrumented + - Add support for environment variables on a job level in CI configuration file - The Projects::HousekeepingService class has extra instrumentation - All service classes (those residing in app/services) are now instrumented - Developers can now add custom tags to transactions From a6260b1eb7b4f30817d0cc5dbd80c860b5ed8a31 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 16 Apr 2016 20:21:01 +0200 Subject: [PATCH 287/573] Remove code that removes duplicate CI variables At this point this is being handled by GitLab Runner and we need to introduce this as a separate merge request. --- app/models/ci/build.rb | 3 +-- spec/models/build_spec.rb | 13 ------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 1146028b9a..85ef0523b3 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -147,8 +147,7 @@ module Ci end def variables - (predefined_variables + yaml_variables + project_variables + trigger_variables) - .reverse.uniq { |var| var[:key] }.reverse + predefined_variables + yaml_variables + project_variables + trigger_variables end def merge_request diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 5ee40c2253..b5d356aa06 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -253,19 +253,6 @@ describe Ci::Build, models: true do ) end end - - context 'when job variable has same key other variable has' do - let(:build) { create(:ci_build, name: 'production') } - - it 'contains job yaml variable' do - expect(subject).to include(key: :DB_NAME, value: 'mysql', - public: true) - end - - it 'contains only one variable with this key' do - expect(subject.count { |var| var[:key] == :DB_NAME } ).to eq 1 - end - end end end end From cf3e3effb01215c2556ba509d3cb4debe1d901e8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 16 Apr 2016 20:52:01 +0200 Subject: [PATCH 288/573] Minor refactoring in code related to job variables --- lib/ci/gitlab_ci_yaml_processor.rb | 6 +-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 43 +++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index e152de590d..209da7ad81 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -45,11 +45,7 @@ module Ci end def job_variables(name) - if job = @jobs[name.to_sym] - job[:variables] || [] - else - [] - end + @jobs[name.to_sym].try(:fetch, :variables, []) || [] end private diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 45f08c95cc..9a014c4b6c 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -345,50 +345,55 @@ module Ci end end - describe "Variables" do + describe 'Variables' do context 'when global variables are defined' do - it 'returns variables' do + it 'returns global variables' do variables = { - var1: "value1", - var2: "value2", + VAR1: 'value1', + VAR2: 'value2', } + config = YAML.dump({ - variables: variables, - before_script: ["pwd"], - rspec: { script: "rspec" } - }) + variables: variables, + before_script: ['pwd'], + rspec: { script: 'rspec' } + }) config_processor = GitlabCiYamlProcessor.new(config, path) + expect(config_processor.global_variables).to eq(variables) end end context 'when job variables are defined' do - let(:job_variables) { { KEY1: 'value1', SOME_KEY_2: 'value2' } } - let(:yaml_config) do - YAML.dump( + it 'returns job variables' do + variables = { + KEY1: 'value1', + SOME_KEY_2: 'value2' + } + + config = YAML.dump( { before_script: ['pwd'], rspec: { - variables: job_variables, + variables: variables, script: 'rspec' } }) - end - it 'returns job variables' do - config = GitlabCiYamlProcessor.new(yaml_config, path) + config_processor = GitlabCiYamlProcessor.new(config, path) - expect(config.job_variables(:rspec)).to eq job_variables + expect(config_processor.job_variables(:rspec)).to eq variables end end context 'when job variables are not defined' do it 'returns empty array' do config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec" } - }) + before_script: ['pwd'], + rspec: { script: 'rspec' } + }) config_processor = GitlabCiYamlProcessor.new(config, path) + expect(config_processor.job_variables(:rspec)).to eq [] end end From 5d89bdb16a9340c7d8b43efc03dba623f8fb4d31 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 16 Apr 2016 21:17:05 +0200 Subject: [PATCH 289/573] Add documentation for job-level build variables --- doc/ci/variables/README.md | 4 +++- doc/ci/yaml/README.md | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index b0e53cbc26..35dac4c933 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -70,12 +70,14 @@ These variables can be later used in all executed commands and scripts. The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. +Variables can be defined on a global level, but also on a job level. + More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** -GitLab CI allows you to define per-project **Secure Variables** that are set in build environment. +GitLab CI allows you to define per-project **Secure Variables** that are set in build environment. The secure variables are stored out of the repository (the `.gitlab-ci.yml`). The variables are securely passed to GitLab Runner and are available in build environment. It's desired method to use them for storing passwords, secret keys or whatever you want. diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index abb6e97e5e..ea7d6dfa46 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -23,6 +23,7 @@ If you want a quick introduction to GitLab CI, follow our - [Jobs](#jobs) - [script](#script) - [stage](#stage) + - [job variables](#job-variables) - [only and except](#only-and-except) - [tags](#tags) - [when](#when) @@ -174,6 +175,8 @@ These variables can be later used in all executed commands and scripts. The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. +Variables can be also defined on [job level](#job-variables). + ### cache >**Note:** @@ -324,6 +327,7 @@ job_name: | services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | | stage | no | Defines a build stage (default: `test`) | | type | no | Alias for `stage` | +| variables | no | Define build variables on a job level | | only | no | Defines a list of git refs for which build is created | | except | no | Defines a list of git refs for which build is not created | | tags | no | Defines a list of tags which are used to select Runner | @@ -414,6 +418,23 @@ job: The above example will run `job` for all branches on `gitlab-org/gitlab-ce`, except master. +### job variables + +It is possible to define build variables using a `variables` keyword on a job +level. It works basically the same way like it's global-level equivalent but +allows you to define a job specific build variables. + +When `variables` keyword is used on a job level, it can override global YAML +build variables and predefined variables. + +Build variables priority is defined as follows: + +* predefined variables +* global YAML variables +* job YAML variables +* secure variables +* trigger variables + ### tags `tags` is used to select specific Runners from the list of all Runners that are From a2649739055842ce519fab5a8b0c68dbf23cb35a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 16 Apr 2016 22:04:59 +0200 Subject: [PATCH 290/573] Improve grammar in CI documentation for build vars --- doc/ci/variables/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 35dac4c933..f321a618f3 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -70,17 +70,20 @@ These variables can be later used in all executed commands and scripts. The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. -Variables can be defined on a global level, but also on a job level. +Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** -GitLab CI allows you to define per-project **Secure Variables** that are set in build environment. +GitLab CI allows you to define per-project **Secure Variables** that are set in +the build environment. The secure variables are stored out of the repository (the `.gitlab-ci.yml`). -The variables are securely passed to GitLab Runner and are available in build environment. -It's desired method to use them for storing passwords, secret keys or whatever you want. +The variables are securely passed to GitLab Runner and are available in the +build environment. +It's desired method to use them for storing passwords, secret keys or whatever +you want. **The value of the variable can be shown in build log if explicitly asked to do so.** If your project is public or internal you can make the builds private. From a2957291b3a28eff3988dfc8bdcff4b99b20a1b3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 17 Apr 2016 20:08:02 +0200 Subject: [PATCH 291/573] Update documentation on job level build variables --- doc/ci/variables/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index f321a618f3..e1df9e5f54 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -1,17 +1,19 @@ ## Variables + When receiving a build from GitLab CI, the runner prepares the build environment. It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables** The variables can be overwritten. They take precedence over each other in this order: 1. Secure variables -1. YAML-defined variables +1. YAML-defined job-leve variables +1. YAML-defined global variables 1. Predefined variables For example, if you define: -1. API_TOKEN=SECURE as Secure Variable -1. API_TOKEN=YAML as YAML-defined variable +1. `API_TOKEN=SECURE` as Secure Variable +1. `API_TOKEN=YAML` as YAML-defined variable -The API_TOKEN will take the Secure Variable value: `SECURE`. +The `API_TOKEN` will take the Secure Variable value: `SECURE`. ### Predefined variables (Environment Variables) From 2b1c08be8fa5c18f1fe151feb2a0938734086481 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 17 Apr 2016 20:23:42 +0200 Subject: [PATCH 292/573] Validate job-level variables in YAML config file --- lib/ci/gitlab_ci_yaml_processor.rb | 8 ++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 45 ++++++++++++++------ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 209da7ad81..ddf34b60a8 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -153,6 +153,7 @@ module Ci validate_job_types!(name, job) validate_job_stage!(name, job) if job[:stage] + validate_job_variables!(name, job) if job[:variables] validate_job_cache!(name, job) if job[:cache] validate_job_artifacts!(name, job) if job[:artifacts] validate_job_dependencies!(name, job) if job[:dependencies] @@ -214,6 +215,13 @@ module Ci end end + def validate_job_variables!(name, job) + if job[:variables] && !validate_variables(job[:variables]) + raise ValidationError, + "#{name} job: variables should be a map of key-valued strings" + end + end + def validate_job_cache!(name, job) if job[:cache][:key] && !validate_string(job[:cache][:key]) raise ValidationError, "#{name} job: cache:key parameter should be a string" diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 9a014c4b6c..8813a72477 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -366,22 +366,41 @@ module Ci end context 'when job variables are defined' do - it 'returns job variables' do - variables = { - KEY1: 'value1', - SOME_KEY_2: 'value2' - } + context 'when syntax is correct' do + it 'returns job variables' do + variables = { + KEY1: 'value1', + SOME_KEY_2: 'value2' + } - config = YAML.dump( - { before_script: ['pwd'], - rspec: { - variables: variables, - script: 'rspec' } - }) + config = YAML.dump( + { before_script: ['pwd'], + rspec: { + variables: variables, + script: 'rspec' } + }) - config_processor = GitlabCiYamlProcessor.new(config, path) + config_processor = GitlabCiYamlProcessor.new(config, path) - expect(config_processor.job_variables(:rspec)).to eq variables + expect(config_processor.job_variables(:rspec)).to eq variables + end + end + + context 'when syntax is incorrect' do + it 'raises error' do + variables = [:KEY1, 'value1', :KEY2, 'value2'] + + config = YAML.dump( + { before_script: ['pwd'], + rspec: { + variables: variables, + script: 'rspec' } + }) + + expect { GitlabCiYamlProcessor.new(config, path) } + .to raise_error(GitlabCiYamlProcessor::ValidationError, + /job: variables should be a map/) + end end end From 2972a991df0e7a0071de1803205814f2d105df46 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Apr 2016 12:29:36 +0200 Subject: [PATCH 293/573] Add minor fixes in docs for job-specific variables --- doc/ci/variables/README.md | 3 ++- doc/ci/yaml/README.md | 15 +++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index e1df9e5f54..70fb81492d 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -4,8 +4,9 @@ When receiving a build from GitLab CI, the runner prepares the build environment It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables** The variables can be overwritten. They take precedence over each other in this order: +1. Trigger variables 1. Secure variables -1. YAML-defined job-leve variables +1. YAML-defined job-level variables 1. YAML-defined global variables 1. Predefined variables diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ea7d6dfa46..61475b4598 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -421,19 +421,14 @@ except master. ### job variables It is possible to define build variables using a `variables` keyword on a job -level. It works basically the same way like it's global-level equivalent but -allows you to define a job specific build variables. +level. It works basically the same way as its global-level equivalent but +allows you to define job-specific build variables. -When `variables` keyword is used on a job level, it can override global YAML +When the `variables` keyword is used on a job level, it overrides global YAML build variables and predefined variables. -Build variables priority is defined as follows: - -* predefined variables -* global YAML variables -* job YAML variables -* secure variables -* trigger variables +Build variables priority is defined in +[variables documentation](../variables/README.md). ### tags From 28ce41c00ebba5b60c69f75d9bce97c11210779a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 07:35:43 -0400 Subject: [PATCH 294/573] Fix tests --- app/models/commit_status.rb | 2 +- features/steps/dashboard/dashboard.rb | 2 +- spec/models/ci/commit_spec.rb | 27 ++++++++++++++++++++++++++- spec/models/commit_status_spec.rb | 24 ------------------------ 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 24a26b4be8..aa56314aa1 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -110,7 +110,7 @@ class CommitStatus < ActiveRecord::Base elsif started_at Time.now - started_at end - duration.to_i + duration end def stuck? diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index ba0e829dc0..b5980b3510 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -13,7 +13,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps end step 'I should see "Shop" project CI status' do - expect(page).to have_link "Build: skipped" + expect(page).to have_link "Build skipped" end step 'I should see last push widget' do diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index aef4f00720..c12327c2a7 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -355,7 +355,8 @@ describe Ci::Commit, models: true do end context 'update state' do - let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: Time.now - 120, finished_at: Time.now - 60 } + let(:current) { Time.now.change(:usec => 0) } + let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 } before do build @@ -368,4 +369,28 @@ describe Ci::Commit, models: true do end end end + + describe '#branch?' do + subject { commit.branch? } + + context 'is not a tag' do + before do + commit.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + commit.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 31d546820c..971e675037 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -232,28 +232,4 @@ describe CommitStatus, models: true do end end end - - describe '#branch?' do - subject { commit_status.branch? } - - context 'is not a tag' do - before do - commit_status.tag = false - end - - it 'return true when tag is set to false' do - is_expected.to be_truthy - end - end - - context 'is not a tag' do - before do - commit_status.tag = true - end - - it 'return false when tag is set to true' do - is_expected.to be_falsey - end - end - end end From 0592112c6e9cebfcae6869c8fa273d7c13b74c16 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 18 Apr 2016 12:36:15 +0100 Subject: [PATCH 295/573] change the link color of the target branch in a merge rquest --- app/views/projects/merge_requests/_show.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 285ad26316..7125d7d9d1 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -31,7 +31,8 @@ %span Request to merge %span.label-branch= source_branch_with_namespace(@merge_request) %span into - = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" + %span.label-branch + = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) - if @merge_request.open? && @merge_request.diverged_from_target_branch? %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) From 1339fda1cd3325c0186b5f1b53444e7319ad3cb6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Apr 2016 12:41:13 +0200 Subject: [PATCH 296/573] Minor refactorings in CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 11 +++++++---- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index ddf34b60a8..b8ede3a7ed 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -45,7 +45,10 @@ module Ci end def job_variables(name) - @jobs[name.to_sym].try(:fetch, :variables, []) || [] + job = @jobs[name.to_sym] + return [] unless job + + job.fetch(:variables, []) end private @@ -123,7 +126,7 @@ module Ci end unless @variables.nil? || validate_variables(@variables) - raise ValidationError, "variables should be a map of key-valued strings" + raise ValidationError, "variables should be a map of key-value strings" end if @cache @@ -216,9 +219,9 @@ module Ci end def validate_job_variables!(name, job) - if job[:variables] && !validate_variables(job[:variables]) + unless validate_variables(job[:variables]) raise ValidationError, - "#{name} job: variables should be a map of key-valued strings" + "#{name} job: variables should be a map of key-value strings" end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 8813a72477..5f4b63bcaf 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -786,14 +786,14 @@ EOT config = YAML.dump({ variables: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings") end - it "returns errors if variables is not a map of key-valued strings" do + it "returns errors if variables is not a map of key-value strings" do config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings") end it "returns errors if job when is not on_success, on_failure or always" do From e5ff37c1fcd384f417518c8cca6a2a2dcc3c2767 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 07:45:23 -0400 Subject: [PATCH 297/573] Fix rubocop --- spec/models/ci/commit_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index c12327c2a7..82c18aaa01 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -355,7 +355,7 @@ describe Ci::Commit, models: true do end context 'update state' do - let(:current) { Time.now.change(:usec => 0) } + let(:current) { Time.now.change(usec: 0) } let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 } before do From 63bd1f92d96c9d023723a78259be3c86846e30a5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 07:51:49 -0400 Subject: [PATCH 298/573] Fix rubocop complains --- lib/ci/gitlab_ci_yaml_processor.rb | 64 +++++++++++--------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 +- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index a87329c296..f1d6f2be1e 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -100,6 +100,29 @@ module Ci end def validate! + validate_global! + + @jobs.each do |name, job| + validate_job!(name, job) + end + + true + end + + def validate_job!(name, job) + validate_job_name!(name) + validate_job_keys!(name, job) + validate_job_types!(name, job) + + validate_job_stage!(name, job) if job[:stage] + validate_job_cache!(name, job) if job[:cache] + validate_job_artifacts!(name, job) if job[:artifacts] + validate_job_dependencies!(name, job) if job[:dependencies] + end + + private + + def validate_global! unless validate_array_of_strings(@before_script) raise ValidationError, "before_script should be an array of strings" end @@ -124,40 +147,23 @@ module Ci raise ValidationError, "variables should be a map of key-valued strings" end - if @cache - if @cache[:key] && !validate_string(@cache[:key]) - raise ValidationError, "cache:key parameter should be a string" - end - - if @cache[:untracked] && !validate_boolean(@cache[:untracked]) - raise ValidationError, "cache:untracked parameter should be an boolean" - end - - if @cache[:paths] && !validate_array_of_strings(@cache[:paths]) - raise ValidationError, "cache:paths parameter should be an array of strings" - end - end - - @jobs.each do |name, job| - validate_job!(name, job) - end - - true + validate_global_cache! if @cache end - def validate_job!(name, job) - validate_job_name!(name) - validate_job_keys!(name, job) - validate_job_types!(name, job) + def validate_global_cache! + if @cache[:key] && !validate_string(@cache[:key]) + raise ValidationError, "cache:key parameter should be a string" + end - validate_job_stage!(name, job) if job[:stage] - validate_job_cache!(name, job) if job[:cache] - validate_job_artifacts!(name, job) if job[:artifacts] - validate_job_dependencies!(name, job) if job[:dependencies] + if @cache[:untracked] && !validate_boolean(@cache[:untracked]) + raise ValidationError, "cache:untracked parameter should be an boolean" + end + + if @cache[:paths] && !validate_array_of_strings(@cache[:paths]) + raise ValidationError, "cache:paths parameter should be an array of strings" + end end - private - def validate_job_name!(name) if name.blank? || !validate_string(name) raise ValidationError, "job name should be non-empty string" diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 2421d6eee8..b94fec2ddf 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -295,12 +295,12 @@ module Ci describe "after_script" do context "in global context" do - let(:config) { + let(:config) do { after_script: ["after_script"], test: { script: ["script"] } } - } + end it "return after_script in options" do expect(subject[:options][:after_script]).to eq(["after_script"]) From fe3ac403b97eb8dbe45189ba3737537a536b8956 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Apr 2016 13:45:26 +0100 Subject: [PATCH 299/573] Addressed feedback --- app/assets/javascripts/importer_status.js.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index 2eb151a641..b0edc89564 100644 --- a/app/assets/javascripts/importer_status.js.coffee +++ b/app/assets/javascripts/importer_status.js.coffee @@ -23,14 +23,14 @@ class @ImporterStatus $('.js-import-all') .off 'click' - .on 'click', (e) => - $btn = $(e.currentTarget) + .on 'click', (e) -> + $btn = $(@) $btn .disable() .addClass 'is-loading' $('.js-add-to-import').each -> - $(this).click() + $(this).trigger('click') setAutoUpdate: -> setInterval (=> From 073c3d15c71a0f877b62c7d3d7417a9721da1dba Mon Sep 17 00:00:00 2001 From: Anton Davydov Date: Tue, 1 Mar 2016 15:36:50 +0300 Subject: [PATCH 300/573] Initial implementation if license template selector and /licenses/:key API endpoint --- Gemfile | 3 ++ Gemfile.lock | 3 ++ app/assets/javascripts/api.js.coffee | 13 ++++++ .../blob/blob_license_selector.js.coffee | 24 ++++++++++ app/assets/stylesheets/pages/editor.scss | 9 ++++ app/controllers/projects/blob_controller.rb | 9 ++++ app/helpers/projects_helper.rb | 7 +++ app/views/projects/blob/_editor.html.haml | 11 ++++- app/views/projects/empty.html.haml | 6 ++- lib/api/api.rb | 1 + lib/api/licenses.rb | 27 +++++++++++ spec/requests/api/licenses_spec.rb | 46 +++++++++++++++++++ 12 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/blob/blob_license_selector.js.coffee create mode 100644 lib/api/licenses.rb create mode 100644 spec/requests/api/licenses_spec.rb diff --git a/Gemfile b/Gemfile index 199ef65d92..ca4157cf47 100644 --- a/Gemfile +++ b/Gemfile @@ -190,6 +190,9 @@ gem 'babosa', '~> 1.0.2' # Sanitizes SVG input gem "loofah", "~> 2.0.3" +# Working with license +gem 'licensee', "~> 7.0.0" + # Protect against bruteforcing gem "rack-attack", '~> 4.3.1' diff --git a/Gemfile.lock b/Gemfile.lock index ad7d7c1855..2a6faeff2f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -452,6 +452,8 @@ GEM addressable (~> 2.3) letter_opener (1.1.2) launchy (~> 2.2) + licensee (7.0.0) + rugged (~> 0.23) listen (3.0.5) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) @@ -957,6 +959,7 @@ DEPENDENCIES jquery-ui-rails (~> 5.0.0) kaminari (~> 0.16.3) letter_opener (~> 1.1.2) + licensee (~> 7.0.0) loofah (~> 2.0.3) mail_room (~> 0.6.1) method_source (~> 0.8) diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index f3ed9a6671..f4757658d5 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -5,6 +5,7 @@ group_projects_path: "/api/:version/groups/:id/projects.json" projects_path: "/api/:version/projects.json" labels_path: "/api/:version/projects/:id/labels" + license_path: "/api/:version/licenses/:key" group: (group_id, callback) -> url = Api.buildUrl(Api.group_path) @@ -92,6 +93,18 @@ ).done (projects) -> callback(projects) + # Return text for specific license + licenseText: (key, fullname, callback) -> + url = Api.buildUrl(Api.license_path) + url = url.replace(':key', key) + + $.ajax( + url: url + data: + fullname: fullname + ).done (projects) -> + callback(projects) + buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? return url.replace(':version', gon.api_version) diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee new file mode 100644 index 0000000000..b43c1ec2bb --- /dev/null +++ b/app/assets/javascripts/blob/blob_license_selector.js.coffee @@ -0,0 +1,24 @@ +class @BlobLicenseSelector + licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i + + constructor: (editor)-> + self = this + @licenseSelector = $('.js-license-selector') + @toggleLicenseSelector($('#file_name').val()) + + $('#file_name').on 'input', -> + self.toggleLicenseSelector($(this).val()) + + $('select.license-select').select2( + width: 'resolve' + dropdownAutoWidth: true + placeholder: 'Choose a license template' + ).on 'change', (e) -> + Api.licenseText $(this).val(), $(this).data('fullname'), (data) -> + editor.setValue(data, -1) + + toggleLicenseSelector: (fileName) => + if @licenseRegex.test(fileName) + @licenseSelector.show() + else + @licenseSelector.hide() diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 0f0592a0ab..8981f070a2 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -26,6 +26,10 @@ line-height: 42px; padding-top: 7px; padding-bottom: 7px; + + .pull-right { + height: 20px; + } } .editor-ref { @@ -53,4 +57,9 @@ .select2 { float: right; } + + .encoding-selector, + .license-selector { + display: inline-block; + } } diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index cd8b291167..6960562d15 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -122,6 +122,15 @@ class Projects::BlobController < Projects::ApplicationController end def editor_variables + @licenses = { + 'Popular' => Licensee::License.all(featured: true).map!{ |license| [license.name, license.key] }, + 'Other' => Licensee::License.all(featured: false).map!{ |license| [license.name, license.key] } + } + + unless @repository.empty? + @current_license_key = Licensee.license(@repository.path).try(:key) + end + @target_branch = params[:target_branch] @file_path = diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7e00aaccea..0b0cb658b1 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -306,6 +306,13 @@ module ProjectsHelper namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md') end + def new_license_path + ref = @repository.root_ref if @repository + ref ||= 'master' + + namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') + end + def last_push_event if current_user current_user.recent_push(@project.id) diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index f8b6fa253c..8ebbc91f81 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -13,7 +13,11 @@ required: true, class: 'form-control new-file-name' .pull-right - = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' + .license-selector.js-license-selector.hide + = select_tag :license_type, grouped_options_for_select(@licenses, @current_license_key), include_blank: true, class: 'select2 license-select', data: { fullname: @repository.project.creator.name } + + .encoding-selector + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' .file-content.code %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]} @@ -22,3 +26,8 @@ .center %h2 %i.icon-spinner.icon-spin + +:javascript + window.onload = function() { + new BlobLicenseSelector(blob.editor) + } diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 6ad7b05155..2eb7970330 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -14,8 +14,10 @@ %p If you already have files you can push them using command line instructions below. %p - Otherwise you can start with - = link_to "adding README", new_readme_path, class: 'underlined-link' + Otherwise you can start with adding + = link_to "README", new_readme_path, class: 'underlined-link' + or + = link_to "LICENSE", new_license_path, class: 'underlined-link' file to this project. - if can?(current_user, :push_code, @project) diff --git a/lib/api/api.rb b/lib/api/api.rb index 7d65145176..cc1004f800 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -57,5 +57,6 @@ module API mount Builds mount Variables mount Runners + mount Licenses end end diff --git a/lib/api/licenses.rb b/lib/api/licenses.rb new file mode 100644 index 0000000000..7fffbef84e --- /dev/null +++ b/lib/api/licenses.rb @@ -0,0 +1,27 @@ +module API + # Licenses API + class Licenses < Grape::API + YEAR_TEMPLATE_REGEX = /(\[|<|{)(year|yyyy)(\]|>|})/ + FULLNAME_TEMPLATE_REGEX = /\[fullname\]/ + + # Get text for specific license + # + # Parameters: + # key (required) - The key of a license + # fullname - Reository owner fullname + # Example Request: + # GET /licenses/mit + get 'licenses/:key', requirements: { key: /[\w.-]*/ } do + env['api.format'] = :txt + license = Licensee::License.find(params[:key]).try(:text) + + if license + license + .gsub(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) + .gsub(FULLNAME_TEMPLATE_REGEX, params[:fullname]) + else + error!('License not found', 404) + end + end + end +end diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/licenses_spec.rb new file mode 100644 index 0000000000..f0f8b041f6 --- /dev/null +++ b/spec/requests/api/licenses_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + describe 'GET /licenses/:key' do + before(:each) do + get api("/licenses/#{license_type}?fullname=Anton") + end + + context 'for mit license name' do + let(:license_type){ 'mit' } + + it 'returns MIT license text and replases template values' do + expect(response.body).to include('Copyright (c) 2016 Anton') + expect(response.body).to include('Copyright (c) 2016') + end + end + + context 'for gnu license name' do + let(:license_type){ 'gpl-3.0' } + + it 'returns GNU license text and replases template values' do + expect(response.body).to include('GNU GENERAL PUBLIC LICENSE') + expect(response.body).to include('Copyright (C) 2016') + end + end + + context 'for apache license name' do + let(:license_type){ 'apache-2.0' } + + it 'returns Apache license text and replases template values' do + expect(response.body).to include('Apache License') + expect(response.body).to include('Copyright 2016') + end + end + + context 'for mythic license name' do + let(:license_type){ 'muth-over9000' } + + it 'returns string with error' do + expect(response).to have_http_status(404) + expect(response.body).to eq 'License not found' + end + end + end +end From 13804aba867d19009ca94d820aa7ec650a509f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 11 Apr 2016 15:49:25 +0200 Subject: [PATCH 301/573] Continue implementation of the license template selector and /licenses API endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 2 + app/assets/javascripts/api.js.coffee | 14 +- .../blob/blob_license_selector.js.coffee | 36 +++-- .../javascripts/blob/edit_blob.js.coffee | 67 ++++---- .../javascripts/blob/new_blob.js.coffee | 20 --- app/controllers/projects/blob_controller.rb | 9 -- app/helpers/blob_helper.rb | 11 ++ app/helpers/projects_helper.rb | 50 ++---- app/models/repository.rb | 37 +++-- app/views/projects/_readme.html.haml | 4 +- app/views/projects/blob/_editor.html.haml | 7 +- app/views/projects/blob/new.html.haml | 2 +- app/views/projects/empty.html.haml | 6 +- app/views/projects/show.html.haml | 12 +- doc/api/README.md | 1 + doc/api/licenses.md | 147 +++++++++++++++++ lib/api/entities.rb | 11 ++ lib/api/licenses.rb | 57 +++++-- ...project_owner_creates_license_file_spec.rb | 61 +++++++ ...eate_license_file_in_empty_project_spec.rb | 36 +++++ spec/models/repository_spec.rb | 65 ++++++-- spec/requests/api/licenses_spec.rb | 152 ++++++++++++++---- 22 files changed, 594 insertions(+), 213 deletions(-) delete mode 100644 app/assets/javascripts/blob/new_blob.js.coffee create mode 100644 doc/api/licenses.md create mode 100644 spec/features/projects/files/project_owner_creates_license_file_spec.rb create mode 100644 spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb diff --git a/CHANGELOG b/CHANGELOG index b929f1fd7b..3f963e039b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,8 @@ v 8.7.0 (unreleased) - Fix a bug whith trailing slash in teamcity_url (Charles May) - Allow back dating on issues when created or updated through the API - Allow back dating on issue notes when created through the API + - Propose license template when creating a new LICENSE file + - API: Expose /licenses and /licenses/:key - Fix avatar stretching by providing a cropping feature - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - Allow SAML to handle external users based on user's information !3530 diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index f4757658d5..dd1bbb3755 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -93,17 +93,15 @@ ).done (projects) -> callback(projects) - # Return text for specific license - licenseText: (key, fullname, callback) -> - url = Api.buildUrl(Api.license_path) - url = url.replace(':key', key) + # Return text for a specific license + licenseText: (key, data, callback) -> + url = Api.buildUrl(Api.license_path).replace(':key', key) $.ajax( url: url - data: - fullname: fullname - ).done (projects) -> - callback(projects) + data: data + ).done (license) -> + callback(license) buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee index b43c1ec2bb..e17eaa75dc 100644 --- a/app/assets/javascripts/blob/blob_license_selector.js.coffee +++ b/app/assets/javascripts/blob/blob_license_selector.js.coffee @@ -1,24 +1,30 @@ class @BlobLicenseSelector licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i - constructor: (editor)-> - self = this - @licenseSelector = $('.js-license-selector') - @toggleLicenseSelector($('#file_name').val()) + constructor: (editor) -> + @$licenseSelector = $('.js-license-selector') + $fileNameInput = $('#file_name') - $('#file_name').on 'input', -> - self.toggleLicenseSelector($(this).val()) + initialFileNameValue = if $fileNameInput.length + $fileNameInput.val() + else if $('.editor-file-name').length + $('.editor-file-name').text().trim() - $('select.license-select').select2( - width: 'resolve' - dropdownAutoWidth: true - placeholder: 'Choose a license template' - ).on 'change', (e) -> - Api.licenseText $(this).val(), $(this).data('fullname'), (data) -> - editor.setValue(data, -1) + @toggleLicenseSelector(initialFileNameValue) + + if $fileNameInput + $fileNameInput.on 'keyup blur', (e) => + @toggleLicenseSelector($(e.target).val()) + + $('select.license-select').on 'change', (e) -> + data = + project: $(this).data('project') + fullname: $(this).data('fullname') + Api.licenseText $(this).val(), data, (license) -> + editor.setValue(license.content, -1) toggleLicenseSelector: (fileName) => if @licenseRegex.test(fileName) - @licenseSelector.show() + @$licenseSelector.show() else - @licenseSelector.hide() + @$licenseSelector.hide() diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index 390e41ed8d..eea9aa972e 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -1,44 +1,39 @@ class @EditBlob - constructor: (assets_path, mode)-> - ace.config.set "modePath", assets_path + '/ace' + constructor: (assets_path, ace_mode = null) -> + ace.config.set "modePath", "#{assets_path}/ace" ace.config.loadModule "ace/ext/searchbox" - if mode - ace_mode = mode - editor = ace.edit("editor") - editor.focus() - @editor = editor - - if ace_mode - editor.getSession().setMode "ace/mode/" + ace_mode + @editor = ace.edit("editor") + @editor.focus() + @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode # Before a form submission, move the content from the Ace editor into the # submitted textarea - $('form').submit -> - $("#file-content").val(editor.getValue()) + $('form').submit => + $("#file-content").val(@editor.getValue()) - editModePanes = $(".js-edit-mode-pane") - editModeLinks = $(".js-edit-mode a") - editModeLinks.click (event) -> - event.preventDefault() - currentLink = $(this) - paneId = currentLink.attr("href") - currentPane = editModePanes.filter(paneId) - editModeLinks.parent().removeClass "active hover" - currentLink.parent().addClass "active hover" - editModePanes.hide() - if paneId is "#preview" - currentPane.fadeIn 200 - $.post currentLink.data("preview-url"), - content: editor.getValue() - , (response) -> - currentPane.empty().append response - currentPane.syntaxHighlight() - return + @initModePanesAndLinks() + new BlobLicenseSelector(@editor) - else - currentPane.fadeIn 200 - editor.focus() - return + initModePanesAndLinks: -> + @$editModePanes = $(".js-edit-mode-pane") + @$editModeLinks = $(".js-edit-mode a") + @$editModeLinks.click @editModeLinkClickHandler - editor: -> - return @editor + editModeLinkClickHandler: (event) => + event.preventDefault() + currentLink = $(event.target) + paneId = currentLink.attr("href") + currentPane = @$editModePanes.filter(paneId) + @$editModeLinks.parent().removeClass "active hover" + currentLink.parent().addClass "active hover" + @$editModePanes.hide() + currentPane.fadeIn 200 + if paneId is "#preview" + $.post currentLink.data("preview-url"), + content: @editor.getValue() + , (response) -> + currentPane.empty().append response + currentPane.syntaxHighlight() + + else + @editor.focus() diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee deleted file mode 100644 index 68c5e5195e..0000000000 --- a/app/assets/javascripts/blob/new_blob.js.coffee +++ /dev/null @@ -1,20 +0,0 @@ -class @NewBlob - constructor: (assets_path, mode)-> - ace.config.set "modePath", assets_path + '/ace' - ace.config.loadModule "ace/ext/searchbox" - if mode - ace_mode = mode - editor = ace.edit("editor") - editor.focus() - @editor = editor - - if ace_mode - editor.getSession().setMode "ace/mode/" + ace_mode - - # Before a form submission, move the content from the Ace editor into the - # submitted textarea - $('form').submit -> - $("#file-content").val(editor.getValue()) - - editor: -> - return @editor diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 6960562d15..cd8b291167 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -122,15 +122,6 @@ class Projects::BlobController < Projects::ApplicationController end def editor_variables - @licenses = { - 'Popular' => Licensee::License.all(featured: true).map!{ |license| [license.name, license.key] }, - 'Other' => Licensee::License.all(featured: false).map!{ |license| [license.name, license.key] } - } - - unless @repository.empty? - @current_license_key = Licensee.license(@repository.path).try(:key) - end - @target_branch = params[:target_branch] @file_path = diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 9e59a295fc..a4d7c425d0 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -173,4 +173,15 @@ module BlobHelper response.etag = @blob.id !stale end + + def licenses_for_select + return @licenses_for_select if defined?(@licenses_for_select) + + licenses = Licensee::License.all + + @licenses_for_select = { + Popular: licenses.select(&:featured).map { |license| [license.name, license.key] }, + Other: licenses.reject(&:featured).map { |license| [license.name, license.key] } + } + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 0b0cb658b1..81be73937a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -216,40 +216,14 @@ module ProjectsHelper end end - def add_contribution_guide_path(project) - if project && !project.repository.contribution_guide - namespace_project_new_blob_path( - project.namespace, - project, - project.default_branch, - file_name: "CONTRIBUTING.md", - commit_message: "Add contribution guide" - ) - end - end - - def add_changelog_path(project) - if project && !project.repository.changelog - namespace_project_new_blob_path( - project.namespace, - project, - project.default_branch, - file_name: "CHANGELOG", - commit_message: "Add changelog" - ) - end - end - - def add_license_path(project) - if project && !project.repository.license - namespace_project_new_blob_path( - project.namespace, - project, - project.default_branch, - file_name: "LICENSE", - commit_message: "Add license" - ) - end + def add_special_file_path(project, file_name:, commit_message: nil) + namespace_project_new_blob_path( + project.namespace, + project, + project.default_branch || 'master', + file_name: file_name, + commit_message: commit_message || "Add #{file_name.downcase}" + ) end def contribution_guide_path(project) @@ -272,7 +246,7 @@ module ProjectsHelper end def license_path(project) - filename_path(project, :license) + filename_path(project, :license_blob) end def version_path(project) @@ -342,6 +316,12 @@ module ProjectsHelper @ref || @repository.try(:root_ref) end + def license_short_name(project) + license = Licensee::License.new(project.repository.license_key) + + license.nickname || license.name + end + private def filename_path(project, filename) diff --git a/app/models/repository.rb b/app/models/repository.rb index 589756f853..5d6fe017b5 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -228,7 +228,8 @@ class Repository def cache_keys %i(size branch_names tag_names commit_count - readme version contribution_guide changelog license) + readme version contribution_guide changelog + license_blob license_key) end def build_cache @@ -461,27 +462,21 @@ class Repository end end - def license - cache.fetch(:license) do - licenses = tree(:head).blobs.find_all do |file| - file.name =~ /\A(copying|license|licence)/i - end + def license_blob + return nil if !exists? || empty? - preferences = [ - /\Alicen[sc]e\z/i, # LICENSE, LICENCE - /\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt - /\Acopying\z/i, # COPYING - /\Acopying\.(?!lesser)/i, # COPYING.txt - /Acopying.lesser/i # COPYING.LESSER - ] - - license = nil - preferences.each do |r| - license = licenses.find { |l| l.name =~ r } - break if license + cache.fetch(:license_blob) do + if licensee_project.license + blob_at_branch(root_ref, licensee_project.matched_file.filename) end + end + end - license + def license_key + return nil if !exists? || empty? + + cache.fetch(:license_key) do + licensee_project.license.try(:key) || 'no-license' end end @@ -925,4 +920,8 @@ class Repository def cache @cache ||= RepositoryCache.new(path_with_namespace) end + + def licensee_project + @licensee_project ||= Licensee.project(path) + end end diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index d1191928d4..a9908eaecc 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -9,7 +9,7 @@ - else .gray-content-block.second-block.center %h3.page-title - This project does not have README yet + This project does not have a README yet - if can?(current_user, :push_code, @project) %p A @@ -18,5 +18,5 @@ distributed with computer software, forming part of its documentation. %p We recommend you to - = link_to "add README", new_readme_path, class: 'underlined-link' + = link_to "add a README", new_readme_path, class: 'underlined-link' file to the repository and GitLab will render it here instead of this message. diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 8ebbc91f81..fefa652a3d 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -14,7 +14,7 @@ .pull-right .license-selector.js-license-selector.hide - = select_tag :license_type, grouped_options_for_select(@licenses, @current_license_key), include_blank: true, class: 'select2 license-select', data: { fullname: @repository.project.creator.name } + = select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name} .encoding-selector = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' @@ -26,8 +26,3 @@ .center %h2 %i.icon-spinner.icon-spin - -:javascript - window.onload = function() { - new BlobLicenseSelector(blob.editor) - } diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 1dd2b5c0af..0459699432 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -14,5 +14,5 @@ cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) :javascript - blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null) + blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}") new NewCommitForm($('.js-new-blob-form')) diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 2eb7970330..52d093871b 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -14,10 +14,10 @@ %p If you already have files you can push them using command line instructions below. %p - Otherwise you can start with adding + Otherwise you can start with adding a = link_to "README", new_readme_path, class: 'underlined-link' - or - = link_to "LICENSE", new_license_path, class: 'underlined-link' + or a + = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link' file to this project. - if can?(current_user, :push_code, @project) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 4310f038fc..d854ac2172 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -36,9 +36,9 @@ %li = link_to 'Changelog', changelog_path(@project) - - if @repository.license + - if @repository.license_blob %li - = link_to 'License', license_path(@project) + = link_to license_short_name(@project), license_path(@project) - if @repository.contribution_guide %li @@ -47,15 +47,15 @@ - if current_user && can_push_branch?(@project, @project.default_branch) - unless @repository.changelog %li.missing - = link_to add_changelog_path(@project) do + = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do Add Changelog - - unless @repository.license + - unless @repository.license_blob %li.missing - = link_to add_license_path(@project) do + = link_to add_special_file_path(@project, file_name: 'LICENSE') do Add License - unless @repository.contribution_guide %li.missing - = link_to add_contribution_guide_path(@project) do + = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do Add Contribution guide - if @repository.commit diff --git a/doc/api/README.md b/doc/api/README.md index 3a8fa6cebd..ff039f1886 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -33,6 +33,7 @@ following locations: - [Build triggers](build_triggers.md) - [Build Variables](build_variables.md) - [Runners](runners.md) +- [Licenses](licenses.md) ## Authentication diff --git a/doc/api/licenses.md b/doc/api/licenses.md new file mode 100644 index 0000000000..855b0eab56 --- /dev/null +++ b/doc/api/licenses.md @@ -0,0 +1,147 @@ +# Licenses + +## List license templates + +Get all license templates. + +``` +GET /licenses +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `popular` | boolean | no | If passed, returns only popular licenses | + +```bash +curl https://gitlab.example.com/api/v3/licenses?popular=1 +``` + +Example response: + +```json +[ + { + "key": "apache-2.0", + "name": "Apache License 2.0", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/apache-2.0/", + "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html", + "description": "A permissive license that also provides an express grant of patent rights from contributors to users.", + "conditions": [ + "include-copyright", + "document-changes" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "trademark-use", + "no-liability" + ], + "content": " Apache License\n Version 2.0, January 2004\n [...]" + }, + { + "key": "gpl-3.0", + "name": "GNU General Public License v3.0", + "nickname": "GNU GPLv3", + "featured": true, + "html_url": "http://choosealicense.com/licenses/gpl-3.0/", + "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt", + "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.", + "conditions": [ + "include-copyright", + "document-changes", + "disclose-source", + "same-license" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]" + }, + { + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]" + } +] +``` + +## Single license template + +Get a single license template. You can pass parameters to replace the license +placeholder. + +``` +GET /licenses/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the license template | +| `project` | string | no | The copyrighted project name | +| `fullname` | string | no | The full-name of the copyright holder | + +>**Note:** +If you omit the `fullname` parameter but authenticate your request, the name of +the authenticated user will be used to replace the copyright holder placeholder. + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project +``` + +Example response: + +```json +{ + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" +} +``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 60b9f5e0ec..61df948df6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -439,5 +439,16 @@ module API class Variable < Grape::Entity expose :key, :value end + + class License < Grape::Entity + expose :key, :name, :nickname, :featured + expose :url, as: :html_url + expose(:source_url) { |license| license.meta['source'] } + expose(:description) { |license| license.meta['description'] } + expose(:conditions) { |license| license.meta['required'] } + expose(:permissions) { |license| license.meta['permitted'] } + expose(:limitations) { |license| license.meta['forbidden'] } + expose :content + end end end diff --git a/lib/api/licenses.rb b/lib/api/licenses.rb index 7fffbef84e..dac29df30f 100644 --- a/lib/api/licenses.rb +++ b/lib/api/licenses.rb @@ -1,27 +1,58 @@ module API # Licenses API class Licenses < Grape::API - YEAR_TEMPLATE_REGEX = /(\[|<|{)(year|yyyy)(\]|>|})/ - FULLNAME_TEMPLATE_REGEX = /\[fullname\]/ + PROJECT_TEMPLATE_REGEX = + /[\<\{\[] + (project|description| + one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here + [\>\}\]]/xi + YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i + FULLNAME_TEMPLATE_REGEX = + /[\<\{\[] + (fullname|name\sof\s(author|copyright\sowner)) + [\>\}\]]/xi + + # Get the list of the available license templates + # + # Parameters: + # popular - Filter licenses to only the popular ones + # + # Example Request: + # GET /licenses + # GET /licenses?popular=1 + get 'licenses' do + options = { + featured: params[:popular].present? ? true : nil + } + present Licensee::License.all(options), with: Entities::License + end # Get text for specific license # # Parameters: # key (required) - The key of a license - # fullname - Reository owner fullname + # project - Copyrighted project name + # fullname - Full name of copyright holder + # # Example Request: # GET /licenses/mit - get 'licenses/:key', requirements: { key: /[\w.-]*/ } do - env['api.format'] = :txt - license = Licensee::License.find(params[:key]).try(:text) + # + get 'licenses/:key', requirements: { key: /[\w\.-]+/ } do + required_attributes! [:key] - if license - license - .gsub(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) - .gsub(FULLNAME_TEMPLATE_REGEX, params[:fullname]) - else - error!('License not found', 404) - end + not_found!('License') unless Licensee::License.find(params[:key]) + + # We create a fresh Licensee::License object since we'll modify its + # content in place below. + license = Licensee::License.new(params[:key]) + + license.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) + license.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present? + + fullname = params[:fullname].presence || current_user.try(:name) + license.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname + + present license, with: Entities::License end end end diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb new file mode 100644 index 0000000000..c0fcfd0693 --- /dev/null +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +feature 'creates a license file', feature: true, js: true do + include Select2Helper + + let(:project_master) { create(:user) } + let(:project) { create(:project) } + background do + project.repository.remove_file(project_master, 'LICENSE', 'Remove LICENSE', 'master') + project.team << [project_master, :master] + login_as(project_master) + visit namespace_project_path(project.namespace, project) + end + + scenario 'project master creates a license file manually from a template' do + visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref) + find('.add-to-tree').click + click_link 'New file' + + fill_in :file_name, with: 'LICENSE' + + expect(page).to have_selector('.license-selector') + + select2('mit', from: '#license_type') + + file_content = find('.file-content') + expect(file_content).to have_content('The MIT License (MIT)') + expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}") + + fill_in :commit_message, with: 'Add a LICENSE file', visible: true + click_button 'Commit Changes' + + expect(current_path).to eq( + namespace_project_blob_path(project.namespace, project, 'master/LICENSE')) + expect(page).to have_content('The MIT License (MIT)') + expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}") + end + + scenario 'project master creates a license file from the "Add license" link' do + click_link 'Add License' + + expect(current_path).to eq( + namespace_project_new_blob_path(project.namespace, project, 'master')) + expect(find('#file_name').value).to eq('LICENSE') + expect(page).to have_selector('.license-selector') + + select2('mit', from: '#license_type') + + file_content = find('.file-content') + expect(file_content).to have_content('The MIT License (MIT)') + expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}") + + fill_in :commit_message, with: 'Add a LICENSE file', visible: true + click_button 'Commit Changes' + + expect(current_path).to eq( + namespace_project_blob_path(project.namespace, project, 'master/LICENSE')) + expect(page).to have_content('The MIT License (MIT)') + expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}") + end +end diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb new file mode 100644 index 0000000000..6c51d00d81 --- /dev/null +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +feature 'creates a license file in empty project', feature: true, js: true do + include Select2Helper + + let(:project_master) { create(:user) } + let(:project) { create(:project_empty_repo) } + background do + project.team << [project_master, :master] + login_as(project_master) + visit namespace_project_path(project.namespace, project) + end + + scenario 'project master creates a license file from a template' do + click_on 'LICENSE' + + expect(current_path).to eq( + namespace_project_new_blob_path(project.namespace, project, 'master')) + expect(find('#file_name').value).to eq('LICENSE') + expect(page).to have_selector('.license-selector') + + select2('mit', from: '#license_type') + + file_content = find('.file-content') + expect(file_content).to have_content('The MIT License (MIT)') + expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}") + + fill_in :commit_message, with: 'Add a LICENSE file', visible: true + click_button 'Commit Changes' + + expect(current_path).to eq( + namespace_project_blob_path(project.namespace, project, 'master/LICENSE')) + expect(page).to have_content('The MIT License (MIT)') + expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}") + end +end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f30a21e79a..a374c50574 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -135,22 +135,69 @@ describe Repository, models: true do end - describe "#license" do + describe '#license_blob' do before do - repository.send(:cache).expire(:license) + repository.send(:cache).expire(:license_blob) + repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master') end - it 'test selection preference' do - files = [TestBlob.new('file'), TestBlob.new('license'), TestBlob.new('copying')] - expect(repository.tree).to receive(:blobs).and_return(files) + it 'looks in the root_ref only' do + repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'markdown') + repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'markdown', false) - expect(repository.license.name).to eq('license') + expect(repository.license_blob).to be_nil end - it 'also accepts licence instead of license' do - expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('licence')]) + it 'favors license file with no extension' do + repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false) + repository.commit_file(user, 'LICENSE.md', Licensee::License.new('mit').content, 'Add LICENSE.md', 'master', false) - expect(repository.license.name).to eq('licence') + expect(repository.license_blob.name).to eq('LICENSE') + end + + it 'favors .md file to .txt' do + repository.commit_file(user, 'LICENSE.md', Licensee::License.new('mit').content, 'Add LICENSE.md', 'master', false) + repository.commit_file(user, 'LICENSE.txt', Licensee::License.new('mit').content, 'Add LICENSE.txt', 'master', false) + + expect(repository.license_blob.name).to eq('LICENSE.md') + end + + it 'favors LICENCE to LICENSE' do + repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false) + repository.commit_file(user, 'LICENCE', Licensee::License.new('mit').content, 'Add LICENCE', 'master', false) + + expect(repository.license_blob.name).to eq('LICENCE') + end + + it 'favors LICENSE to COPYING' do + repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false) + repository.commit_file(user, 'COPYING', Licensee::License.new('mit').content, 'Add COPYING', 'master', false) + + expect(repository.license_blob.name).to eq('LICENSE') + end + + it 'favors LICENCE to COPYING' do + repository.commit_file(user, 'LICENCE', Licensee::License.new('mit').content, 'Add LICENCE', 'master', false) + repository.commit_file(user, 'COPYING', Licensee::License.new('mit').content, 'Add COPYING', 'master', false) + + expect(repository.license_blob.name).to eq('LICENCE') + end + end + + describe '#license_key' do + before do + repository.send(:cache).expire(:license_key) + repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master') + end + + it 'returns "no-license" when no license is detected' do + expect(repository.license_key).to eq('no-license') + end + + it 'returns the license key' do + repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false) + + expect(repository.license_key).to eq('mit') end end diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/licenses_spec.rb index f0f8b041f6..61a58231c5 100644 --- a/spec/requests/api/licenses_spec.rb +++ b/spec/requests/api/licenses_spec.rb @@ -1,45 +1,135 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Licenses, api: true do include ApiHelpers + + describe 'Entity' do + before { get api('/licenses/mit') } + + it { expect(json_response['key']).to eq('mit') } + it { expect(json_response['name']).to eq('MIT License') } + it { expect(json_response['nickname']).to be_nil } + it { expect(json_response['featured']).to be true } + it { expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') } + it { expect(json_response['source_url']).to eq('http://opensource.org/licenses/MIT') } + it { expect(json_response['description']).to include('A permissive license that is short and to the point.') } + it { expect(json_response['conditions']).to eq(%w[include-copyright]) } + it { expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) } + it { expect(json_response['limitations']).to eq(%w[no-liability]) } + it { expect(json_response['content']).to include('The MIT License (MIT)') } + end + + describe 'GET /licenses' do + it 'returns a list of available license templates' do + get api('/licenses') + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(15) + expect(json_response.first['key']).to eq('agpl-3.0') + end + + describe 'the popular parameter' do + context 'with popular=1' do + it 'returns a list of available popular license templates' do + get api('/licenses?popular=1') + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(3) + expect(json_response.first['key']).to eq('apache-2.0') + end + end + end + end + describe 'GET /licenses/:key' do - before(:each) do - get api("/licenses/#{license_type}?fullname=Anton") - end + context 'with :project and :fullname given' do + before do + get api("/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") + end - context 'for mit license name' do - let(:license_type){ 'mit' } + context 'for the mit license' do + let(:license_type) { 'mit' } - it 'returns MIT license text and replases template values' do - expect(response.body).to include('Copyright (c) 2016 Anton') - expect(response.body).to include('Copyright (c) 2016') + it 'returns the license text' do + expect(json_response['content']).to include('The MIT License (MIT)') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('Copyright (c) 2016 Anton') + end + end + + context 'for the agpl-3.0 license' do + let(:license_type) { 'agpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include('Copyright (C) 2016 Anton') + end + end + + context 'for the gpl-3.0 license' do + let(:license_type) { 'gpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include('Copyright (C) 2016 Anton') + end + end + + context 'for the gpl-2.0 license' do + let(:license_type) { 'gpl-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include('Copyright (C) 2016 Anton') + end + end + + context 'for the apache-2.0 license' do + let(:license_type) { 'apache-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('Apache License') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('Copyright 2016 Anton') + end + end + + context 'for an uknown license' do + let(:license_type) { 'muth-over9000' } + + it 'returns a 404' do + expect(response.status).to eq(404) + end end end - context 'for gnu license name' do - let(:license_type){ 'gpl-3.0' } + context 'with no :fullname given' do + context 'with an authenticated user' do + let(:user) { create(:user) } - it 'returns GNU license text and replases template values' do - expect(response.body).to include('GNU GENERAL PUBLIC LICENSE') - expect(response.body).to include('Copyright (C) 2016') - end - end + it 'replaces the copyright owner placeholder with the name of the current user' do + get api('/licenses/mit', user) - context 'for apache license name' do - let(:license_type){ 'apache-2.0' } - - it 'returns Apache license text and replases template values' do - expect(response.body).to include('Apache License') - expect(response.body).to include('Copyright 2016') - end - end - - context 'for mythic license name' do - let(:license_type){ 'muth-over9000' } - - it 'returns string with error' do - expect(response).to have_http_status(404) - expect(response.body).to eq 'License not found' + expect(json_response['content']).to include("Copyright (c) 2016 #{user.name}") + end end end end From 0aaaae9d0536c5637662f6c72a6612ec13f0342f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 12 Apr 2016 16:56:18 +0200 Subject: [PATCH 302/573] Fix features specs (hopefully) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- features/steps/project/source/browse_files.rb | 2 +- lib/api/licenses.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index e072505e5d..21545eabc3 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -283,7 +283,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I click on "add a file" link' do - click_link 'adding README' + click_link 'adding a README' # Remove pre-receive hook so we can push without auth FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive')) diff --git a/lib/api/licenses.rb b/lib/api/licenses.rb index dac29df30f..f9536fc82b 100644 --- a/lib/api/licenses.rb +++ b/lib/api/licenses.rb @@ -5,12 +5,12 @@ module API /[\<\{\[] (project|description| one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here - [\>\}\]]/xi - YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i + [\>\}\]]/xi.freeze + YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze FULLNAME_TEMPLATE_REGEX = /[\<\{\[] (fullname|name\sof\s(author|copyright\sowner)) - [\>\}\]]/xi + [\>\}\]]/xi.freeze # Get the list of the available license templates # From 2f18c77e1b0e3b6f3d668c3cb1814d4675641a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 13 Apr 2016 15:56:37 +0200 Subject: [PATCH 303/573] Update to licensee 8.0.0 and fix API specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- Gemfile | 2 +- Gemfile.lock | 6 +++--- lib/api/entities.rb | 9 +++++---- spec/requests/api/licenses_spec.rb | 8 ++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index ca4157cf47..3c9a5ba955 100644 --- a/Gemfile +++ b/Gemfile @@ -191,7 +191,7 @@ gem 'babosa', '~> 1.0.2' gem "loofah", "~> 2.0.3" # Working with license -gem 'licensee', "~> 7.0.0" +gem 'licensee', '~> 8.0.0' # Protect against bruteforcing gem "rack-attack", '~> 4.3.1' diff --git a/Gemfile.lock b/Gemfile.lock index 2a6faeff2f..3cff184b00 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -452,8 +452,8 @@ GEM addressable (~> 2.3) letter_opener (1.1.2) launchy (~> 2.2) - licensee (7.0.0) - rugged (~> 0.23) + licensee (8.0.0) + rugged (>= 0.24b) listen (3.0.5) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) @@ -959,7 +959,7 @@ DEPENDENCIES jquery-ui-rails (~> 5.0.0) kaminari (~> 0.16.3) letter_opener (~> 1.1.2) - licensee (~> 7.0.0) + licensee (~> 8.0.0) loofah (~> 2.0.3) mail_room (~> 0.6.1) method_source (~> 0.8) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 61df948df6..e7af928b0e 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -441,13 +441,14 @@ module API end class License < Grape::Entity - expose :key, :name, :nickname, :featured + expose :key, :name, :nickname + expose :featured, as: :popular expose :url, as: :html_url expose(:source_url) { |license| license.meta['source'] } expose(:description) { |license| license.meta['description'] } - expose(:conditions) { |license| license.meta['required'] } - expose(:permissions) { |license| license.meta['permitted'] } - expose(:limitations) { |license| license.meta['forbidden'] } + expose(:conditions) { |license| license.meta['conditions'] } + expose(:permissions) { |license| license.meta['permissions'] } + expose(:limitations) { |license| license.meta['limitations'] } expose :content end end diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/licenses_spec.rb index 61a58231c5..c17dcb222a 100644 --- a/spec/requests/api/licenses_spec.rb +++ b/spec/requests/api/licenses_spec.rb @@ -9,9 +9,9 @@ describe API::Licenses, api: true do it { expect(json_response['key']).to eq('mit') } it { expect(json_response['name']).to eq('MIT License') } it { expect(json_response['nickname']).to be_nil } - it { expect(json_response['featured']).to be true } + it { expect(json_response['popular']).to be true } it { expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') } - it { expect(json_response['source_url']).to eq('http://opensource.org/licenses/MIT') } + it { expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') } it { expect(json_response['description']).to include('A permissive license that is short and to the point.') } it { expect(json_response['conditions']).to eq(%w[include-copyright]) } it { expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) } @@ -26,7 +26,7 @@ describe API::Licenses, api: true do expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.size).to eq(15) - expect(json_response.first['key']).to eq('agpl-3.0') + expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') end describe 'the popular parameter' do @@ -37,7 +37,7 @@ describe API::Licenses, api: true do expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.size).to eq(3) - expect(json_response.first['key']).to eq('apache-2.0') + expect(json_response.map { |l| l['key'] }).to include('apache-2.0') end end end From b2370fefad16b6b0a808a75b9a7b1d1613187693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 13 Apr 2016 17:00:22 +0200 Subject: [PATCH 304/573] Fix specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- features/project/source/browse_files.feature | 13 ------------- features/steps/project/source/browse_files.rb | 4 ++-- .../project_owner_creates_license_file_spec.rb | 2 +- ..._to_create_license_file_in_empty_project_spec.rb | 9 ++++++--- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 1e09dbc4c8..fdffd71de8 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -123,19 +123,6 @@ Feature: Project Source Browse Files And I am redirected to the fork's new merge request page And I can see the replacement commit message - @javascript - Scenario: I can create file in empty repo - Given I own an empty project - And I visit my empty project page - And I create bare repo - When I click on "add a file" link - And I edit code - And I fill the new file name - And I fill the commit message - And I click on "Commit Changes" - Then I am redirected to the new file - And I should see its new content - @javascript Scenario: If I enter an illegal file name I see an error message Given I click on "New file" link in repo diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 21545eabc3..c26d7a1521 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -282,8 +282,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_link 'Create empty bare repository' end - step 'I click on "add a file" link' do - click_link 'adding a README' + step 'I click on "README" link' do + click_link 'README' # Remove pre-receive hook so we can push without auth FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive')) diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index c0fcfd0693..3d6ffbc4c6 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'creates a license file', feature: true, js: true do +feature 'project owner creates a license file', feature: true, js: true do include Select2Helper let(:project_master) { create(:user) } diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index 6c51d00d81..3268e24020 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -1,17 +1,18 @@ require 'spec_helper' -feature 'creates a license file in empty project', feature: true, js: true do +feature 'project owner sees a link to create a license file in empty project', feature: true, js: true do include Select2Helper let(:project_master) { create(:user) } - let(:project) { create(:project_empty_repo) } + let(:project) { create(:empty_project) } background do project.team << [project_master, :master] login_as(project_master) - visit namespace_project_path(project.namespace, project) end scenario 'project master creates a license file from a template' do + visit namespace_project_path(project.namespace, project) + click_link 'Create empty bare repository' click_on 'LICENSE' expect(current_path).to eq( @@ -26,6 +27,8 @@ feature 'creates a license file in empty project', feature: true, js: true do expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}") fill_in :commit_message, with: 'Add a LICENSE file', visible: true + # Remove pre-receive hook so we can push without auth + FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive')) click_button 'Commit Changes' expect(current_path).to eq( From 7e6f0ac0e04c3fa05a8d952bf86463001dc48487 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 18 Apr 2016 12:07:40 +0200 Subject: [PATCH 305/573] Count the number of SQL queries per transaction Fixes gitlab-org/gitlab-ce#15335 --- CHANGELOG | 1 + lib/gitlab/metrics/subscribers/active_record.rb | 1 + spec/lib/gitlab/metrics/subscribers/active_record_spec.rb | 3 +++ 3 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b929f1fd7b..502ed0e959 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.7.0 (unreleased) - Method instrumentation now uses Module#prepend instead of aliasing methods - Repository.clean_old_archives is now instrumented - Add support for environment variables on a job level in CI configuration file + - SQL query counts are now tracked per transaction - The Projects::HousekeepingService class has extra instrumentation - All service classes (those residing in app/services) are now instrumented - Developers can now add custom tags to transactions diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb index 8008b3bc89..96cad941d5 100644 --- a/lib/gitlab/metrics/subscribers/active_record.rb +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -9,6 +9,7 @@ module Gitlab return unless current_transaction current_transaction.increment(:sql_duration, event.duration) + current_transaction.increment(:sql_count, 1) end private diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb index 7bc070a4d0..e3293a0120 100644 --- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do expect(transaction).to receive(:increment). with(:sql_duration, 0.2) + expect(transaction).to receive(:increment). + with(:sql_count, 1) + subscriber.sql(event) end end From 6b124d42d9861a5f8c3dd5add7cabc864fa3673c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 15 Apr 2016 15:51:41 +0200 Subject: [PATCH 306/573] Add configurable shared runners text --- CHANGELOG | 2 ++ .../admin/application_settings_controller.rb | 1 + app/helpers/application_settings_helper.rb | 4 ++++ .../admin/application_settings/_form.html.haml | 6 +++++- .../projects/runners/_shared_runners.html.haml | 7 +++++-- ...hared_runners_text_to_application_settings.rb | 5 +++++ db/schema.rb | 3 ++- spec/features/runners_spec.rb | 16 ++++++++++++++++ 8 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb diff --git a/CHANGELOG b/CHANGELOG index 571c31e63a..d1b20a34b1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,8 @@ v 8.7.0 (unreleased) - Allow projects to be transfered to a lower visibility level group - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 + - Improved Markdown rendering performance !3389 (Yorick Peterse) + - Make shared runners text in box configurable (Kamil Trzciński) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - Expose project badges in project settings diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b4a28b8dd3..2520d4452a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -75,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :admin_notification_email, :user_oauth_applications, :shared_runners_enabled, + :shared_runners_text, :max_artifacts_size, :metrics_enabled, :metrics_host, diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 60a0ff32c9..914b0ef604 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -15,6 +15,10 @@ module ApplicationSettingsHelper current_application_settings.sign_in_text end + def shared_runners_text + current_application_settings.shared_runners_text + end + def user_oauth_applications? current_application_settings.user_oauth_applications end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 555aea554f..e5bc7d4fdb 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -153,7 +153,11 @@ = f.label :shared_runners_enabled do = f.check_box :shared_runners_enabled Enable shared runners for new projects - + .form-group + = f.label :shared_runners_text, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :shared_runners_text, class: 'form-control', rows: 4 + .help-block Markdown enabled .form-group = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' .col-sm-10 diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml index 6a37f444bb..ec47ed79e5 100644 --- a/app/views/projects/runners/_shared_runners.html.haml +++ b/app/views/projects/runners/_shared_runners.html.haml @@ -1,7 +1,10 @@ %h3 Shared runners -.bs-callout.bs-callout-warning - GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X. +.bs-callout.bs-callout-warning.shared-runners-description + - if shared_runners_text.present? + = markdown(shared_runners_text) + - else + GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X. %hr - if @project.shared_runners_enabled? = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do diff --git a/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb b/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb new file mode 100644 index 0000000000..d493044c67 --- /dev/null +++ b/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddSharedRunnersTextToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :shared_runners_text, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 42c261003b..0ab32cab8b 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: 20160415133440) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -78,6 +78,7 @@ ActiveRecord::Schema.define(version: 20160412140240) do t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" t.boolean "repository_checks_enabled", default: true + t.text "shared_runners_text" end create_table "audit_events", force: :cascade do |t| diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index e8886e7edf..1fe7645250 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -80,6 +80,22 @@ describe "Runners" do end end + describe "shared runners description" do + let(:shared_runners_text) { 'custom shared runners description' } + + before { stub_application_setting(shared_runners_text: shared_runners_text) } + + before do + @project = FactoryGirl.create :empty_project, shared_runners_enabled: false + @project.team << [user, :master] + visit runners_path(@project) + end + + it "sees shared runners description" do + expect(page.find(".shared-runners-description")).to have_content(shared_runners_text) + end + end + describe "show page" do before do @project = FactoryGirl.create :empty_project From 21041525af5a678053cc47e082dc7484a67dfdf5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 09:56:38 -0400 Subject: [PATCH 307/573] Update according to review comments --- CHANGELOG | 2 +- spec/features/runners_spec.rb | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d1b20a34b1..d691113e19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,7 +19,7 @@ v 8.7.0 (unreleased) - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Improved Markdown rendering performance !3389 - Improved Markdown rendering performance !3389 (Yorick Peterse) - - Make shared runners text in box configurable (Kamil Trzciński) + - Make shared runners text in box configurable - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - Expose project badges in project settings diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 1fe7645250..789b06d7c6 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -83,12 +83,11 @@ describe "Runners" do describe "shared runners description" do let(:shared_runners_text) { 'custom shared runners description' } - before { stub_application_setting(shared_runners_text: shared_runners_text) } - before do - @project = FactoryGirl.create :empty_project, shared_runners_enabled: false - @project.team << [user, :master] - visit runners_path(@project) + stub_application_setting(shared_runners_text: shared_runners_text) + project = FactoryGirl.create :empty_project, shared_runners_enabled: false + project.team << [user, :master] + visit runners_path(project) end it "sees shared runners description" do From b05f0a48584ea45cc89a8efaafd8e54642b8497c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 24 Mar 2016 12:55:04 -0300 Subject: [PATCH 308/573] Restrict user profiles based on restricted visibility levels --- app/controllers/users_controller.rb | 4 ++++ app/models/user.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8e7956da48..49ddcfed7b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,7 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! before_action :set_user + before_filter :authorize_read_user, only: [:show] def show respond_to do |format| @@ -74,6 +75,9 @@ class UsersController < ApplicationController end private + def authorize_read_user + render_404 unless @user.public? + end def set_user @user = User.find_by_username!(params[:username]) diff --git a/app/models/user.rb b/app/models/user.rb index 031315debd..e2b602d598 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -835,6 +835,10 @@ class User < ActiveRecord::Base notification_settings.find_or_initialize_by(source: source) end + def public? + current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + end + private def projects_union From 57519565f167cb771ffed504feefe7b0eb37c027 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 29 Mar 2016 12:24:42 -0300 Subject: [PATCH 309/573] Move verification to abilities --- .../groups/group_members_controller.rb | 7 ++++ .../projects/project_members_controller.rb | 7 ++++ app/controllers/users_controller.rb | 8 +++-- app/models/ability.rb | 33 ++++++++++++++++--- app/models/user.rb | 4 --- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index d5ef33888c..50398d5d3b 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -1,6 +1,7 @@ class Groups::GroupMembersController < Groups::ApplicationController # Authorize before_action :authorize_admin_group_member!, except: [:index, :leave] + before_action :authorize_read_group_members, only: [:index] def index @project = @group.projects.find(params[:project_id]) if params[:project_id] @@ -79,4 +80,10 @@ class Groups::GroupMembersController < Groups::ApplicationController def member_params params.require(:group_member).permit(:access_level, :user_id) end + + private + + def authorize_read_group_members + render_404 unless can?(current_user, :read_group_members, @group) + end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index e457db2f0b..7badbb47d0 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,6 +1,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize before_action :authorize_admin_project_member!, except: :leave + before_action :authorize_read_project_members, only: :index def index @project_members = @project.project_members @@ -112,4 +113,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end + + private + + def authorize_read_project_members + can?(current_user, :read_project_members, @project) + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 49ddcfed7b..69b66e161c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,8 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - before_action :set_user - before_filter :authorize_read_user, only: [:show] + #TO-DO Remove this "set_user" before action. It is not good to use before filters for loading database records. + before_action :set_user, except: [:show] + before_action :authorize_read_user, only: [:show] def show respond_to do |format| @@ -76,7 +77,8 @@ class UsersController < ApplicationController private def authorize_read_user - render_404 unless @user.public? + set_user + render_404 unless can?(current_user, :read_user, @user) end def set_user diff --git a/app/models/ability.rb b/app/models/ability.rb index c0bf6def7c..d3e724b84e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -18,6 +18,7 @@ class Ability when Namespace then namespace_abilities(user, subject) when GroupMember then group_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject) + when User then user_abilities() else [] end.concat(global_abilities(user)) end @@ -35,6 +36,8 @@ class Ability anonymous_project_abilities(subject) when subject.is_a?(Group) || subject.respond_to?(:group) anonymous_group_abilities(subject) + when subject.is_a?(User) + anonymous_user_abilities() else [] end @@ -67,6 +70,10 @@ class Ability # Allow to read issues by anonymous user if issue is not confidential rules << :read_issue unless subject.is_a?(Issue) && subject.confidential? + # Allow anonymous users to read project members if public is not a restricted level + restricted_public_level = current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + rules << :read_project_member unless restricted_public_level + rules - project_disabled_features_rules(project) else [] @@ -81,17 +88,23 @@ class Ability end def anonymous_group_abilities(subject) + rules = [] + group = if subject.is_a?(Group) subject else subject.group end - if group && group.public? - [:read_group] - else - [] + if group + rules << [:read_group] if group.public? + + # Allow anonymous users to read project members if public is not a restricted level + restricted_public_level = current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + rules << [:read_group_members] unless restricted_public_level end + + rules end def anonymous_personal_snippet_abilities(snippet) @@ -110,6 +123,11 @@ class Ability end end + def anonymous_user_abilities() + restricted_by_public = current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + [:read_user] unless restricted_by_public + end + def global_abilities(user) rules = [] rules << :create_group if user.can_create_group @@ -164,6 +182,7 @@ class Ability :download_code, :fork_project, :read_commit_status, + :read_project_members ] end @@ -285,7 +304,7 @@ class Ability def group_abilities(user, group) rules = [] - rules << :read_group if can_read_group?(user, group) + rules << [:read_group, :read_group_members] if can_read_group?(user, group) # Only group masters and group owners can create new projects if group.has_master?(user) || group.has_owner?(user) || user.admin? @@ -456,6 +475,10 @@ class Ability rules end + def user_abilities() + [:read_user] + end + def abilities @abilities ||= begin abilities = Six.new diff --git a/app/models/user.rb b/app/models/user.rb index e2b602d598..031315debd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -835,10 +835,6 @@ class User < ActiveRecord::Base notification_settings.find_or_initialize_by(source: source) end - def public? - current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - end - private def projects_union From 668d6ffa437aa5c920e987beb5de4e8dacbfd00c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 30 Mar 2016 17:14:21 -0300 Subject: [PATCH 310/573] Add specs and fix code --- app/controllers/users_controller.rb | 2 +- app/models/ability.rb | 25 +++++++++++-------- app/views/layouts/nav/_group.html.haml | 13 ++++++---- app/views/layouts/nav/_project.html.haml | 2 +- .../groups/group_members_controller_spec.rb | 19 ++++++++++++++ spec/controllers/users_controller_spec.rb | 22 ++++++++++++++++ 6 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 spec/controllers/groups/group_members_controller_spec.rb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 69b66e161c..642f5eea1d 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - #TO-DO Remove this "set_user" before action. It is not good to use before filters for loading database records. + #TODO felipe_artur: Remove this "set_user" before action. It is not good to use before filters for loading database records. before_action :set_user, except: [:show] before_action :authorize_read_user, only: [:show] diff --git a/app/models/ability.rb b/app/models/ability.rb index d3e724b84e..2914ca16b2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,4 +1,6 @@ class Ability + @public_restricted = nil + class << self def allowed(user, subject) return anonymous_abilities(user, subject) if user.nil? @@ -18,7 +20,7 @@ class Ability when Namespace then namespace_abilities(user, subject) when GroupMember then group_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject) - when User then user_abilities() + when User then user_abilities else [] end.concat(global_abilities(user)) end @@ -37,7 +39,7 @@ class Ability when subject.is_a?(Group) || subject.respond_to?(:group) anonymous_group_abilities(subject) when subject.is_a?(User) - anonymous_user_abilities() + anonymous_user_abilities else [] end @@ -71,8 +73,7 @@ class Ability rules << :read_issue unless subject.is_a?(Issue) && subject.confidential? # Allow anonymous users to read project members if public is not a restricted level - restricted_public_level = current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - rules << :read_project_member unless restricted_public_level + rules << :read_project_member unless restricted_public_level? rules - project_disabled_features_rules(project) else @@ -100,8 +101,7 @@ class Ability rules << [:read_group] if group.public? # Allow anonymous users to read project members if public is not a restricted level - restricted_public_level = current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - rules << [:read_group_members] unless restricted_public_level + rules << [:read_group_members] unless restricted_public_level? end rules @@ -123,9 +123,8 @@ class Ability end end - def anonymous_user_abilities() - restricted_by_public = current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - [:read_user] unless restricted_by_public + def anonymous_user_abilities + [:read_user] unless restricted_public_level? end def global_abilities(user) @@ -303,7 +302,6 @@ class Ability def group_abilities(user, group) rules = [] - rules << [:read_group, :read_group_members] if can_read_group?(user, group) # Only group masters and group owners can create new projects @@ -475,7 +473,7 @@ class Ability rules end - def user_abilities() + def user_abilities [:read_user] end @@ -493,6 +491,11 @@ class Ability private + def restricted_public_level? + @public_restricted ||= current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + @public_restricted + end + def named_abilities(name) [ :"read_#{name}", diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 55940741dc..927f61c89f 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -36,11 +36,14 @@ Merge Requests - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute %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') - %span - Members + + - if can?(current_user, :read_group_members, @group) + = 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 diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 86b46e8c75..d651de0fbe 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -77,7 +77,7 @@ Merge Requests %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) - - if project_nav_tab? :settings + - if project_nav_tab?(:settings) && can?(current_user, :read_project_members, @project) = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = icon('users fw') diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb new file mode 100644 index 0000000000..3a4dd2bf1f --- /dev/null +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Groups::GroupMembersController do + let(:user) { create(:user) } + let(:group) { create(:group) } + + + context "When public visibility level is restricted" do + before do + group.add_owner(user) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it 'does not show group members' do + get :index, group_id: group.path + expect(response.status).to eq(404) + end + end +end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 7337ff58be..f6235c29a1 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -38,6 +38,28 @@ describe UsersController do end end end + + context 'When public visibility level is restricted' do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + context 'when logged out' do + it 'renders 404' do + get :show, username: user.username + expect(response.status).to eq(404) + end + end + + context 'when logged in' do + before { sign_in(user) } + + it 'renders 404' do + get :show, username: user.username + expect(response.status).to eq(200) + end + end + end end describe 'GET #calendar' do From e8a77c0aee3eaf99793b3678a0eb97194244b339 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 31 Mar 2016 11:36:40 -0300 Subject: [PATCH 311/573] Fix code --- app/controllers/groups/group_members_controller.rb | 4 ++-- app/controllers/users_controller.rb | 6 +++--- app/models/ability.rb | 4 ---- spec/controllers/groups/group_members_controller_spec.rb | 3 +-- spec/controllers/users_controller_spec.rb | 3 ++- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 50398d5d3b..9fc7263580 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -1,7 +1,7 @@ class Groups::GroupMembersController < Groups::ApplicationController # Authorize before_action :authorize_admin_group_member!, except: [:index, :leave] - before_action :authorize_read_group_members, only: [:index] + before_action :authorize_read_group_members!, only: [:index] def index @project = @group.projects.find(params[:project_id]) if params[:project_id] @@ -83,7 +83,7 @@ class Groups::GroupMembersController < Groups::ApplicationController private - def authorize_read_group_members + def authorize_read_group_members! render_404 unless can?(current_user, :read_group_members, @group) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 642f5eea1d..233dca54b9 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,8 +1,7 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - #TODO felipe_artur: Remove this "set_user" before action. It is not good to use before filters for loading database records. before_action :set_user, except: [:show] - before_action :authorize_read_user, only: [:show] + before_action :authorize_read_user!, only: [:show] def show respond_to do |format| @@ -76,7 +75,8 @@ class UsersController < ApplicationController end private - def authorize_read_user + + def authorize_read_user! set_user render_404 unless can?(current_user, :read_user, @user) end diff --git a/app/models/ability.rb b/app/models/ability.rb index 2914ca16b2..874ec36094 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,5 +1,4 @@ class Ability - @public_restricted = nil class << self def allowed(user, subject) @@ -72,7 +71,6 @@ class Ability # Allow to read issues by anonymous user if issue is not confidential rules << :read_issue unless subject.is_a?(Issue) && subject.confidential? - # Allow anonymous users to read project members if public is not a restricted level rules << :read_project_member unless restricted_public_level? rules - project_disabled_features_rules(project) @@ -100,7 +98,6 @@ class Ability if group rules << [:read_group] if group.public? - # Allow anonymous users to read project members if public is not a restricted level rules << [:read_group_members] unless restricted_public_level? end @@ -493,7 +490,6 @@ class Ability def restricted_public_level? @public_restricted ||= current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - @public_restricted end def named_abilities(name) diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 3a4dd2bf1f..cf8f2a2805 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -4,8 +4,7 @@ describe Groups::GroupMembersController do let(:user) { create(:user) } let(:group) { create(:group) } - - context "When public visibility level is restricted" do + context "when public visibility level is restricted" do before do group.add_owner(user) stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index f6235c29a1..26acfd3bd9 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -54,9 +54,10 @@ describe UsersController do context 'when logged in' do before { sign_in(user) } - it 'renders 404' do + it 'renders show' do get :show, username: user.username expect(response.status).to eq(200) + expect(response).to render_template('show') end end end From 147879ae66fd742d13bbb5b72d492788bc48c8d9 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 1 Apr 2016 16:50:18 -0300 Subject: [PATCH 312/573] Fix specs --- app/models/ability.rb | 4 ++-- spec/controllers/users_controller_spec.rb | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 874ec36094..684834aa39 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -156,6 +156,7 @@ class Ability rules -= project_archived_rules end + rules << :read_project_members rules - project_disabled_features_rules(project) end end @@ -177,8 +178,7 @@ class Ability @public_project_rules ||= project_guest_rules + [ :download_code, :fork_project, - :read_commit_status, - :read_project_members + :read_commit_status ] end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 26acfd3bd9..7701da9747 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -30,10 +30,12 @@ describe UsersController do end describe 'when logged out' do + before { stub_application_setting(restricted_visibility_levels: []) } + it 'renders the show template' do get :show, username: user.username - expect(response).to be_success + expect(response.status).to eq(200) expect(response).to render_template('show') end end From 07b38c3b389b8b0b6a3d6af7a38555c189e71afe Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 5 Apr 2016 18:56:07 -0300 Subject: [PATCH 313/573] Code fixes --- .../projects/project_members_controller.rb | 7 ------- app/controllers/users_controller.rb | 21 +++++++++---------- app/models/ability.rb | 10 ++------- app/views/layouts/nav/_project.html.haml | 2 +- spec/controllers/users_controller_spec.rb | 2 +- 5 files changed, 14 insertions(+), 28 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 7badbb47d0..e457db2f0b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,7 +1,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize before_action :authorize_admin_project_member!, except: :leave - before_action :authorize_read_project_members, only: :index def index @project_members = @project.project_members @@ -113,10 +112,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end - - private - - def authorize_read_project_members - can?(current_user, :read_project_members, @project) - end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 233dca54b9..2ae180c8a1 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - before_action :set_user, except: [:show] + before_action :user before_action :authorize_read_user!, only: [:show] def show @@ -77,26 +77,25 @@ class UsersController < ApplicationController private def authorize_read_user! - set_user - render_404 unless can?(current_user, :read_user, @user) + render_404 unless can?(current_user, :read_user, user) end - def set_user - @user = User.find_by_username!(params[:username]) + def user + @user ||= User.find_by_username!(params[:username]) end def contributed_projects - ContributedProjectsFinder.new(@user).execute(current_user) + ContributedProjectsFinder.new(user).execute(current_user) end def contributions_calendar @contributions_calendar ||= Gitlab::ContributionsCalendar. - new(contributed_projects, @user) + new(contributed_projects, user) end def load_events # Get user activity feed for projects common for both users - @events = @user.recent_events. + @events = user.recent_events. merge(projects_for_current_user). references(:project). with_associations. @@ -105,16 +104,16 @@ class UsersController < ApplicationController def load_projects @projects = - PersonalProjectsFinder.new(@user).execute(current_user) + PersonalProjectsFinder.new(user).execute(current_user) .page(params[:page]) end def load_contributed_projects - @contributed_projects = contributed_projects.joined(@user) + @contributed_projects = contributed_projects.joined(user) end def load_groups - @groups = JoinedGroupsFinder.new(@user).execute(current_user) + @groups = JoinedGroupsFinder.new(user).execute(current_user) end def projects_for_current_user diff --git a/app/models/ability.rb b/app/models/ability.rb index 684834aa39..7c452c69d1 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,5 +1,4 @@ class Ability - class << self def allowed(user, subject) return anonymous_abilities(user, subject) if user.nil? @@ -58,7 +57,6 @@ class Ability :read_label, :read_milestone, :read_project_snippet, - :read_project_member, :read_merge_request, :read_note, :read_commit_status, @@ -71,8 +69,6 @@ class Ability # Allow to read issues by anonymous user if issue is not confidential rules << :read_issue unless subject.is_a?(Issue) && subject.confidential? - rules << :read_project_member unless restricted_public_level? - rules - project_disabled_features_rules(project) else [] @@ -96,9 +92,8 @@ class Ability end if group - rules << [:read_group] if group.public? - - rules << [:read_group_members] unless restricted_public_level? + rules << :read_group if group.public? + rules << :read_group_members unless restricted_public_level? end rules @@ -156,7 +151,6 @@ class Ability rules -= project_archived_rules end - rules << :read_project_members rules - project_disabled_features_rules(project) end end diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index d651de0fbe..2c9e215948 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -77,7 +77,7 @@ Merge Requests %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) - - if project_nav_tab?(:settings) && can?(current_user, :read_project_members, @project) + - if project_nav_tab?(:settings) = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = icon('users fw') diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 7701da9747..948935bc10 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -41,7 +41,7 @@ describe UsersController do end end - context 'When public visibility level is restricted' do + context 'when public visibility level is restricted' do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) end From ce96d482d9056e9acdfea02d055c2706653cba92 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 6 Apr 2016 18:09:24 -0300 Subject: [PATCH 314/573] Insert users check into api --- app/models/ability.rb | 6 +++--- lib/api/api_guard.rb | 4 ++++ lib/api/users.rb | 10 ++++++++-- spec/controllers/users_controller_spec.rb | 2 -- spec/requests/api/users_spec.rb | 18 ++++++++++++++++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 7c452c69d1..e327d4eef2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -91,8 +91,8 @@ class Ability subject.group end - if group - rules << :read_group if group.public? + if group.public? + rules << :read_group rules << :read_group_members unless restricted_public_level? end @@ -483,7 +483,7 @@ class Ability private def restricted_public_level? - @public_restricted ||= current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) end def named_abilities(name) diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index b9994fcefd..6ce5529abf 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -79,6 +79,10 @@ module APIGuard @current_user end + def public_access_restricted? + current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + end + private def find_access_token @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods) diff --git a/lib/api/users.rb b/lib/api/users.rb index 0a14bac07c..9647a40686 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -11,6 +11,10 @@ module API # GET /users?search=Admin # GET /users?username=root get do + if !current_user && public_access_restricted? + render_api_error!("Not authorized.", 403) + end + if params[:username].present? @users = User.where(username: params[:username]) else @@ -36,10 +40,12 @@ module API get ":id" do @user = User.find(params[:id]) - if current_user.is_admin? + if current_user.present? && current_user.is_admin? present @user, with: Entities::UserFull - else + elsif can?(current_user, :read_user, @user) present @user, with: Entities::User + else + render_api_error!("User not found.", 404) end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 948935bc10..8045c8b940 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -30,8 +30,6 @@ describe UsersController do end describe 'when logged out' do - before { stub_application_setting(restricted_visibility_levels: []) } - it 'renders the show template' do get :show, username: user.username diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 679227bf88..40b24c125b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -20,6 +20,24 @@ describe API::API, api: true do end context "when authenticated" do + #These specs are written just in case API authentication is not required anymore + context "when public level is restricted" do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + allow_any_instance_of(API::Helpers).to receive(:authenticate!).and_return(true) + end + + it "renders 403" do + get api("/users") + expect(response.status).to eq(403) + end + + it "renders 404" do + get api("/users/#{user.id}") + expect(response.status).to eq(404) + end + end + it "should return an array of users" do get api("/users", user) expect(response.status).to eq(200) From 09c8cf9de68c5d6f1250d6717b00f3b7e2008d3f Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 7 Apr 2016 16:36:26 -0300 Subject: [PATCH 315/573] Remove group members check --- app/controllers/groups/group_members_controller.rb | 7 ------- app/models/ability.rb | 8 +++----- app/views/layouts/nav/_group.html.haml | 13 +++++-------- app/views/layouts/nav/_project.html.haml | 2 +- .../groups/group_members_controller_spec.rb | 8 +++++--- 5 files changed, 14 insertions(+), 24 deletions(-) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 9fc7263580..d5ef33888c 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -1,7 +1,6 @@ class Groups::GroupMembersController < Groups::ApplicationController # Authorize before_action :authorize_admin_group_member!, except: [:index, :leave] - before_action :authorize_read_group_members!, only: [:index] def index @project = @group.projects.find(params[:project_id]) if params[:project_id] @@ -80,10 +79,4 @@ class Groups::GroupMembersController < Groups::ApplicationController def member_params params.require(:group_member).permit(:access_level, :user_id) end - - private - - def authorize_read_group_members! - render_404 unless can?(current_user, :read_group_members, @group) - end end diff --git a/app/models/ability.rb b/app/models/ability.rb index e327d4eef2..a4bde72d99 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -57,6 +57,7 @@ class Ability :read_label, :read_milestone, :read_project_snippet, + :read_project_member, :read_merge_request, :read_note, :read_commit_status, @@ -91,10 +92,7 @@ class Ability subject.group end - if group.public? - rules << :read_group - rules << :read_group_members unless restricted_public_level? - end + rules << :read_group if group.public? rules end @@ -293,7 +291,7 @@ class Ability def group_abilities(user, group) rules = [] - rules << [:read_group, :read_group_members] if can_read_group?(user, group) + rules << :read_group if can_read_group?(user, group) # Only group masters and group owners can create new projects if group.has_master?(user) || group.has_owner?(user) || user.admin? diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 927f61c89f..55940741dc 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -36,14 +36,11 @@ Merge Requests - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute %span.count= number_with_delimiter(merge_requests.count) - - - if can?(current_user, :read_group_members, @group) - = nav_link(controller: [:group_members]) do - = link_to group_group_members_path(@group), title: 'Members' do - = icon('users fw') - %span - Members - + = 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 diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 2c9e215948..86b46e8c75 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -77,7 +77,7 @@ Merge Requests %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) - - if project_nav_tab?(:settings) + - if project_nav_tab? :settings = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = icon('users fw') diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index cf8f2a2805..a598659871 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -4,15 +4,17 @@ describe Groups::GroupMembersController do let(:user) { create(:user) } let(:group) { create(:group) } - context "when public visibility level is restricted" do + context "index" do before do group.add_owner(user) stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) end - it 'does not show group members' do + it 'renders index with group members' do get :index, group_id: group.path - expect(response.status).to eq(404) + + expect(response.status).to eq(200) + expect(response).to render_template(:index) end end end From 7d54e721da0ccd21f0150bbb6ab60b51970033c2 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 7 Apr 2016 17:44:46 -0300 Subject: [PATCH 316/573] Insert instructions in admin page and permissions document --- app/views/admin/application_settings/_form.html.haml | 4 +++- doc/permissions/permissions.md | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 555aea554f..37b07c348d 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -26,7 +26,9 @@ .btn-group{ data: data_attrs } - restricted_level_checkboxes('restricted-visibility-help').each do |level| = level - %span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets + %span.help-block#restricted-visibility-help + Selected levels cannot be used by non-admin users for projects or snippets. + If public level is restricted user profiles are not accessible to not logged users. .form-group = f.label :import_sources, class: 'control-label col-sm-2' .col-sm-10 diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 6219693b8a..f8cfd2898f 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -93,3 +93,10 @@ An administrator can flag a user as external [through the API](../api/users.md) or by checking the checkbox on the admin panel. As an administrator, navigate to **Admin > Users** to create a new user or edit an existing one. There, you will find the option to flag the user as external. + +## Restricted visibility levels + +Visibility levels can be restricted in admin settings page by administrator, when +restricting a visibility level groups, projects and snippets are not allowed to be +created with that visibility setting. If the public visibility level is restricted +user profiles are accessible to not logged users. From 820c08cefd78e593e94012061be29000d523ffd0 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 12 Apr 2016 12:04:33 -0300 Subject: [PATCH 317/573] Fix documentation and improve permissions code --- app/models/ability.rb | 1 + app/views/admin/application_settings/_form.html.haml | 2 +- doc/permissions/permissions.md | 7 ------- doc/public_access/public_access.md | 3 +++ lib/api/api_guard.rb | 4 ---- lib/api/users.rb | 2 +- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index a4bde72d99..6103a2947e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -120,6 +120,7 @@ class Ability def global_abilities(user) rules = [] rules << :create_group if user.can_create_group + rules << :read_users_list rules end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 37b07c348d..aadd2c54f2 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -28,7 +28,7 @@ = level %span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets. - If public level is restricted user profiles are not accessible to not logged users. + If the public level is restricted, user profiles are only visible to logged in users. .form-group = f.label :import_sources, class: 'control-label col-sm-2' .col-sm-10 diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index f8cfd2898f..6219693b8a 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -93,10 +93,3 @@ An administrator can flag a user as external [through the API](../api/users.md) or by checking the checkbox on the admin panel. As an administrator, navigate to **Admin > Users** to create a new user or edit an existing one. There, you will find the option to flag the user as external. - -## Restricted visibility levels - -Visibility levels can be restricted in admin settings page by administrator, when -restricting a visibility level groups, projects and snippets are not allowed to be -created with that visibility setting. If the public visibility level is restricted -user profiles are accessible to not logged users. diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index 20aa90f0d6..17bb75ecec 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -58,6 +58,9 @@ you are logged in or not. When visiting the public page of a user, you can only see the projects which you are privileged to. +If the public level is restricted, user profiles are only visible to logged in users. + + ## Restricting the use of public or internal projects In the Admin area under **Settings** (`/admin/application_settings`), you can diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 6ce5529abf..b9994fcefd 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -79,10 +79,6 @@ module APIGuard @current_user end - def public_access_restricted? - current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - end - private def find_access_token @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods) diff --git a/lib/api/users.rb b/lib/api/users.rb index 9647a40686..315268fc0c 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -11,7 +11,7 @@ module API # GET /users?search=Admin # GET /users?username=root get do - if !current_user && public_access_restricted? + unless can?(current_user, :read_users_list, nil) render_api_error!("Not authorized.", 403) end From 2366768d3b28ea70c91fc49c471e66152650d442 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 13 Apr 2016 15:37:17 -0300 Subject: [PATCH 318/573] Add changelog entry --- CHANGELOG | 1 + lib/api/users.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 571c31e63a..3cb89582d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.7.0 (unreleased) - Project switcher uses new dropdown styling - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) + - Restrict user profiles when public visibility level is restricted. - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) - Add setting for customizing the list of trusted proxies !3524 diff --git a/lib/api/users.rb b/lib/api/users.rb index 315268fc0c..ea6fa2dc8a 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -40,7 +40,7 @@ module API get ":id" do @user = User.find(params[:id]) - if current_user.present? && current_user.is_admin? + if current_user && current_user.is_admin? present @user, with: Entities::UserFull elsif can?(current_user, :read_user, @user) present @user, with: Entities::User From 0ce5cc99621c84dfc61f6105d177ced4d9a4ed85 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 10:18:46 -0400 Subject: [PATCH 319/573] Resolve merge --- lib/ci/gitlab_ci_yaml_processor.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 2a228845ec..d8a7fcfe15 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -120,17 +120,6 @@ module Ci true end - def validate_job!(name, job) - validate_job_name!(name) - validate_job_keys!(name, job) - validate_job_types!(name, job) - - validate_job_stage!(name, job) if job[:stage] - validate_job_cache!(name, job) if job[:cache] - validate_job_artifacts!(name, job) if job[:artifacts] - validate_job_dependencies!(name, job) if job[:dependencies] - end - private def validate_global! @@ -170,12 +159,9 @@ module Ci raise ValidationError, "cache:untracked parameter should be an boolean" end -<<<<<<< HEAD if @cache[:paths] && !validate_array_of_strings(@cache[:paths]) raise ValidationError, "cache:paths parameter should be an array of strings" end -======= - true end def validate_job!(name, job) @@ -188,7 +174,6 @@ module Ci validate_job_cache!(name, job) if job[:cache] validate_job_artifacts!(name, job) if job[:artifacts] validate_job_dependencies!(name, job) if job[:dependencies] ->>>>>>> origin/master end def validate_job_name!(name) From d13bba44f0de0aa5df00c37b4f743504d187bb03 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 15 Apr 2016 18:14:41 -0300 Subject: [PATCH 320/573] Use GitHub Issue/PR number as iid to keep references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With these changes we don’t lost the issue/pr references when importing them to GitLab. --- app/models/concerns/internal_id.rb | 10 ++++++---- lib/gitlab/github_import/issue_formatter.rb | 1 + lib/gitlab/github_import/pull_request_formatter.rb | 1 + spec/lib/gitlab/github_import/issue_formatter_spec.rb | 2 ++ .../github_import/pull_request_formatter_spec.rb | 3 +++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb index 51288094ef..5382dde676 100644 --- a/app/models/concerns/internal_id.rb +++ b/app/models/concerns/internal_id.rb @@ -7,11 +7,13 @@ module InternalId end def set_iid - records = project.send(self.class.name.tableize) - records = records.with_deleted if self.paranoid? - max_iid = records.maximum(:iid) + if iid.blank? + records = project.send(self.class.name.tableize) + records = records.with_deleted if self.paranoid? + max_iid = records.maximum(:iid) - self.iid = max_iid.to_i + 1 + self.iid = max_iid.to_i + 1 + end end def to_param diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb index 1e3ba44f27..acb332cb0c 100644 --- a/lib/gitlab/github_import/issue_formatter.rb +++ b/lib/gitlab/github_import/issue_formatter.rb @@ -3,6 +3,7 @@ module Gitlab class IssueFormatter < BaseFormatter def attributes { + iid: number, project: project, title: raw_data.title, description: description, diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index 4e507b090e..5ee8a14624 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -3,6 +3,7 @@ module Gitlab class PullRequestFormatter < BaseFormatter def attributes { + iid: number, title: raw_data.title, description: description, source_project: source_project, diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index fd05428b32..4f3d7f4405 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -30,6 +30,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do it 'returns formatted attributes' do expected = { + iid: 1347, project: project, title: 'Found a bug', description: "*Created by: octocat*\n\nI'm having a problem with this.", @@ -50,6 +51,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do it 'returns formatted attributes' do expected = { + iid: 1347, project: project, title: 'Found a bug', description: "*Created by: octocat*\n\nI'm having a problem with this.", diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index e49dcb4234..11249e57ca 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -35,6 +35,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do it 'returns formatted attributes' do expected = { + iid: 1347, title: 'New feature', description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, @@ -58,6 +59,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do it 'returns formatted attributes' do expected = { + iid: 1347, title: 'New feature', description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, @@ -81,6 +83,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do it 'returns formatted attributes' do expected = { + iid: 1347, title: 'New feature', description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, From c9f2fc3c1802aabf913df3094aebf2d020b19f2a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 15 Apr 2016 18:16:11 -0300 Subject: [PATCH 321/573] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index e641adbfce..330b500dee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,7 @@ v 8.7.0 (unreleased) - Author and participants are displayed first on users autocompletion - Show number sign on external issue reference text (Florent Baldino) - Updated print style for issues + - Use GitHub Issue/PR number as iid to keep references v 8.6.6 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 From 17a730178def129d49e92d7a9a1b309efc67c755 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 18 Apr 2016 11:40:24 -0400 Subject: [PATCH 322/573] Remove additional misuse of `data-original-title` attribute --- app/helpers/projects_helper.rb | 2 +- app/views/shared/milestones/_issuable.html.haml | 2 +- spec/features/issues/update_issues_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7e00aaccea..4470aa16e3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -52,7 +52,7 @@ module ProjectsHelper link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe else title = opts[:title].sub(":name", sanitize(author.name)) - link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe + link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe end end diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index e1127b2311..47b66d44e4 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -23,5 +23,5 @@ - if assignee = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }), - class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do + class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '') diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb index 3eb903a93f..b03dd0f666 100644 --- a/spec/features/issues/update_issues_spec.rb +++ b/spec/features/issues/update_issues_spec.rb @@ -48,7 +48,7 @@ feature 'Multiple issue updating from issues#index', feature: true do click_update_issues_button page.within('.issue .controls') do - expect(find('.author_link')["data-original-title"]).to have_content(user.name) + expect(find('.author_link')["title"]).to have_content(user.name) end end From b9698d2b4512cca4687579aafba6a4403cbc6067 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 18 Apr 2016 17:42:48 +0200 Subject: [PATCH 323/573] Add "action" tag to /internal/allowed API This allows us to re-use any other analytics that rely on the "action" tag having a value set. --- CHANGELOG | 1 + lib/api/internal.rb | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 350be45627..9a6f1bbfef 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.7.0 (unreleased) + - Transactions for /internal/allowed now have an "action" tag set - Method instrumentation now uses Module#prepend instead of aliasing methods - Repository.clean_old_archives is now instrumented - Add support for environment variables on a job level in CI configuration file diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 2200208b94..8cfa1f1556 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -23,9 +23,11 @@ module API end post "/allowed" do + Gitlab::Metrics.tag_transaction('action', 'Grape#/internal/allowed') + status 200 - actor = + actor = if params[:key_id] Key.find_by(id: params[:key_id]) elsif params[:user_id] @@ -33,7 +35,7 @@ module API end project_path = params[:project] - + # Check for *.wiki repositories. # Strip out the .wiki from the pathname before finding the # project. This applies the correct project permissions to From ea7114541838652fe3e6dcf3bdc4c3812f013e7b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Apr 2016 16:45:49 +0100 Subject: [PATCH 324/573] Fixed issue with multi select not working with arrow keys --- app/assets/javascripts/gl_dropdown.js.coffee | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 2dc37257e2..fac928ef20 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -32,10 +32,8 @@ class GitLabDropdownFilter else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS $inputContainer.removeClass HAS_VALUE_CLASS - if keyCode is 13 and @input.val() isnt "" - if @options.enterCallback - @options.enterCallback() - return + if keyCode is 13 + return false clearTimeout timeout timeout = setTimeout => @@ -132,7 +130,6 @@ class GitLabDropdown @filterInput = @getElement(FILTER_INPUT) @highlight = false @filterInputBlur = true - @enterCallback = true } = @options self = @ @@ -178,9 +175,6 @@ class GitLabDropdown callback: (data) => currentIndex = -1 @parseData data - enterCallback: => - if @enterCallback - @selectRowAtIndex 0 # Event listeners From 872d31d6e9566f55f9eadd12d496d07e86c13f0a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Apr 2016 16:56:48 +0100 Subject: [PATCH 325/573] Fixed issue with spinner not hiding on diff changes --- app/assets/javascripts/merge_request_tabs.js.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 1ab6e5114b..e8613cab72 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -183,9 +183,10 @@ class @MergeRequestTabs else $diffLine = $('td', $diffLine) - $diffLine.addClass 'hll' - diffLineTop = $diffLine.offset().top - navBarHeight = $('.navbar-gitlab').outerHeight() + if $diffLine.length + $diffLine.addClass 'hll' + diffLineTop = $diffLine.offset().top + navBarHeight = $('.navbar-gitlab').outerHeight() loadBuilds: (source) -> return if @buildsLoaded From 5f98fdcdc9bca5b33220a1bb2b6ded32606271ff Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 18 Apr 2016 12:26:32 -0400 Subject: [PATCH 326/573] Add sentry logger to GitLab --- app/assets/javascripts/application.js.coffee | 3 +++ lib/gitlab/gon_helper.rb | 1 + vendor/assets/javascripts/raven.min.js | 3 +++ 3 files changed, 7 insertions(+) create mode 100644 vendor/assets/javascripts/raven.min.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 6f435e4c54..ab2432e138 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -295,3 +295,6 @@ $ -> checkInitialSidebarSize() new Aside() + + if gon.sentry_dsn? + Raven.config('your public dsn').install() \ No newline at end of file diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 5ebaad6ca6..4bdaedbf9b 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -7,6 +7,7 @@ module Gitlab gon.max_file_size = current_application_settings.max_attachment_size gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class + gon.sentry_dsn = ApplicationSetting.current.sentry_dsn if current_user gon.current_user_id = current_user.id diff --git a/vendor/assets/javascripts/raven.min.js b/vendor/assets/javascripts/raven.min.js new file mode 100644 index 0000000000..9df09c0737 --- /dev/null +++ b/vendor/assets/javascripts/raven.min.js @@ -0,0 +1,3 @@ +/*! Raven.js 2.3.0 (64245e6) | github.com/getsentry/raven-js */ +!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.Raven=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;d.length>g;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";function d(a){this.name="RavenConfigError",this.message=a}d.prototype=new Error,d.prototype.constructor=d,b.exports=d},{}],2:[function(a,b,c){"use strict";function d(){return+new Date}function e(){this.a=!("object"!=typeof JSON||!JSON.stringify),this.b="undefined"!=typeof document,this.c=null,this.d=null,this.e=null,this.f=null,this.g=null,this.h={},this.i={logger:"javascript",ignoreErrors:[],ignoreUrls:[],whitelistUrls:[],includePaths:[],crossOrigin:"anonymous",collectWindowErrors:!0,maxMessageLength:0,stackTraceLimit:50},this.j=0,this.k=!1,this.l=Error.stackTraceLimit,this.m=window.console||{},this.n={},this.o=[],this.p=d(),this.q=[];for(var a in this.m)this.n[a]=this.m[a]}var f=a(5),g=a(1),h=a(4),i=h.isFunction,j=h.isUndefined,k=h.isError,l=h.isEmptyObject,m=h.hasKey,n=h.joinRegExp,o=h.each,p=h.objectMerge,q=h.truncate,r=h.urlencode,s=h.uuid4,t="source protocol user pass host port path".split(" "),u=/^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;e.prototype={VERSION:"2.3.0",debug:!1,TraceKit:f,config:function(a,b){var c=this;if(this.e)return this.r("error","Error: Raven has already been configured"),this;if(!a)return this;b&&o(b,function(a,b){"tags"===a||"extra"===a?c.h[a]=b:c.i[a]=b});var d=this.s(a),e=d.path.lastIndexOf("/"),g=d.path.substr(1,e);return this.t=a,this.i.ignoreErrors.push(/^Script error\.?$/),this.i.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/),this.i.ignoreErrors=n(this.i.ignoreErrors),this.i.ignoreUrls=this.i.ignoreUrls.length?n(this.i.ignoreUrls):!1,this.i.whitelistUrls=this.i.whitelistUrls.length?n(this.i.whitelistUrls):!1,this.i.includePaths=n(this.i.includePaths),this.f=d.user,this.u=d.pass&&d.pass.substr(1),this.g=d.path.substr(e+1),this.e=this.v(d),this.w=this.e+"/"+g+"api/"+this.g+"/store/",this.i.fetchContext&&(f.remoteFetching=!0),this.i.linesOfContext&&(f.linesOfContext=this.i.linesOfContext),f.collectWindowErrors=!!this.i.collectWindowErrors,this},install:function(){var a=this;return this.isSetup()&&!this.k&&(f.report.subscribe(function(){a.x.apply(a,arguments)}),this.y(),this.z(),this.k=!0),Error.stackTraceLimit=this.i.stackTraceLimit,this},context:function(a,b,c){return i(a)&&(c=b||[],b=a,a=void 0),this.wrap(a,b).apply(this,c)},wrap:function(a,b){function c(){for(var c=[],e=arguments.length,f=!a||a&&a.deep!==!1;e--;)c[e]=f?d.wrap(a,arguments[e]):arguments[e];try{return b.apply(this,c)}catch(g){throw d.A(),d.captureException(g,a),g}}var d=this;if(j(b)&&!i(a))return a;if(i(a)&&(b=a,a=void 0),!i(b))return b;try{if(b.B)return b}catch(e){return b}if(b.C)return b.C;for(var f in b)m(b,f)&&(c[f]=b[f]);return b.C=c,c.prototype=b.prototype,c.B=!0,c.D=b,c},uninstall:function(){return f.report.uninstall(),this.E(),Error.stackTraceLimit=this.l,this.k=!1,this},captureException:function(a,b){if(!k(a))return this.captureMessage(a,b);this.c=a;try{var c=f.computeStackTrace(a);this.F(c,b)}catch(d){if(a!==d)throw d}return this},captureMessage:function(a,b){return this.i.ignoreErrors.test&&this.i.ignoreErrors.test(a)?void 0:(this.G(p({message:a+""},b)),this)},addPlugin:function(a){var b=Array.prototype.slice.call(arguments,1);return this.o.push([a,b]),this.k&&this.z(),this},setUserContext:function(a){return this.h.user=a,this},setExtraContext:function(a){return this.H("extra",a),this},setTagsContext:function(a){return this.H("tags",a),this},clearContext:function(){return this.h={},this},getContext:function(){return JSON.parse(JSON.stringify(this.h))},setRelease:function(a){return this.i.release=a,this},setDataCallback:function(a){return this.i.dataCallback=a,this},setShouldSendCallback:function(a){return this.i.shouldSendCallback=a,this},setTransport:function(a){return this.i.transport=a,this},lastException:function(){return this.c},lastEventId:function(){return this.d},isSetup:function(){return this.a?this.e?!0:(this.ravenNotConfiguredError||(this.ravenNotConfiguredError=!0,this.r("error","Error: Raven has not been configured.")),!1):!1},afterLoad:function(){var a=window.RavenConfig;a&&this.config(a.dsn,a.config).install()},showReportDialog:function(a){if(window.document){a=a||{};var b=a.eventId||this.lastEventId();if(!b)throw new g("Missing eventId");var c=a.dsn||this.t;if(!c)throw new g("Missing DSN");var d=encodeURIComponent,e="";e+="?eventId="+d(b),e+="&dsn="+d(c);var f=a.user||this.h.user;f&&(f.name&&(e+="&name="+d(f.name)),f.email&&(e+="&email="+d(f.email)));var h=this.v(this.s(c)),i=document.createElement("script");i.async=!0,i.src=h+"/api/embed/error-page/"+e,(document.head||document.body).appendChild(i)}},A:function(){var a=this;this.j+=1,setTimeout(function(){a.j-=1})},I:function(a,b){var c,d;if(this.b){b=b||{},a="raven"+a.substr(0,1).toUpperCase()+a.substr(1),document.createEvent?(c=document.createEvent("HTMLEvents"),c.initEvent(a,!0,!0)):(c=document.createEventObject(),c.eventType=a);for(d in b)m(b,d)&&(c[d]=b[d]);if(document.createEvent)document.dispatchEvent(c);else try{document.fireEvent("on"+c.eventType.toLowerCase(),c)}catch(e){}}},y:function(){function a(a,b,d,e){var f=a[b];a[b]=d(f),e||c.q.push([a,b,f])}function b(a){return function(b,d){var e=[].slice.call(arguments),f=e[0];return i(f)&&(e[0]=c.wrap(f)),a.apply?a.apply(this,e):a(e[0],e[1])}}var c=this;a(window,"setTimeout",b),a(window,"setInterval",b),window.requestAnimationFrame&&a(window,"requestAnimationFrame",function(a){return function(b){return a(c.wrap(b))}}),"EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload".replace(/\w+/g,function(b){var d=window[b]&&window[b].prototype;d&&d.hasOwnProperty&&d.hasOwnProperty("addEventListener")&&(a(d,"addEventListener",function(a){return function(b,d,e,f){try{d&&d.handleEvent&&(d.handleEvent=c.wrap(d.handleEvent))}catch(g){}return a.call(this,b,c.wrap(d),e,f)}}),a(d,"removeEventListener",function(a){return function(b,c,d,e){return c=c&&(c.C?c.C:c),a.call(this,b,c,d,e)}}))}),"XMLHttpRequest"in window&&a(XMLHttpRequest.prototype,"send",function(b){return function(d){var e=this;return"onreadystatechange onload onerror onprogress".replace(/\w+/g,function(b){b in e&&"[object Function]"===Object.prototype.toString.call(e[b])&&a(e,b,function(a){return c.wrap(a)},!0)}),b.apply(this,arguments)}});var d=window.jQuery||window.$;d&&d.fn&&d.fn.ready&&a(d.fn,"ready",function(a){return function(b){return a.call(this,c.wrap(b))}})},E:function(){for(var a;this.q.length;){a=this.q.shift();var b=a[0],c=a[1],d=a[2];b[c]=d}},z:function(){var a=this;o(this.o,function(b,c){var d=c[0],e=c[1];d.apply(a,[a].concat(e))})},s:function(a){var b=u.exec(a),c={},d=7;try{for(;d--;)c[t[d]]=b[d]||""}catch(e){throw new g("Invalid DSN: "+a)}if(c.pass&&!this.i.allowSecretKey)throw new g("Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key");return c},v:function(a){var b="//"+a.host+(a.port?":"+a.port:"");return a.protocol&&(b=a.protocol+":"+b),b},x:function(){this.j||this.F.apply(this,arguments)},F:function(a,b){var c=this,d=[];a.stack&&a.stack.length&&o(a.stack,function(a,b){var e=c.J(b);e&&d.push(e)}),this.I("handle",{stackInfo:a,options:b}),this.K(a.name,a.message,a.url,a.lineno,d.slice(0,this.i.stackTraceLimit),b)},J:function(a){if(a.url){var b,c={filename:a.url,lineno:a.line,colno:a.column,"function":a.func||"?"},d=this.L(a);if(d){var e=["pre_context","context_line","post_context"];for(b=3;b--;)c[e[b]]=d[b]}return c.in_app=!(this.i.includePaths.test&&!this.i.includePaths.test(c.filename)||/(Raven|TraceKit)\./.test(c["function"])||/raven\.(min\.)?js$/.test(c.filename)),c}},L:function(a){if(a.context&&this.i.fetchContext){for(var b=a.context,c=~~(b.length/2),d=b.length,e=!1;d--;)if(b[d].length>300){e=!0;break}if(e){if(j(a.column))return;return[[],b[c].substr(a.column,50),[]]}return[b.slice(0,c),b[c],b.slice(c+1)]}},K:function(a,b,c,d,e,f){var g,h;if((!this.i.ignoreErrors.test||!this.i.ignoreErrors.test(b))&&(b+="",b=q(b,this.i.maxMessageLength),h=(a?a+": ":"")+b,h=q(h,this.i.maxMessageLength),e&&e.length?(c=e[0].filename||c,e.reverse(),g={frames:e}):c&&(g={frames:[{filename:c,lineno:d,in_app:!0}]}),(!this.i.ignoreUrls.test||!this.i.ignoreUrls.test(c))&&(!this.i.whitelistUrls.test||this.i.whitelistUrls.test(c)))){var i=p({exception:{values:[{type:a,value:b,stacktrace:g}]},culprit:c,message:h},f);this.G(i)}},M:function(a){var b=this.i.maxMessageLength;if(a.message=q(a.message,b),a.exception){var c=a.exception.values[0];c.value=q(c.value,b)}return a},N:function(){if(this.b&&document.location&&document.location.href){var a={headers:{"User-Agent":navigator.userAgent}};return a.url=document.location.href,document.referrer&&(a.headers.Referer=document.referrer),a}},G:function(a){var b=this,c=this.i,e={project:this.g,logger:c.logger,platform:"javascript"},f=this.N();if(f&&(e.request=f),a=p(e,a),a.tags=p(p({},this.h.tags),a.tags),a.extra=p(p({},this.h.extra),a.extra),a.extra["session:duration"]=d()-this.p,l(a.tags)&&delete a.tags,this.h.user&&(a.user=this.h.user),c.release&&(a.release=c.release),c.serverName&&(a.server_name=c.serverName),i(c.dataCallback)&&(a=c.dataCallback(a)||a),a&&!l(a)&&(!i(c.shouldSendCallback)||c.shouldSendCallback(a))&&(this.d=a.event_id||(a.event_id=s()),a=this.M(a),this.r("debug","Raven about to send:",a),this.isSetup())){var g={sentry_version:"7",sentry_client:"raven-js/"+this.VERSION,sentry_key:this.f};this.u&&(g.sentry_secret=this.u);var h=this.w;(c.transport||this.O).call(this,{url:h,auth:g,data:a,options:c,onSuccess:function(){b.I("success",{data:a,src:h})},onError:function(){b.I("failure",{data:a,src:h})}})}},P:function(a){a.auth.sentry_data=JSON.stringify(a.data);var b=this.Q(),c=a.url+"?"+r(a.auth),d=a.options.crossOrigin;(d||""===d)&&(b.crossOrigin=d),b.onload=a.onSuccess,b.onerror=b.onabort=a.onError,b.src=c},R:function(a){function b(){200===c.status?a.onSuccess&&a.onSuccess():a.onError&&a.onError()}var c,d=a.url;c=new XMLHttpRequest,"withCredentials"in c?c.onreadystatechange=function(){4===c.readyState&&b()}:(c=new XDomainRequest,d=d.replace(/^https?:/,""),c.onload=b),c.open("POST",d+"?"+r(a.auth)),c.send(JSON.stringify(a.data))},O:function(a){var b="withCredentials"in new XMLHttpRequest||"undefined"!=typeof XDomainRequest;return(b?this.R:this.P)(a)},Q:function(){return document.createElement("img")},r:function(a){this.n[a]&&this.debug&&Function.prototype.apply.call(this.n[a],this.m,[].slice.call(arguments,1))},H:function(a,b){j(b)?delete this.h[a]:this.h[a]=p(this.h[a]||{},b)}},e.prototype.setUser=e.prototype.setUserContext,e.prototype.setReleaseContext=e.prototype.setRelease,b.exports=e},{1:1,4:4,5:5}],3:[function(a,b,c){"use strict";var d=a(2),e=window.Raven,f=new d;f.noConflict=function(){return window.Raven=e,f},f.afterLoad(),b.exports=f},{2:2}],4:[function(a,b,c){"use strict";function d(a){return void 0===a}function e(a){return"function"==typeof a}function f(a){return"[object String]"===q.toString.call(a)}function g(a){return"object"==typeof a&&null!==a}function h(a){for(var b in a)return!1;return!0}function i(a){var b=q.toString.call(a);return g(a)&&"[object Error]"===b||"[object Exception]"===b||a instanceof Error}function j(a,b){var c,e;if(d(a.length))for(c in a)m(a,c)&&b.call(null,c,a[c]);else if(e=a.length)for(c=0;e>c;c++)b.call(null,c,a[c])}function k(a,b){return b?(j(b,function(b,c){a[b]=c}),a):a}function l(a,b){return!b||b>=a.length?a:a.substr(0,b)+"…"}function m(a,b){return q.hasOwnProperty.call(a,b)}function n(a){for(var b,c=[],d=0,e=a.length;e>d;d++)b=a[d],f(b)?c.push(b.replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1")):b&&b.source&&c.push(b.source);return new RegExp(c.join("|"),"i")}function o(a){var b=[];return j(a,function(a,c){b.push(encodeURIComponent(a)+"="+encodeURIComponent(c))}),b.join("&")}function p(){var a=window.crypto||window.msCrypto;if(!d(a)&&a.getRandomValues){var b=new Uint16Array(8);a.getRandomValues(b),b[3]=4095&b[3]|16384,b[4]=16383&b[4]|32768;var c=function(a){for(var b=a.toString(16);4>b.length;)b="0"+b;return b};return c(b[0])+c(b[1])+c(b[2])+c(b[3])+c(b[4])+c(b[5])+c(b[6])+c(b[7])}return"xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0,c="x"===a?b:3&b|8;return c.toString(16)})}var q=Object.prototype;b.exports={isUndefined:d,isFunction:e,isString:f,isObject:g,isEmptyObject:h,isError:i,each:j,objectMerge:k,truncate:l,hasKey:m,joinRegExp:n,urlencode:o,uuid4:p}},{}],5:[function(a,b,c){"use strict";function d(){return"undefined"==typeof document?"":document.location.href}var e=a(4),f=e.hasKey,g=e.isString,h=e.isUndefined,i={remoteFetching:!1,collectWindowErrors:!0,linesOfContext:7,debug:!1},j=[].slice,k="?",l=/^(?:Uncaught )?((?:Eval|Internal|Range|Reference|Syntax|Type|URI)Error)\: ?(.*)$/;i.report=function(){function a(a){k(),r.push(a)}function b(a){for(var b=r.length-1;b>=0;--b)r[b]===a&&r.splice(b,1)}function c(){m(),r=[]}function e(a,b){var c=null;if(!b||i.collectWindowErrors){for(var d in r)if(f(r,d))try{r[d].apply(null,[a].concat(j.call(arguments,2)))}catch(e){c=e}if(c)throw c}}function h(a,b,c,f,h){var j=null;if(u)i.computeStackTrace.augmentStackTraceWithInitialElement(u,b,c,a),n();else if(h)j=i.computeStackTrace(h),e(j,!0);else{var k={url:b,line:c,column:f};k.func=i.computeStackTrace.guessFunctionName(k.url,k.line),k.context=i.computeStackTrace.gatherContext(k.url,k.line);var m,o=void 0,q=a;if(g(a)){var m=a.match(l);m&&(o=m[1],q=m[2])}j={name:o,message:q,url:d(),stack:[k]},e(j,!0)}return p?p.apply(this,arguments):!1}function k(){q||(p=window.onerror,window.onerror=h,q=!0)}function m(){q&&(window.onerror=p,q=!1,p=void 0)}function n(){var a=u,b=s;s=null,u=null,t=null,e.apply(null,[a,!1].concat(b))}function o(a,b){var c=j.call(arguments,1);if(u){if(t===a)return;n()}var d=i.computeStackTrace(a);if(u=d,t=a,s=c,window.setTimeout(function(){t===a&&n()},d.incomplete?2e3:0),b!==!1)throw a}var p,q,r=[],s=null,t=null,u=null;return o.subscribe=a,o.unsubscribe=b,o.uninstall=c,o}(),i.computeStackTrace=function(){function a(a){if(!i.remoteFetching)return"";try{var b=function(){try{return new window.XMLHttpRequest}catch(a){return new window.ActiveXObject("Microsoft.XMLHTTP")}},c=b();return c.open("GET",a,!1),c.send(""),c.responseText}catch(d){return""}}function b(b){if(!g(b))return[];if(!f(v,b)){var c="",d="";try{d=document.domain}catch(e){}-1!==b.indexOf(d)&&(c=a(b)),v[b]=c?c.split("\n"):[]}return v[b]}function c(a,c){var d,e=/function ([^(]*)\(([^)]*)\)/,f=/['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,g="",i=10,j=b(a);if(!j.length)return k;for(var l=0;i>l;++l)if(g=j[c-l]+g,!h(g)){if(d=f.exec(g))return d[1];if(d=e.exec(g))return d[1]}return k}function e(a,c){var d=b(a);if(!d.length)return null;var e=[],f=Math.floor(i.linesOfContext/2),g=f+i.linesOfContext%2,j=Math.max(0,c-f-1),k=Math.min(d.length,c+g-1);c-=1;for(var l=j;k>l;++l)h(d[l])||e.push(d[l]);return e.length>0?e:null}function j(a){return a.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g,"\\$&")}function l(a){return j(a).replace("<","(?:<|<)").replace(">","(?:>|>)").replace("&","(?:&|&)").replace('"','(?:"|")').replace(/\s+/g,"\\s+")}function m(a,c){for(var d,e,f=0,g=c.length;g>f;++f)if((d=b(c[f])).length&&(d=d.join("\n"),e=a.exec(d)))return{url:c[f],line:d.substring(0,e.index).split("\n").length,column:e.index-d.lastIndexOf("\n",e.index)-1};return null}function n(a,c,d){var e,f=b(c),g=new RegExp("\\b"+j(a)+"\\b");return d-=1,f&&f.length>d&&(e=g.exec(f[d]))?e.index:null}function o(a){if("undefined"!=typeof document){for(var b,c,d,e,f=[window.location.href],g=document.getElementsByTagName("script"),h=""+a,i=/^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,k=/^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,n=0;g.length>n;++n){var o=g[n];o.src&&f.push(o.src)}if(d=i.exec(h)){var p=d[1]?"\\s+"+d[1]:"",q=d[2].split(",").join("\\s*,\\s*");b=j(d[3]).replace(/;$/,";?"),c=new RegExp("function"+p+"\\s*\\(\\s*"+q+"\\s*\\)\\s*{\\s*"+b+"\\s*}")}else c=new RegExp(j(h).replace(/\s+/g,"\\s+"));if(e=m(c,f))return e;if(d=k.exec(h)){var r=d[1];if(b=l(d[2]),c=new RegExp("on"+r+"=[\\'\"]\\s*"+b+"\\s*[\\'\"]","i"),e=m(c,f[0]))return e;if(c=new RegExp(b),e=m(c,f))return e}return null}}function p(a){if(!h(a.stack)&&a.stack){for(var b,f,g=/^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,i=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i,j=/^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,l=a.stack.split("\n"),m=[],o=/^(.*) is undefined$/.exec(a.message),p=0,q=l.length;q>p;++p){if(b=g.exec(l[p])){var r=b[2]&&-1!==b[2].indexOf("native");f={url:r?null:b[2],func:b[1]||k,args:r?[b[2]]:[],line:b[3]?+b[3]:null,column:b[4]?+b[4]:null}}else if(b=j.exec(l[p]))f={url:b[2],func:b[1]||k,args:[],line:+b[3],column:b[4]?+b[4]:null};else{if(!(b=i.exec(l[p])))continue;f={url:b[3],func:b[1]||k,args:b[2]?b[2].split(","):[],line:b[4]?+b[4]:null,column:b[5]?+b[5]:null}}!f.func&&f.line&&(f.func=c(f.url,f.line)),f.line&&(f.context=e(f.url,f.line)),m.push(f)}return m.length?(m[0].line&&!m[0].column&&o?m[0].column=n(o[1],m[0].url,m[0].line):m[0].column||h(a.columnNumber)||(m[0].column=a.columnNumber+1),{name:a.name,message:a.message,url:d(),stack:m}):null}}function q(a){var b=a.stacktrace;if(!h(a.stacktrace)&&a.stacktrace){for(var f,g=/ line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i,i=/ line (\d+), column (\d+)\s*(?:in (?:]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i,j=b.split("\n"),k=[],l=0;j.length>l;l+=2){var m=null;if((f=g.exec(j[l]))?m={url:f[2],line:+f[1],column:null,func:f[3],args:[]}:(f=i.exec(j[l]))&&(m={url:f[6],line:+f[1],column:+f[2],func:f[3]||f[4],args:f[5]?f[5].split(","):[]}),m){if(!m.func&&m.line&&(m.func=c(m.url,m.line)),m.line)try{m.context=e(m.url,m.line)}catch(n){}m.context||(m.context=[j[l+1]]),k.push(m)}}return k.length?{name:a.name,message:a.message,url:d(),stack:k}:null}}function r(a){var g=a.message.split("\n");if(4>g.length)return null;var h,i=/^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,j=/^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,k=/^\s*Line (\d+) of function script\s*$/i,n=[],o=document.getElementsByTagName("script"),p=[];for(var q in o)f(o,q)&&!o[q].src&&p.push(o[q]);for(var r=2;g.length>r;r+=2){var s=null;if(h=i.exec(g[r]))s={url:h[2],func:h[3],args:[],line:+h[1],column:null};else if(h=j.exec(g[r])){s={url:h[3],func:h[4],args:[],line:+h[1],column:null};var t=+h[1],u=p[h[2]-1];if(u){var v=b(s.url);if(v){v=v.join("\n");var w=v.indexOf(u.innerText);w>=0&&(s.line=t+v.substring(0,w).split("\n").length)}}}else if(h=k.exec(g[r])){var x=window.location.href.replace(/#.*$/,""),y=new RegExp(l(g[r+1])),z=m(y,[x]);s={url:x,func:"",args:[],line:z?z.line:h[1],column:null}}if(s){s.func||(s.func=c(s.url,s.line));var A=e(s.url,s.line),B=A?A[Math.floor(A.length/2)]:null;s.context=A&&B.replace(/^\s*/,"")===g[r+1].replace(/^\s*/,"")?A:[g[r+1]],n.push(s)}}return n.length?{name:a.name,message:g[0],url:d(),stack:n}:null}function s(a,b,d,f){var g={url:b,line:d};if(g.url&&g.line){a.incomplete=!1,g.func||(g.func=c(g.url,g.line)),g.context||(g.context=e(g.url,g.line));var h=/ '([^']+)' /.exec(f);if(h&&(g.column=n(h[1],g.url,g.line)),a.stack.length>0&&a.stack[0].url===g.url){if(a.stack[0].line===g.line)return!1;if(!a.stack[0].line&&a.stack[0].func===g.func)return a.stack[0].line=g.line,a.stack[0].context=g.context,!1}return a.stack.unshift(g),a.partial=!0,!0}return a.incomplete=!0,!1}function t(a,b){for(var e,f,g,h=/function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,j=[],l={},m=!1,p=t.caller;p&&!m;p=p.caller)if(p!==u&&p!==i.report){if(f={url:null,func:k,line:null,column:null},p.name?f.func=p.name:(e=h.exec(p.toString()))&&(f.func=e[1]),"undefined"==typeof f.func)try{f.func=e.input.substring(0,e.input.indexOf("{"))}catch(q){}if(g=o(p)){f.url=g.url,f.line=g.line,f.func===k&&(f.func=c(f.url,f.line));var r=/ '([^']+)' /.exec(a.message||a.description);r&&(f.column=n(r[1],g.url,g.line))}l[""+p]?m=!0:l[""+p]=!0,j.push(f)}b&&j.splice(0,b);var v={name:a.name,message:a.message,url:d(),stack:j};return s(v,a.sourceURL||a.fileName,a.line||a.lineNumber,a.message||a.description),v}function u(a,b){var c=null;b=null==b?0:+b;try{if(c=q(a))return c}catch(e){if(i.debug)throw e}try{if(c=p(a))return c}catch(e){if(i.debug)throw e}try{if(c=r(a))return c}catch(e){if(i.debug)throw e}try{if(c=t(a,b+1))return c}catch(e){if(i.debug)throw e}return{name:a.name,message:a.message,url:d()}}var v={};return u.augmentStackTraceWithInitialElement=s,u.computeStackTraceFromStackProp=p,u.guessFunctionName=c,u.gatherContext=e,u}(),b.exports=i},{4:4}]},{},[3])(3)}); +//# sourceMappingURL=raven.min.js.map \ No newline at end of file From 001fe515cfeb0e4853c1f053d97b579880bde90d Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 18 Apr 2016 12:37:50 -0400 Subject: [PATCH 327/573] Make unminified Raven available. Use the correct sentry dsn. --- app/assets/javascripts/application.js.coffee | 5 +- vendor/assets/javascripts/raven.js | 2435 ++++++++++++++++++ vendor/assets/javascripts/raven.min.js | 3 - 3 files changed, 2438 insertions(+), 5 deletions(-) create mode 100644 vendor/assets/javascripts/raven.js delete mode 100644 vendor/assets/javascripts/raven.min.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index ab2432e138..a98721e56b 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -55,6 +55,7 @@ #= require_tree . #= require fuzzaldrin-plus #= require cropper +#= require raven window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() @@ -295,6 +296,6 @@ $ -> checkInitialSidebarSize() new Aside() - + if gon.sentry_dsn? - Raven.config('your public dsn').install() \ No newline at end of file + Raven.config(gon.sentry_dsn).install() \ No newline at end of file diff --git a/vendor/assets/javascripts/raven.js b/vendor/assets/javascripts/raven.js new file mode 100644 index 0000000000..d99c6f1c2c --- /dev/null +++ b/vendor/assets/javascripts/raven.js @@ -0,0 +1,2435 @@ +/*! Raven.js 2.3.0 (b09d766) | github.com/getsentry/raven-js */ + +/* + * Includes TraceKit + * https://github.com/getsentry/TraceKit + * + * Copyright 2016 Matt Robenolt and other contributors + * Released under the BSD license + * https://github.com/getsentry/raven-js/blob/master/LICENSE + * + */ + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 300) { + isMinified = true; + break; + } + } + + if (isMinified) { + // The source is minified and we don't know which column. Fuck it. + if (isUndefined(frame.column)) return; + + // If the source is minified and has a frame column + // we take a chunk of the offending line to hopefully shed some light + return [ + [], // no pre_context + context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column + [] // no post_context + ]; + } + + return [ + context.slice(0, pivot), // pre_context + context[pivot], // context_line + context.slice(pivot + 1) // post_context + ]; + }, + + _processException: function(type, message, fileurl, lineno, frames, options) { + var stacktrace, fullMessage; + + if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return; + + message += ''; + message = truncate(message, this._globalOptions.maxMessageLength); + + fullMessage = (type ? type + ': ' : '') + message; + fullMessage = truncate(fullMessage, this._globalOptions.maxMessageLength); + + if (frames && frames.length) { + fileurl = frames[0].filename || fileurl; + // Sentry expects frames oldest to newest + // and JS sends them as newest to oldest + frames.reverse(); + stacktrace = {frames: frames}; + } else if (fileurl) { + stacktrace = { + frames: [{ + filename: fileurl, + lineno: lineno, + in_app: true + }] + }; + } + + if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) return; + if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) return; + + var data = objectMerge({ + // sentry.interfaces.Exception + exception: { + values: [{ + type: type, + value: message, + stacktrace: stacktrace + }] + }, + culprit: fileurl, + message: fullMessage + }, options); + + // Fire away! + this._send(data); + }, + + _trimPacket: function(data) { + // For now, we only want to truncate the two different messages + // but this could/should be expanded to just trim everything + var max = this._globalOptions.maxMessageLength; + data.message = truncate(data.message, max); + if (data.exception) { + var exception = data.exception.values[0]; + exception.value = truncate(exception.value, max); + } + + return data; + }, + + _getHttpData: function() { + if (!this._hasDocument || !document.location || !document.location.href) { + return; + } + + var httpData = { + headers: { + 'User-Agent': navigator.userAgent + } + }; + + httpData.url = document.location.href; + + if (document.referrer) { + httpData.headers.Referer = document.referrer; + } + + return httpData; + }, + + + _send: function(data) { + var self = this; + + var globalOptions = this._globalOptions; + + var baseData = { + project: this._globalProject, + logger: globalOptions.logger, + platform: 'javascript' + }, httpData = this._getHttpData(); + + if (httpData) { + baseData.request = httpData; + } + + data = objectMerge(baseData, data); + + // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge + data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags); + data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra); + + // Send along our own collected metadata with extra + data.extra['session:duration'] = now() - this._startTime; + + // If there are no tags/extra, strip the key from the payload alltogther. + if (isEmptyObject(data.tags)) delete data.tags; + + if (this._globalContext.user) { + // sentry.interfaces.User + data.user = this._globalContext.user; + } + + // Include the release if it's defined in globalOptions + if (globalOptions.release) data.release = globalOptions.release; + + // Include server_name if it's defined in globalOptions + if (globalOptions.serverName) data.server_name = globalOptions.serverName; + + if (isFunction(globalOptions.dataCallback)) { + data = globalOptions.dataCallback(data) || data; + } + + // Why?????????? + if (!data || isEmptyObject(data)) { + return; + } + + // Check if the request should be filtered or not + if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { + return; + } + + // Send along an event_id if not explicitly passed. + // This event_id can be used to reference the error within Sentry itself. + // Set lastEventId after we know the error should actually be sent + this._lastEventId = data.event_id || (data.event_id = uuid4()); + + // Try and clean up the packet before sending by truncating long values + data = this._trimPacket(data); + + this._logDebug('debug', 'Raven about to send:', data); + + if (!this.isSetup()) return; + + var auth = { + sentry_version: '7', + sentry_client: 'raven-js/' + this.VERSION, + sentry_key: this._globalKey + }; + if (this._globalSecret) { + auth.sentry_secret = this._globalSecret; + } + + var url = this._globalEndpoint; + (globalOptions.transport || this._makeRequest).call(this, { + url: url, + auth: auth, + data: data, + options: globalOptions, + onSuccess: function success() { + self._triggerEvent('success', { + data: data, + src: url + }); + }, + onError: function failure() { + self._triggerEvent('failure', { + data: data, + src: url + }); + } + }); + }, + + _makeImageRequest: function(opts) { + // Tack on sentry_data to auth options, which get urlencoded + opts.auth.sentry_data = JSON.stringify(opts.data); + + var img = this._newImage(), + src = opts.url + '?' + urlencode(opts.auth), + crossOrigin = opts.options.crossOrigin; + + if (crossOrigin || crossOrigin === '') { + img.crossOrigin = crossOrigin; + } + img.onload = opts.onSuccess; + img.onerror = img.onabort = opts.onError; + img.src = src; + }, + + _makeXhrRequest: function(opts) { + var request; + + var url = opts.url; + function handler() { + if (request.status === 200) { + if (opts.onSuccess) { + opts.onSuccess(); + } + } else if (opts.onError) { + opts.onError(); + } + } + + request = new XMLHttpRequest(); + if ('withCredentials' in request) { + request.onreadystatechange = function () { + if (request.readyState !== 4) { + return; + } + handler(); + }; + } else { + request = new XDomainRequest(); + // xdomainrequest cannot go http -> https (or vice versa), + // so always use protocol relative + url = url.replace(/^https?:/, ''); + + // onreadystatechange not supported by XDomainRequest + request.onload = handler; + } + + // NOTE: auth is intentionally sent as part of query string (NOT as custom + // HTTP header) so as to avoid preflight CORS requests + request.open('POST', url + '?' + urlencode(opts.auth)); + request.send(JSON.stringify(opts.data)); + }, + + _makeRequest: function(opts) { + var hasCORS = + 'withCredentials' in new XMLHttpRequest() || + typeof XDomainRequest !== 'undefined'; + + return (hasCORS ? this._makeXhrRequest : this._makeImageRequest)(opts); + }, + + // Note: this is shitty, but I can't figure out how to get + // sinon to stub document.createElement without breaking everything + // so this wrapper is just so I can stub it for tests. + _newImage: function() { + return document.createElement('img'); + }, + + _logDebug: function(level) { + if (this._originalConsoleMethods[level] && this.debug) { + // In IE<10 console methods do not have their own 'apply' method + Function.prototype.apply.call( + this._originalConsoleMethods[level], + this._originalConsole, + [].slice.call(arguments, 1) + ); + } + }, + + _mergeContext: function(key, context) { + if (isUndefined(context)) { + delete this._globalContext[key]; + } else { + this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context); + } + } +}; + +// Deprecations +Raven.prototype.setUser = Raven.prototype.setUserContext; +Raven.prototype.setReleaseContext = Raven.prototype.setRelease; + +module.exports = Raven; + +},{"1":1,"4":4,"5":5}],3:[function(_dereq_,module,exports){ +/** + * Enforces a single instance of the Raven client, and the + * main entry point for Raven. If you are a consumer of the + * Raven library, you SHOULD load this file (vs raven.js). + **/ + +'use strict'; + +var RavenConstructor = _dereq_(2); + +var _Raven = window.Raven; + +var Raven = new RavenConstructor(); + +/* + * Allow multiple versions of Raven to be installed. + * Strip Raven from the global context and returns the instance. + * + * @return {Raven} + */ +Raven.noConflict = function () { + window.Raven = _Raven; + return Raven; +}; + +Raven.afterLoad(); + +module.exports = Raven; + +},{"2":2}],4:[function(_dereq_,module,exports){ +'use strict'; + +var objectPrototype = Object.prototype; + +function isUndefined(what) { + return what === void 0; +} + +function isFunction(what) { + return typeof what === 'function'; +} + +function isString(what) { + return objectPrototype.toString.call(what) === '[object String]'; +} + +function isObject(what) { + return typeof what === 'object' && what !== null; +} + +function isEmptyObject(what) { + for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars + return true; +} + +// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560 +// with some tiny modifications +function isError(what) { + var toString = objectPrototype.toString.call(what); + return isObject(what) && + toString === '[object Error]' || + toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions + what instanceof Error; +} + +function each(obj, callback) { + var i, j; + + if (isUndefined(obj.length)) { + for (i in obj) { + if (hasKey(obj, i)) { + callback.call(null, i, obj[i]); + } + } + } else { + j = obj.length; + if (j) { + for (i = 0; i < j; i++) { + callback.call(null, i, obj[i]); + } + } + } +} + +function objectMerge(obj1, obj2) { + if (!obj2) { + return obj1; + } + each(obj2, function(key, value){ + obj1[key] = value; + }); + return obj1; +} + +function truncate(str, max) { + return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; +} + +/** + * hasKey, a better form of hasOwnProperty + * Example: hasKey(MainHostObject, property) === true/false + * + * @param {Object} host object to check property + * @param {string} key to check + */ +function hasKey(object, key) { + return objectPrototype.hasOwnProperty.call(object, key); +} + +function joinRegExp(patterns) { + // Combine an array of regular expressions and strings into one large regexp + // Be mad. + var sources = [], + i = 0, len = patterns.length, + pattern; + + for (; i < len; i++) { + pattern = patterns[i]; + if (isString(pattern)) { + // If it's a string, we need to escape it + // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1')); + } else if (pattern && pattern.source) { + // If it's a regexp already, we want to extract the source + sources.push(pattern.source); + } + // Intentionally skip other cases + } + return new RegExp(sources.join('|'), 'i'); +} + +function urlencode(o) { + var pairs = []; + each(o, function(key, value) { + pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + }); + return pairs.join('&'); +} + +function uuid4() { + var crypto = window.crypto || window.msCrypto; + + if (!isUndefined(crypto) && crypto.getRandomValues) { + // Use window.crypto API if available + var arr = new Uint16Array(8); + crypto.getRandomValues(arr); + + // set 4 in byte 7 + arr[3] = arr[3] & 0xFFF | 0x4000; + // set 2 most significant bits of byte 9 to '10' + arr[4] = arr[4] & 0x3FFF | 0x8000; + + var pad = function(num) { + var v = num.toString(16); + while (v.length < 4) { + v = '0' + v; + } + return v; + }; + + return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) + + pad(arr[5]) + pad(arr[6]) + pad(arr[7]); + } else { + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, + v = c === 'x' ? r : r&0x3|0x8; + return v.toString(16); + }); + } +} + +module.exports = { + isUndefined: isUndefined, + isFunction: isFunction, + isString: isString, + isObject: isObject, + isEmptyObject: isEmptyObject, + isError: isError, + each: each, + objectMerge: objectMerge, + truncate: truncate, + hasKey: hasKey, + joinRegExp: joinRegExp, + urlencode: urlencode, + uuid4: uuid4 +}; + +},{}],5:[function(_dereq_,module,exports){ +'use strict'; + +var utils = _dereq_(4); + +var hasKey = utils.hasKey; +var isString = utils.isString; +var isUndefined = utils.isUndefined; + +/* + TraceKit - Cross brower stack traces - github.com/occ/TraceKit + MIT license +*/ + +var TraceKit = { + remoteFetching: false, + collectWindowErrors: true, + // 3 lines before, the offending line, 3 lines after + linesOfContext: 7, + debug: false +}; + +// global reference to slice +var _slice = [].slice; +var UNKNOWN_FUNCTION = '?'; + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types +var ERROR_TYPES_RE = /^(?:Uncaught )?((?:Eval|Internal|Range|Reference|Syntax|Type|URI)Error)\: ?(.*)$/; + +function getLocationHref() { + if (typeof document === 'undefined') + return ''; + + return document.location.href; +} + +/** + * TraceKit.report: cross-browser processing of unhandled exceptions + * + * Syntax: + * TraceKit.report.subscribe(function(stackInfo) { ... }) + * TraceKit.report.unsubscribe(function(stackInfo) { ... }) + * TraceKit.report(exception) + * try { ...code... } catch(ex) { TraceKit.report(ex); } + * + * Supports: + * - Firefox: full stack trace with line numbers, plus column number + * on top frame; column number is not guaranteed + * - Opera: full stack trace with line and column numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * - IE: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * + * In theory, TraceKit should work on all of the following versions: + * - IE5.5+ (only 8.0 tested) + * - Firefox 0.9+ (only 3.5+ tested) + * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require + * Exceptions Have Stacktrace to be enabled in opera:config) + * - Safari 3+ (only 4+ tested) + * - Chrome 1+ (only 5+ tested) + * - Konqueror 3.5+ (untested) + * + * Requires TraceKit.computeStackTrace. + * + * Tries to catch all unhandled exceptions and report them to the + * subscribed handlers. Please note that TraceKit.report will rethrow the + * exception. This is REQUIRED in order to get a useful stack trace in IE. + * If the exception does not reach the top of the browser, you will only + * get a stack trace from the point where TraceKit.report was called. + * + * Handlers receive a stackInfo object as described in the + * TraceKit.computeStackTrace docs. + */ +TraceKit.report = (function reportModuleWrapper() { + var handlers = [], + lastArgs = null, + lastException = null, + lastExceptionStack = null; + + /** + * Add a crash handler. + * @param {Function} handler + */ + function subscribe(handler) { + installGlobalHandler(); + handlers.push(handler); + } + + /** + * Remove a crash handler. + * @param {Function} handler + */ + function unsubscribe(handler) { + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + } + } + } + + /** + * Remove all crash handlers. + */ + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } + + /** + * Dispatch stack information to all handlers. + * @param {Object.} stack + */ + function notifyHandlers(stack, isWindowError) { + var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { + return; + } + for (var i in handlers) { + if (hasKey(handlers, i)) { + try { + handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); + } catch (inner) { + exception = inner; + } + } + } + + if (exception) { + throw exception; + } + } + + var _oldOnerrorHandler, _onErrorHandlerInstalled; + + /** + * Ensures all global unhandled exceptions are recorded. + * Supported by Gecko and IE. + * @param {string} message Error message. + * @param {string} url URL of script that generated the exception. + * @param {(number|string)} lineNo The line number at which the error + * occurred. + * @param {?(number|string)} colNo The column number at which the error + * occurred. + * @param {?Error} ex The actual Error object. + */ + function traceKitWindowOnError(message, url, lineNo, colNo, ex) { + var stack = null; + + if (lastExceptionStack) { + TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); + processLastException(); + } else if (ex) { + // New chrome and blink send along a real error object + // Let's just report that like a normal error. + // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + stack = TraceKit.computeStackTrace(ex); + notifyHandlers(stack, true); + } else { + var location = { + 'url': url, + 'line': lineNo, + 'column': colNo + }; + location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line); + location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line); + + var name = undefined; + var msg = message; // must be new var or will modify original `arguments` + var groups; + if (isString(message)) { + var groups = message.match(ERROR_TYPES_RE); + if (groups) { + name = groups[1]; + msg = groups[2]; + } + } + + stack = { + 'name': name, + 'message': msg, + 'url': getLocationHref(), + 'stack': [location] + }; + notifyHandlers(stack, true); + } + + if (_oldOnerrorHandler) { + return _oldOnerrorHandler.apply(this, arguments); + } + + return false; + } + + function installGlobalHandler () + { + if (_onErrorHandlerInstalled) { + return; + } + _oldOnerrorHandler = window.onerror; + window.onerror = traceKitWindowOnError; + _onErrorHandlerInstalled = true; + } + + function uninstallGlobalHandler () + { + if (!_onErrorHandlerInstalled) { + return; + } + window.onerror = _oldOnerrorHandler; + _onErrorHandlerInstalled = false; + _oldOnerrorHandler = undefined; + } + + function processLastException() { + var _lastExceptionStack = lastExceptionStack, + _lastArgs = lastArgs; + lastArgs = null; + lastExceptionStack = null; + lastException = null; + notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); + } + + /** + * Reports an unhandled Error to TraceKit. + * @param {Error} ex + * @param {?boolean} rethrow If false, do not re-throw the exception. + * Only used for window.onerror to not cause an infinite loop of + * rethrowing. + */ + function report(ex, rethrow) { + var args = _slice.call(arguments, 1); + if (lastExceptionStack) { + if (lastException === ex) { + return; // already caught by an inner catch block, ignore + } else { + processLastException(); + } + } + + var stack = TraceKit.computeStackTrace(ex); + lastExceptionStack = stack; + lastException = ex; + lastArgs = args; + + // If the stack trace is incomplete, wait for 2 seconds for + // slow slow IE to see if onerror occurs or not before reporting + // this exception; otherwise, we will end up with an incomplete + // stack trace + window.setTimeout(function () { + if (lastException === ex) { + processLastException(); + } + }, (stack.incomplete ? 2000 : 0)); + + if (rethrow !== false) { + throw ex; // re-throw to propagate to the top level (and cause window.onerror) + } + } + + report.subscribe = subscribe; + report.unsubscribe = unsubscribe; + report.uninstall = unsubscribeAll; + return report; +}()); + +/** + * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript + * + * Syntax: + * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) + * Returns: + * s.name - exception name + * s.message - exception message + * s.stack[i].url - JavaScript or HTML file URL + * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work) + * s.stack[i].args - arguments passed to the function, if known + * s.stack[i].line - line number, if known + * s.stack[i].column - column number, if known + * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line# + * + * Supports: + * - Firefox: full stack trace with line numbers and unreliable column + * number on top frame + * - Opera 10: full stack trace with line and column numbers + * - Opera 9-: full stack trace with line numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the topmost stacktrace element + * only + * - IE: no line numbers whatsoever + * + * Tries to guess names of anonymous functions by looking for assignments + * in the source code. In IE and Safari, we have to guess source file names + * by searching for function bodies inside all page scripts. This will not + * work for scripts that are loaded cross-domain. + * Here be dragons: some function names may be guessed incorrectly, and + * duplicate functions may be mismatched. + * + * TraceKit.computeStackTrace should only be used for tracing purposes. + * Logging of unhandled exceptions should be done with TraceKit.report, + * which builds on top of TraceKit.computeStackTrace and provides better + * IE support by utilizing the window.onerror event to retrieve information + * about the top of the stack. + * + * Note: In IE and Safari, no stack trace is recorded on the Error object, + * so computeStackTrace instead walks its *own* chain of callers. + * This means that: + * * in Safari, some methods may be missing from the stack trace; + * * in IE, the topmost function in the stack trace will always be the + * caller of computeStackTrace. + * + * This is okay for tracing (because you are likely to be calling + * computeStackTrace from the function you want to be the topmost element + * of the stack trace anyway), but not okay for logging unhandled + * exceptions (because your catch block will likely be far away from the + * inner function that actually caused the exception). + * + */ +TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + var sourceCache = {}; + + /** + * Attempts to retrieve source code via XMLHttpRequest, which is used + * to look up anonymous function names. + * @param {string} url URL of source code. + * @return {string} Source contents. + */ + function loadSource(url) { + if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. + return ''; + } + try { + var getXHR = function() { + try { + return new window.XMLHttpRequest(); + } catch (e) { + // explicitly bubble up the exception if not found + return new window.ActiveXObject('Microsoft.XMLHTTP'); + } + }; + + var request = getXHR(); + request.open('GET', url, false); + request.send(''); + return request.responseText; + } catch (e) { + return ''; + } + } + + /** + * Retrieves source code from the source code cache. + * @param {string} url URL of source code. + * @return {Array.} Source contents. + */ + function getSource(url) { + if (!isString(url)) return []; + if (!hasKey(sourceCache, url)) { + // URL needs to be able to fetched within the acceptable domain. Otherwise, + // cross-domain errors will be triggered. + var source = ''; + var domain = ''; + try { domain = document.domain; } catch (e) {} + if (url.indexOf(domain) !== -1) { + source = loadSource(url); + } + sourceCache[url] = source ? source.split('\n') : []; + } + + return sourceCache[url]; + } + + /** + * Tries to use an externally loaded copy of source code to determine + * the name of a function by looking at the name of the variable it was + * assigned to, if any. + * @param {string} url URL of source code. + * @param {(string|number)} lineNo Line number in source code. + * @return {string} The function name, if discoverable. + */ + function guessFunctionName(url, lineNo) { + var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/, + reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/, + line = '', + maxLines = 10, + source = getSource(url), + m; + + if (!source.length) { + return UNKNOWN_FUNCTION; + } + + // Walk backwards from the first line in the function until we find the line which + // matches the pattern above, which is the function definition + for (var i = 0; i < maxLines; ++i) { + line = source[lineNo - i] + line; + + if (!isUndefined(line)) { + if ((m = reGuessFunction.exec(line))) { + return m[1]; + } else if ((m = reFunctionArgNames.exec(line))) { + return m[1]; + } + } + } + + return UNKNOWN_FUNCTION; + } + + /** + * Retrieves the surrounding lines from where an exception occurred. + * @param {string} url URL of source code. + * @param {(string|number)} line Line number in source code to centre + * around for context. + * @return {?Array.} Lines of source code. + */ + function gatherContext(url, line) { + var source = getSource(url); + + if (!source.length) { + return null; + } + + var context = [], + // linesBefore & linesAfter are inclusive with the offending line. + // if linesOfContext is even, there will be one extra line + // *before* the offending line. + linesBefore = Math.floor(TraceKit.linesOfContext / 2), + // Add one extra line if linesOfContext is odd + linesAfter = linesBefore + (TraceKit.linesOfContext % 2), + start = Math.max(0, line - linesBefore - 1), + end = Math.min(source.length, line + linesAfter - 1); + + line -= 1; // convert to 0-based index + + for (var i = start; i < end; ++i) { + if (!isUndefined(source[i])) { + context.push(source[i]); + } + } + + return context.length > 0 ? context : null; + } + + /** + * Escapes special characters, except for whitespace, in a string to be + * used inside a regular expression as a string literal. + * @param {string} text The string. + * @return {string} The escaped string literal. + */ + function escapeRegExp(text) { + return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&'); + } + + /** + * Escapes special characters in a string to be used inside a regular + * expression as a string literal. Also ensures that HTML entities will + * be matched the same as their literal friends. + * @param {string} body The string. + * @return {string} The escaped string. + */ + function escapeCodeAsRegExpForMatchingInsideHTML(body) { + return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); + } + + /** + * Determines where a code fragment occurs in the source code. + * @param {RegExp} re The function definition. + * @param {Array.} urls A list of URLs to search. + * @return {?Object.} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceInUrls(re, urls) { + var source, m; + for (var i = 0, j = urls.length; i < j; ++i) { + // console.log('searching', urls[i]); + if ((source = getSource(urls[i])).length) { + source = source.join('\n'); + if ((m = re.exec(source))) { + // console.log('Found function in ' + urls[i]); + + return { + 'url': urls[i], + 'line': source.substring(0, m.index).split('\n').length, + 'column': m.index - source.lastIndexOf('\n', m.index) - 1 + }; + } + } + } + + // console.log('no match'); + + return null; + } + + /** + * Determines at which column a code fragment occurs on a line of the + * source code. + * @param {string} fragment The code fragment. + * @param {string} url The URL to search. + * @param {(string|number)} line The line number to examine. + * @return {?number} The column number. + */ + function findSourceInLine(fragment, url, line) { + var source = getSource(url), + re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'), + m; + + line -= 1; + + if (source && source.length > line && (m = re.exec(source[line]))) { + return m.index; + } + + return null; + } + + /** + * Determines where a function was defined within the source code. + * @param {(Function|string)} func A function reference or serialized + * function definition. + * @return {?Object.} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceByFunctionBody(func) { + if (typeof document === 'undefined') + return; + + var urls = [window.location.href], + scripts = document.getElementsByTagName('script'), + body, + code = '' + func, + codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + re, + parts, + result; + + for (var i = 0; i < scripts.length; ++i) { + var script = scripts[i]; + if (script.src) { + urls.push(script.src); + } + } + + if (!(parts = codeRE.exec(code))) { + re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+')); + } + + // not sure if this is really necessary, but I don’t have a test + // corpus large enough to confirm that and it was in the original. + else { + var name = parts[1] ? '\\s+' + parts[1] : '', + args = parts[2].split(',').join('\\s*,\\s*'); + + body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+'); + re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}'); + } + + // look for a normal function definition + if ((result = findSourceInUrls(re, urls))) { + return result; + } + + // look for an old-school event handler function + if ((parts = eventRE.exec(code))) { + var event = parts[1]; + body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]); + + // look for a function defined in HTML as an onXXX handler + re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i'); + + if ((result = findSourceInUrls(re, urls[0]))) { + return result; + } + + // look for ??? + re = new RegExp(body); + + if ((result = findSourceInUrls(re, urls))) { + return result; + } + } + + return null; + } + + // Contents of Exception in various browsers. + // + // SAFARI: + // ex.message = Can't find variable: qq + // ex.line = 59 + // ex.sourceId = 580238192 + // ex.sourceURL = http://... + // ex.expressionBeginOffset = 96 + // ex.expressionCaretOffset = 98 + // ex.expressionEndOffset = 98 + // ex.name = ReferenceError + // + // FIREFOX: + // ex.message = qq is not defined + // ex.fileName = http://... + // ex.lineNumber = 59 + // ex.columnNumber = 69 + // ex.stack = ...stack trace... (see the example below) + // ex.name = ReferenceError + // + // CHROME: + // ex.message = qq is not defined + // ex.name = ReferenceError + // ex.type = not_defined + // ex.arguments = ['aa'] + // ex.stack = ...stack trace... + // + // INTERNET EXPLORER: + // ex.message = ... + // ex.name = ReferenceError + // + // OPERA: + // ex.message = ...message... (see the example below) + // ex.name = ReferenceError + // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) + // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' + + /** + * Computes stack trace information from the stack property. + * Chrome and Gecko use this property. + * @param {Error} ex + * @return {?Object.} Stack trace information. + */ + function computeStackTraceFromStackProp(ex) { + if (isUndefined(ex.stack) || !ex.stack) return; + + var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, + gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i, + winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + lines = ex.stack.split('\n'), + stack = [], + parts, + element, + reference = /^(.*) is undefined$/.exec(ex.message); + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = chrome.exec(lines[i]))) { + var isNative = parts[2] && parts[2].indexOf('native') !== -1; + element = { + 'url': !isNative ? parts[2] : null, + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': isNative ? [parts[2]] : [], + 'line': parts[3] ? +parts[3] : null, + 'column': parts[4] ? +parts[4] : null + }; + } else if ( parts = winjs.exec(lines[i]) ) { + element = { + 'url': parts[2], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': [], + 'line': +parts[3], + 'column': parts[4] ? +parts[4] : null + }; + } else if ((parts = gecko.exec(lines[i]))) { + element = { + 'url': parts[3], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': parts[2] ? parts[2].split(',') : [], + 'line': parts[4] ? +parts[4] : null, + 'column': parts[5] ? +parts[5] : null + }; + } else { + continue; + } + + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + + if (element.line) { + element.context = gatherContext(element.url, element.line); + } + + stack.push(element); + } + + if (!stack.length) { + return null; + } + + if (stack[0].line && !stack[0].column && reference) { + stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line); + } else if (!stack[0].column && !isUndefined(ex.columnNumber)) { + // FireFox uses this awesome columnNumber property for its top frame + // Also note, Firefox's column number is 0-based and everything else expects 1-based, + // so adding 1 + stack[0].column = ex.columnNumber + 1; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': getLocationHref(), + 'stack': stack + }; + } + + /** + * Computes stack trace information from the stacktrace property. + * Opera 10 uses this property. + * @param {Error} ex + * @return {?Object.} Stack trace information. + */ + function computeStackTraceFromStacktraceProp(ex) { + // Access and store the stacktrace property before doing ANYTHING + // else to it because Opera is not very good at providing it + // reliably in other circumstances. + var stacktrace = ex.stacktrace; + if (isUndefined(ex.stacktrace) || !ex.stacktrace) return; + + var opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i, + opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i, + lines = stacktrace.split('\n'), + stack = [], + parts; + + for (var line = 0; line < lines.length; line += 2) { + var element = null; + if ((parts = opera10Regex.exec(lines[line]))) { + element = { + 'url': parts[2], + 'line': +parts[1], + 'column': null, + 'func': parts[3], + 'args':[] + }; + } else if ((parts = opera11Regex.exec(lines[line]))) { + element = { + 'url': parts[6], + 'line': +parts[1], + 'column': +parts[2], + 'func': parts[3] || parts[4], + 'args': parts[5] ? parts[5].split(',') : [] + }; + } + + if (element) { + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + if (element.line) { + try { + element.context = gatherContext(element.url, element.line); + } catch (exc) {} + } + + if (!element.context) { + element.context = [lines[line + 1]]; + } + + stack.push(element); + } + } + + if (!stack.length) { + return null; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': getLocationHref(), + 'stack': stack + }; + } + + /** + * NOT TESTED. + * Computes stack trace information from an error message that includes + * the stack trace. + * Opera 9 and earlier use this method if the option to show stack + * traces is turned on in opera:config. + * @param {Error} ex + * @return {?Object.} Stack information. + */ + function computeStackTraceFromOperaMultiLineMessage(ex) { + // Opera includes a stack trace into the exception message. An example is: + // + // Statement on line 3: Undefined variable: undefinedFunc + // Backtrace: + // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz + // undefinedFunc(a); + // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy + // zzz(x, y, z); + // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx + // yyy(a, a, a); + // Line 1 of function script + // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); } + // ... + + var lines = ex.message.split('\n'); + if (lines.length < 4) { + return null; + } + + var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i, + lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i, + lineRE3 = /^\s*Line (\d+) of function script\s*$/i, + stack = [], + scripts = document.getElementsByTagName('script'), + inlineScriptBlocks = [], + parts; + + for (var s in scripts) { + if (hasKey(scripts, s) && !scripts[s].src) { + inlineScriptBlocks.push(scripts[s]); + } + } + + for (var line = 2; line < lines.length; line += 2) { + var item = null; + if ((parts = lineRE1.exec(lines[line]))) { + item = { + 'url': parts[2], + 'func': parts[3], + 'args': [], + 'line': +parts[1], + 'column': null + }; + } else if ((parts = lineRE2.exec(lines[line]))) { + item = { + 'url': parts[3], + 'func': parts[4], + 'args': [], + 'line': +parts[1], + 'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number. + }; + var relativeLine = (+parts[1]); // relative to the start of the

    dNco6Q0_>QmGPtE_p!+f`jMSv#I)G)ynm&= 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�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 Bc4q(p literal 0 HcmV?d00001 diff --git a/doc/workflow/img/cherry_pick_changes_commit_modal.png b/doc/workflow/img/cherry_pick_changes_commit_modal.png new file mode 100644 index 0000000000000000000000000000000000000000..f502f87677a233f96b2fcfc101404cd01f51f83f GIT binary patch literal 312659 zcmeFZ_ghn4(>5HtqKJx7iWDg#2_Vut2ud$f5<-{WL3#%p2ug>5bm@=;LPz=qNEZTx zBB4o>66u{#zMboN@9*|G-e0~y;M)ftNp|*Ldu7(lnwfLXG3=$f0wo0<1qcM9R8oAd z1p-|&1A#6xUcC&Q!TtQ_I`DGQO;$+11{SSx#GMslF^61Y!m$J%6U-o%(0e$N#}@ zK*!m>O=Yb{&sH#%L)Vp%H|8m~t~UQx{K{VS^p$z!Ma74Qxk_c&B|zp3cC@v@@vT2HUQAPn5NXz$BpQkvwBuN33E z$)t7vMR8VpPeIbiqWzaVPGGwKJ!DoW_$2@Pk^HXH<6`OmI`hw?cZmOW;uZBXuK#tS zB#imq|2pyW`7N^S{(CsEgkJjZC*YmX?eFLR*NK#?X6ODs@kZ_cpT>XX@&8NXzp?;< z{y#`V+cfV=G({_~gX7we2w#Op{*m&iPv`f;-uO~?%zwvvQ`-7t2V@vuZ0>k5VcrJis6g2q^fAH0own_*+FSQ*42tEYac zu7%_ZWk!a95c1){%y6ENl0SPf%d7ZFlPWrB$F=|7+xg`1PPEcaO+y^4SZfv6^)i9F zL(YTi%7Wjmp59lgc^42#gpn>OSKFrl-UFKd>~Qo)GVNKKcZaR84pN!PQ%+C)Vi}(% z1Jt<{arof7E0HCp>sH+OxM*d=_{m>(`DP+o+4G;US5eg3w+Yv*#;#c|=rjL&6W#Z2 z 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