From 88d397f473a40236c7a03a7acf924a3640e70d9c Mon Sep 17 00:00:00 2001 From: Johann MacDonagh Date: Sat, 8 Feb 2014 21:47:20 -0500 Subject: [PATCH 001/408] Instruct users to fetch merge request branch Instructing users to create a new branch on the target branch and then pulling creates a few issues. If the target branch has moved on since the source branch diverged from it, then the pull will create an unnecessary merge commit from the target branch to the source branch. If the user has pull.rebase set to "true" or "preserve", then this creates an even stranger history. These instructions will ensure the local branch created for the merge request is exactly what contributing user has pushed. --- .../projects/merge_requests/show/_how_to_merge.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 9540453ce3e..63db4b30968 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -10,11 +10,11 @@ - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path %p %strong Step 1. - Checkout the branch we are going to merge and pull in the code + Fetch the code and create a new branch pointing to it %pre.dark :preserve - git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch} - git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} + git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} + git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD %p %strong Step 2. Merge the branch and push the changes to GitLab From 1072c95180aa31b999088fec4d14ce6765041a15 Mon Sep 17 00:00:00 2001 From: Tomas Srna Date: Tue, 22 Jul 2014 13:29:41 +0200 Subject: [PATCH 002/408] Attachment URL with non-/ relative root The attachment URL was not working with relative_url_root not equal to '/'. I suggest this fix. --- app/uploaders/attachment_uploader.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index b122b6c8658..24fc294909e 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -26,6 +26,10 @@ class AttachmentUploader < CarrierWave::Uploader::Base Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" end + def url + Gitlab.config.gitlab.relative_url_root + '' + super unless super.nil? + end + def file_storage? self.class.storage == CarrierWave::Storage::File end From 100022d3eaf78d6c4adad647cb00b03a1befffb0 Mon Sep 17 00:00:00 2001 From: polamjag Date: Thu, 14 Aug 2014 02:10:18 +0900 Subject: [PATCH 003/408] append .xmlschema to system hook timestamp --- app/services/system_hooks_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 41014f199d5..9ac1cfb3ba5 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -18,7 +18,7 @@ class SystemHooksService def build_event_data(model, event) data = { event_name: build_event_name(model, event), - created_at: model.created_at + created_at: model.created_at.xmlschema } case model From 8ed7391cb03ebf47efeb5ce5379eb667a3f22676 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Fri, 22 Aug 2014 10:40:25 -0500 Subject: [PATCH 004/408] Handle undefined text area values Check to see if a text area's `val` is defined before trying to call `replace()` on it. --- 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 1960479321c..8ce3988383e 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -54,7 +54,7 @@ window.extractLast = (term) -> return split( term ).pop() window.rstrip = (val) -> - return val.replace(/\s+$/, '') + return if val then val.replace(/\s+$/, '') else val # Disable button if text field is empty window.disableButtonIfEmptyField = (field_selector, button_selector) -> From 8d0bc76487b428ad629f9bdd883cf8af794431a6 Mon Sep 17 00:00:00 2001 From: Marco Cyriacks Date: Fri, 12 Sep 2014 19:52:27 +0200 Subject: [PATCH 005/408] Change gitlab/log permissions in installation.md This patch changes default permission of the gitlab/log directory to u+rwX,go-w. This is done to make the directory NOT readable by group and others and to avoid logrotate complaining about it. chmod 755 is not used to avoid setting executable bit on file within the log dir. --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 1175aff9dd9..c4d9668fde4 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -159,7 +159,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Make sure GitLab can write to the log/ and tmp/ directories sudo chown -R git log/ sudo chown -R git tmp/ - sudo chmod -R u+rwX log/ + sudo chmod -R u+rwX,go-w log/ sudo chmod -R u+rwX tmp/ # Create directory for satellites From 430758653ce7e4d32b40648e6263b79c2709bdad Mon Sep 17 00:00:00 2001 From: Jeroen Jacobs Date: Fri, 27 Jun 2014 16:48:30 +0200 Subject: [PATCH 006/408] Adds comments to commits in the API --- CHANGELOG | 1 + app/models/note.rb | 20 ++++++++-- doc/api/commits.md | 63 ++++++++++++++++++++++++++++++ lib/api/commits.rb | 61 +++++++++++++++++++++++++++++ lib/api/entities.rb | 8 ++++ spec/requests/api/commits_spec.rb | 65 +++++++++++++++++++++++++++++++ 6 files changed, 215 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3006ff4049d..8e5b3965480 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 7.4.0 - Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally - Increase unicorn timeout to 60 seconds - Sort search autocomplete projects by stars count so most popular go first + - Adds comments to commits in the API v 7.3.1 - Fix ref parsing in Gitlab::GitAccess diff --git a/app/models/note.rb b/app/models/note.rb index fa5fdea4eb0..d70ebcd8e6d 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -226,7 +226,7 @@ class Note < ActiveRecord::Base end def diff_file_index - line_code.split('_')[0] + line_code.split('_')[0] if line_code end def diff_file_name @@ -242,11 +242,11 @@ class Note < ActiveRecord::Base end def diff_old_line - line_code.split('_')[1].to_i + line_code.split('_')[1].to_i if line_code end def diff_new_line - line_code.split('_')[2].to_i + line_code.split('_')[2].to_i if line_code end def generate_line_code(line) @@ -267,6 +267,20 @@ class Note < ActiveRecord::Base @diff_line end + def diff_line_type + return @diff_line_type if @diff_line_type + + if diff + diff_lines.each do |line| + if generate_line_code(line) == self.line_code + @diff_line_type = line.type + end + end + end + + @diff_line_type + end + def truncated_diff_lines max_number_of_lines = 16 prev_match_line = nil diff --git a/doc/api/commits.md b/doc/api/commits.md index 9475ecbaa67..eb8d6a43592 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -93,3 +93,66 @@ Parameters: } ] ``` + +## Get the comments of a commit + +Get the comments of a commit in a project. + +``` +GET /projects/:id/repository/commits/:sha/comments +``` + +Parameters: + +- `id` (required) - The ID of a project +- `sha` (required) - The name of a repository branch or tag or if not given the default branch + +```json +[ + { + "note": "this code is really nice", + "author": { + "id": 11, + "username": "admin", + "email": "admin@local.host", + "name": "Administrator", + "state": "active", + "created_at": "2014-03-06T08:17:35.000Z" + } + } +] +``` + +## Post comment to commit + +Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required. + +``` +POST /projects/:id/repository/commits/:sha/comments +``` + +Parameters: + +- `id` (required) - The ID of a project +- `sha` (required) - The name of a repository branch or tag or if not given the default branch +- `note` (required) - Text of comment +- `path` (optional) - The file path +- `line` (optional) - The line number +- `line_type` (optional) - The line type (new or old) + +```json +{ + "author": { + "id": 1, + "username": "admin", + "email": "admin@local.host", + "name": "Administrator", + "blocked": false, + "created_at": "2012-04-29T08:46:00Z" + }, + "note": "text1", + "path": "example.rb", + "line": 5, + "line_type": "new" +} +``` diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 4a67313430a..6c5391b98c8 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -50,6 +50,67 @@ module API not_found! "Commit" unless commit commit.diffs end + + # Get a commit's comments + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash + # Examples: + # GET /projects/:id/repository/commits/:sha/comments + get ':id/repository/commits/:sha/comments' do + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! 'Commit' unless commit + notes = Note.where(commit_id: commit.id) + present paginate(notes), with: Entities::CommitNote + end + + # Post comment to commit + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash + # note (required) - Text of comment + # path (optional) - The file path + # line (optional) - The line number + # line_type (optional) - The type of line (new or old) + # Examples: + # POST /projects/:id/repository/commits/:sha/comments + post ':id/repository/commits/:sha/comments' do + required_attributes! [:note] + + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! 'Commit' unless commit + opts = { + note: params[:note], + noteable_type: 'Commit', + commit_id: commit.id + } + + if params[:path] && params[:line] && params[:line_type] + commit.diffs.each do |diff| + next unless diff.new_path == params[:path] + lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) + + lines.each do |line| + next unless line.new_pos == params[:line].to_i && line.type == params[:line_type] + break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos) + end + + break if opts[:line_code] + end + end + + note = ::Notes::CreateService.new(user_project, current_user, opts).execute + + if note.save + present note, with: Entities::CommitNote + else + not_found! + end + end end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ffa3e8a149e..c7b86ed3d76 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -158,6 +158,14 @@ module API expose :author, using: Entities::UserBasic end + class CommitNote < Grape::Entity + expose :note + expose(:path) { |note| note.diff_file_name } + expose(:line) { |note| note.diff_new_line } + expose(:line_type) { |note| note.diff_line_type } + expose :author, using: Entities::UserBasic + end + class Event < Grape::Entity expose :title, :project_id, :action_name expose :target_id, :target_type, :author_id diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 38e0a284c36..a3f58f50913 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -8,6 +8,7 @@ describe API::API, api: true do let!(:project) { create(:project, creator_id: user.id) } let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } + let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } before { project.team << [user, :reporter] } @@ -81,4 +82,68 @@ describe API::API, api: true do end end end + + describe 'GET /projects:id/repository/commits/:sha/comments' do + context 'authorized user' do + it 'should return merge_request comments' do + get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) + response.status.should == 200 + json_response.should be_an Array + json_response.length.should == 1 + json_response.first['note'].should == 'a comment on a commit' + json_response.first['author']['id'].should == user.id + end + + it 'should return a 404 error if merge_request_id not found' do + get api("/projects/#{project.id}/repository/commits/1234ab/comments", user) + response.status.should == 404 + end + end + + context 'unauthorized user' do + it 'should not return the diff of the selected commit' do + get api("/projects/#{project.id}/repository/commits/1234ab/comments") + response.status.should == 401 + end + end + end + + describe 'POST /projects:id/repository/commits/:sha/comments' do + context 'authorized user' do + it 'should return comment' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' + response.status.should == 201 + json_response['note'].should == 'My comment' + json_response['path'].should be_nil + json_response['line'].should be_nil + json_response['line_type'].should be_nil + end + + it 'should return the inline comment' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new' + response.status.should == 201 + json_response['note'].should == 'My comment' + json_response['path'].should == project.repository.commit.diffs.first.new_path + json_response['line'].should == 7 + json_response['line_type'].should == 'new' + end + + it 'should return 400 if note is missing' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) + response.status.should == 400 + end + + it 'should return 404 if note is attached to non existent commit' do + post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' + response.status.should == 404 + end + end + + context 'unauthorized user' do + it 'should not return the diff of the selected commit' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") + response.status.should == 401 + end + end + end end From bc3137ac4961f5f763fe8db2b5bb43bccfa34258 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 29 Sep 2014 21:35:41 +0300 Subject: [PATCH 007/408] Fix milestone link in issue. Closes #174 (gitlab.com). --- app/views/projects/issues/_issue_context.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml index 8c3f0823386..f8f1add2fd4 100644 --- a/app/views/projects/issues/_issue_context.html.haml +++ b/app/views/projects/issues/_issue_context.html.haml @@ -19,6 +19,7 @@ = hidden_field_tag :issue_context = f.submit class: 'btn' - elsif issue.milestone - = link_to issue.milestone.title, project_milestone_path + = link_to project_milestone_path(@project, @issue.milestone) do + = @issue.milestone.title - else None From 9567778f9446a738090f4fa1c6ceea9daa26f7a4 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 29 Sep 2014 16:50:20 +0200 Subject: [PATCH 008/408] Fix version of test seed branches. --- spec/support/test_env.rb | 45 ++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 5f55871dc4a..e6db410fb1c 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -3,6 +3,16 @@ require 'rspec/mocks' module TestEnv extend self + # When developing the seed repository, comment out the branch you will modify. + BRANCH_SHA = { + 'feature' => '0b4bc9a', + 'feature_conflict' => 'bb5206f', + 'fix' => '12d65c8', + 'improve/awesome' => '5937ac0', + 'markdown' => '0ed8c6c', + 'master' => '5937ac0' + } + # Test environment # # See gitlab.yml.example test section for paths @@ -18,13 +28,13 @@ module TestEnv if File.directory?(tmp_test_path) Dir.entries(tmp_test_path).each do |entry| - unless ['.', '..', 'gitlab-shell'].include?(entry) + unless ['.', '..', 'gitlab-shell', factory_repo_name].include?(entry) FileUtils.rm_r(File.join(tmp_test_path, entry)) end end end - FileUtils.mkdir_p(tmp_test_path) + FileUtils.mkdir_p(repos_path) # Setup GitLab shell for test instance setup_gitlab_shell @@ -49,13 +59,32 @@ module TestEnv clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git" unless File.directory?(factory_repo_path) - git_cmd = %W(git clone --bare #{clone_url} #{factory_repo_path}) - system(*git_cmd) + system(*%W(git clone #{clone_url} #{factory_repo_path})) end + + Dir.chdir(factory_repo_path) do + BRANCH_SHA.each do |branch, sha| + # Try to reset without fetching to avoid using the network. + reset = %W(git update-ref refs/heads/#{branch} #{sha}) + unless system(*reset) + if system(*%w(git fetch origin)) + unless system(*reset) + raise 'The fetched test seed '\ + 'does not contain the required revision.' + end + else + raise 'Could not fetch test seed repository.' + end + end + end + end + + # We must copy bare repositories because we will push to them. + system(*%W(git clone --bare #{factory_repo_path} #{factory_repo_path_bare})) end def copy_repo(project) - base_repo_path = File.expand_path(factory_repo_path) + base_repo_path = File.expand_path(factory_repo_path_bare) target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git") FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) @@ -69,7 +98,11 @@ module TestEnv private def factory_repo_path - @factory_repo_path ||= repos_path + "/root/#{factory_repo_name}.git" + @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name) + end + + def factory_repo_path_bare + factory_repo_path.to_s + '_bare' end def factory_repo_name From 6294033024d311efa14a9400eb0b8815c702fc44 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Fri, 26 Sep 2014 16:57:36 +0200 Subject: [PATCH 009/408] Use button type=submit instead of input. --- app/views/admin/groups/index.html.haml | 2 +- app/views/admin/groups/show.html.haml | 2 +- app/views/admin/projects/index.html.haml | 2 +- app/views/devise/sessions/_new_ldap.html.haml | 2 +- app/views/explore/groups/index.html.haml | 2 +- app/views/explore/projects/index.html.haml | 2 +- app/views/groups/members.html.haml | 2 +- app/views/layouts/_search.html.haml | 2 +- app/views/projects/blob/_remove.html.haml | 2 +- app/views/projects/branches/new.html.haml | 2 +- app/views/projects/compare/_form.html.haml | 2 +- app/views/projects/tags/new.html.haml | 2 +- app/views/projects/team_members/import.html.haml | 2 +- app/views/search/show.html.haml | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 9a0d5967927..e83c667767f 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -10,7 +10,7 @@ = form_tag admin_groups_path, method: :get, class: 'form-inline' do .form-group = text_field_tag :name, params[:name], class: "form-control input-mn-300" - = submit_tag "Search", class: "btn submit btn-primary" + = button_tag "Search", class: "btn submit btn-primary" %hr diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index d59d2a23179..bb660e60bd2 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -64,7 +64,7 @@ %div.prepend-top-10 = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr - = submit_tag 'Add users into group', class: "btn btn-create" + = button_tag 'Add users into group', class: "btn btn-create" .panel.panel-default .panel-heading %h3.panel-title diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 5ca6090f8d3..2cd6b12be7f 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -35,7 +35,7 @@ = label %hr = hidden_field_tag :sort, params[:sort] - = submit_tag "Search", class: "btn submit btn-primary" + = button_tag "Search", class: "btn submit btn-primary" = link_to "Reset", admin_projects_path, class: "btn btn-cancel" .col-md-9 diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 6c5a878e904..be3cafab935 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -2,4 +2,4 @@ = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} %br/ - = submit_tag "LDAP Sign in", class: "btn-save btn" + = button_tag "LDAP Sign in", class: "btn-save btn" diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 80ddd5c1bde..b45ba920d1d 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -4,7 +4,7 @@ .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search" .form-group - = submit_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-primary wide" .pull-right .dropdown.inline diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index c8bf78385e8..f797c4e3830 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -4,7 +4,7 @@ .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" .form-group - = submit_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-primary wide" .pull-right .dropdown.inline diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml index ebf407d4ef1..9931fa5c3a6 100644 --- a/app/views/groups/members.html.haml +++ b/app/views/groups/members.html.haml @@ -13,7 +13,7 @@ = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } - = submit_tag 'Search', class: 'btn' + = button_tag 'Search', class: 'btn' - if current_user && current_user.can?(:manage_group, @group) .pull-right diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 5ab82122ad7..2460a6a014d 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -8,7 +8,7 @@ - if @snippet || @snippets = hidden_field_tag :snippets, true = hidden_field_tag :repository_ref, @ref - = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test' + = button_tag 'Go' if ENV['RAILS_ENV'] == 'test' .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } :javascript diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index 93ffd4463b1..bc43f96b15d 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -19,7 +19,7 @@ .form-group .col-sm-2 .col-sm-10 - = submit_tag 'Remove file', class: 'btn btn-remove btn-remove-file' + = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" :javascript diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 3f202f7ea6b..d2e307816bd 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -15,7 +15,7 @@ .col-sm-10 = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control' .form-actions - = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3 + = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' :javascript diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index da6157cf1b6..cb0a3747f7d 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -12,7 +12,7 @@ %span.input-group-addon to = text_field_tag :to, params[:to], class: "form-control"   - = submit_tag "Compare", class: "btn btn-create commits-compare-btn" + = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if compare_to_mr_button? = link_to compare_mr_path, class: 'prepend-left-10 btn' do %strong Make a merge request diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 45ee61caf68..1c80e76ec7e 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -21,7 +21,7 @@ = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control' .light (Optional) Entering a message will create an annotated tag. .form-actions - = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3 + = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel' :javascript diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml index 510b579fe2f..d1f46c61b2e 100644 --- a/app/views/projects/team_members/import.html.haml +++ b/app/views/projects/team_members/import.html.haml @@ -9,6 +9,6 @@ .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .form-actions - = submit_tag 'Import project members', class: "btn btn-create" + = button_tag 'Import project members', class: "btn btn-create" = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel" diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index bae57917a4c..5b4816e4c40 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -6,7 +6,7 @@ .col-sm-6 = search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search" .col-sm-4 - = submit_tag 'Search', class: "btn btn-create" + = button_tag 'Search', class: "btn btn-create" .form-group .col-sm-2 - unless params[:snippets].eql? 'true' From 6bae8c48ef83cf45984930c57282903cbff506ad Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sat, 16 Aug 2014 11:53:44 +0200 Subject: [PATCH 010/408] Update default regex message to match regex. --- lib/gitlab/regex.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 4b8038843b0..c4d0d85b7f5 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -67,8 +67,7 @@ module Gitlab def default_regex_message "can contain only letters, digits, '_', '-' and '.'. " \ - "It must start with letter, digit or '_', optionally preceeded by '.'. " \ - "It must not end in '.git'." + "Cannot start with '-' or end in '.git'" \ end def default_regex From adf04082299a37bc953d93a4d38f9b8c24cc307d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 1 Oct 2014 20:07:28 +0300 Subject: [PATCH 011/408] Fix identation. --- app/views/projects/issues/_issue_context.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml index f8f1add2fd4..648f459dc9e 100644 --- a/app/views/projects/issues/_issue_context.html.haml +++ b/app/views/projects/issues/_issue_context.html.haml @@ -20,6 +20,6 @@ = f.submit class: 'btn' - elsif issue.milestone = link_to project_milestone_path(@project, @issue.milestone) do - = @issue.milestone.title + = @issue.milestone.title - else None From c44764f523cea756f1f2efdc4db954f4f19df440 Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Fri, 3 Oct 2014 10:12:44 +0200 Subject: [PATCH 012/408] Prepare ForkService to support forking projects to given namespaces Remove overload of BaseService.initialize, so initialize gains params, which is used to pass the namespace (like e.g. in TransferService). The namespace is checked for permission to create projects in it. --- CHANGELOG | 1 + app/services/projects/fork_service.rb | 19 +++++--- spec/services/projects/fork_service_spec.rb | 52 +++++++++++++++++++-- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0250b4a23c0..410863d3f99 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 7.4.0 - Do not delete tmp/repositories itself during clean-up, only its contents - Support for backup uploads to remote storage - Prevent notes polling when there are not notes + - Internal ForkService: Prepare support for fork to a given namespace - API: Add support for forking a project via the API (Bernhard Kaindl) - API: filter project issues by milestone (Julien Bianchi) - Fail harder in the backup script diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index a59311bf942..c4f2d08efe9 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -2,11 +2,9 @@ module Projects class ForkService < BaseService include Gitlab::ShellAdapter - def initialize(project, user) - @from_project, @current_user = project, user - end - def execute + @from_project = @project + project_params = { visibility_level: @from_project.visibility_level, description: @from_project.description, @@ -15,8 +13,15 @@ module Projects project = Project.new(project_params) project.name = @from_project.name project.path = @from_project.path - project.namespace = current_user.namespace - project.creator = current_user + project.namespace = @current_user.namespace + if namespace = @params[:namespace] + project.namespace = namespace + end + project.creator = @current_user + unless @current_user.can?(:create_projects, project.namespace) + project.errors.add(:namespace, 'insufficient access rights') + return project + end # If the project cannot save, we do not want to trigger the project destroy # as this can have the side effect of deleting a repo attached to an existing @@ -27,7 +32,7 @@ module Projects #First save the DB entries as they can be rolled back if the repo fork fails project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id) if project.save - project.team << [current_user, :master] + project.team << [@current_user, :master] end #Now fork the repo unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path) diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 0edc3a8e807..5c80345c2b3 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -42,10 +42,54 @@ describe Projects::ForkService do end end - def fork_project(from_project, user, fork_success = true) - context = Projects::ForkService.new(from_project, user) - shell = double("gitlab_shell") - shell.stub(fork_repository: fork_success) + describe :fork_to_namespace do + before do + @group_owner = create(:user) + @developer = create(:user) + @project = create(:project, creator_id: @group_owner.id, + star_count: 777, + description: 'Wow, such a cool project!') + @group = create(:group) + @group.add_user(@group_owner, GroupMember::OWNER) + @group.add_user(@developer, GroupMember::DEVELOPER) + @opts = { namespace: @group } + end + + context 'fork project for group' do + it 'group owner successfully forks project into the group' do + to_project = fork_project(@project, @group_owner, true, @opts) + to_project.owner.should == @group + to_project.namespace.should == @group + to_project.name.should == @project.name + to_project.path.should == @project.path + to_project.description.should == @project.description + to_project.star_count.should be_zero + end + end + + context 'fork project for group when user not owner' do + it 'group developer should fail to fork project into the group' do + to_project = fork_project(@project, @developer, true, @opts) + to_project.errors[:namespace].should == ['insufficient access rights'] + end + end + + context 'project already exists in group' do + it 'should fail due to validation, not transaction failure' do + existing_project = create(:project, name: @project.name, + namespace: @group) + to_project = fork_project(@project, @group_owner, true, @opts) + existing_project.persisted?.should be_true + to_project.errors[:base].should == ['Invalid fork destination'] + to_project.errors[:name].should == ['has already been taken'] + to_project.errors[:path].should == ['has already been taken'] + end + end + end + + def fork_project(from_project, user, fork_success = true, params = {}) + context = Projects::ForkService.new(from_project, user, params) + shell = double('gitlab_shell').stub(fork_repository: fork_success) context.stub(gitlab_shell: shell) context.execute end From 84fbd2935197c545703541e24d453f6e723293bf Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Sat, 4 Oct 2014 11:44:20 +0200 Subject: [PATCH 013/408] transfer_service_spec: cleanup, merge common code, check against nil - replace creation of group2 with the use of group without add_owner(user) - fold TransferService calls into new test function transfer_project - remove currently not used (and not working) gitlab_shell stub (will submit testcase simulating failure in gitlab_shell separately) - add checks against not be_nil (result.should be_false passes even if nil) --- .../projects/transfer_service_spec.rb | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 2508dfc4565..79d0526ff89 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -3,15 +3,12 @@ require 'spec_helper' describe Projects::TransferService do let(:user) { create(:user) } let(:group) { create(:group) } - let(:group2) { create(:group) } let(:project) { create(:project, namespace: user.namespace) } context 'namespace -> namespace' do before do group.add_owner(user) - @service = Projects::TransferService.new(project, user, namespace_id: group.id) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, namespace_id: group.id) end it { @result.should be_true } @@ -20,24 +17,25 @@ describe Projects::TransferService do context 'namespace -> no namespace' do before do - group.add_owner(user) - @service = Projects::TransferService.new(project, user, namespace_id: nil) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, namespace_id: nil) end + it { @result.should_not be_nil } # { result.should be_false } passes on nil it { @result.should be_false } it { project.namespace.should == user.namespace } end context 'namespace -> not allowed namespace' do before do - @service = Projects::TransferService.new(project, user, namespace_id: group2.id) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, namespace_id: group.id) end + it { @result.should_not be_nil } # { result.should be_false } passes on nil it { @result.should be_false } it { project.namespace.should == user.namespace } end + + def transfer_project(project, user, params) + Projects::TransferService.new(project, user, params).execute + end end From 11848febd1170042523907652a36503c57e9fac2 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Sun, 5 Oct 2014 17:03:15 +0400 Subject: [PATCH 014/408] Add issueable actor --- app/models/concerns/issuable.rb | 3 ++- app/models/user.rb | 8 ++++++++ app/services/issues/base_service.rb | 2 +- app/services/merge_requests/base_merge_service.rb | 3 ++- app/services/merge_requests/base_service.rb | 3 ++- doc/web_hooks/web_hooks.md | 10 ++++++++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 553087946d6..f49708fd6eb 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -131,9 +131,10 @@ module Issuable users.concat(mentions.reduce([], :|)).uniq end - def to_hook_data + def to_hook_data(user) { object_kind: self.class.name.underscore, + user: user.hook_attrs, object_attributes: hook_attrs } end diff --git a/app/models/user.rb b/app/models/user.rb index c90f2462426..45e4d71808f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -488,6 +488,14 @@ class User < ActiveRecord::Base end end + def hook_attrs + { + name: name, + username: username, + avatar_url: avatar_url + } + end + def ensure_namespace_correct # Ensure user has namespace self.create_namespace!(path: self.username, name: self.username) unless self.namespace diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 71b9ffc3489..2deffe3927a 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -8,7 +8,7 @@ module Issues end def execute_hooks(issue, action = 'open') - issue_data = issue.to_hook_data + issue_data = issue.to_hook_data(current_user) issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) issue_data[:object_attributes].merge!(url: issue_url, action: action) issue.project.execute_hooks(issue_data, :issue_hooks) diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb index 9bc50d3d16c..700a21ca011 100644 --- a/app/services/merge_requests/base_merge_service.rb +++ b/app/services/merge_requests/base_merge_service.rb @@ -13,7 +13,8 @@ module MergeRequests def execute_project_hooks(merge_request) if merge_request.project - merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) + hook_data = merge_request.to_hook_data(current_user) + merge_request.project.execute_hooks(hook_data, :merge_request_hooks) end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 2907f3587da..9f57a718eac 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -13,7 +13,8 @@ module MergeRequests def execute_hooks(merge_request) if merge_request.project - merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) + hook_data = merge_request.to_hook_data(current_user) + merge_request.project.execute_hooks(hook_data, :merge_request_hooks) end end diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 31791da8074..f19517c0f18 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -63,6 +63,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re ```json { "object_kind": "issue", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, "object_attributes": { "id": 301, "title": "New API: create/update/delete file", @@ -92,6 +97,11 @@ Triggered when a new merge request is created or an existing merge request was u ```json { "object_kind": "merge_request", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, "object_attributes": { "id": 99, "target_branch": "master", From 9bebacd69260b7106bcee42ad7317c7f9c5c5525 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sat, 4 Oct 2014 17:09:06 +0200 Subject: [PATCH 015/408] Dry admin logs. --- app/views/admin/logs/show.html.haml | 87 ++++++++--------------------- lib/gitlab/app_logger.rb | 4 +- lib/gitlab/git_logger.rb | 4 +- lib/gitlab/logger.rb | 4 ++ lib/gitlab/production_logger.rb | 7 +++ lib/gitlab/sidekiq_logger.rb | 7 +++ 6 files changed, 44 insertions(+), 69 deletions(-) create mode 100644 lib/gitlab/production_logger.rb create mode 100644 lib/gitlab/sidekiq_logger.rb diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index b3f8f012f00..384c6ee9af5 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,68 +1,25 @@ +- loggers = [Gitlab::GitLogger, Gitlab::AppLogger, + Gitlab::ProductionLogger, Gitlab::SidekiqLogger] %ul.nav.nav-tabs.log-tabs - %li.active - = link_to "githost.log", "#githost", 'data-toggle' => 'tab' - %li - = link_to "application.log", "#application", 'data-toggle' => 'tab' - %li - = link_to "production.log", "#production", 'data-toggle' => 'tab' - %li - = link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab' - + - loggers.each do |klass| + %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } + = link_to klass::file_name, "##{klass::file_name_noext}", + 'data-toggle' => 'tab' %p.light To prevent performance issues admin logs output the last 2000 lines .tab-content - .tab-pane.active#githost - .file-holder#README - .file-title - %i.fa.fa-file - githost.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::GitLogger.read_latest.each do |line| - %li - %p= line - .tab-pane#application - .file-holder#README - .file-title - %i.fa.fa-file - application.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::AppLogger.read_latest.each do |line| - %li - %p= line - .tab-pane#production - .file-holder#README - .file-title - %i.fa.fa-file - production.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::Logger.read_latest_for('production.log').each do |line| - %li - %p= line - .tab-pane#sidekiq - .file-holder#README - .file-title - %i.fa.fa-file - sidekiq.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::Logger.read_latest_for('sidekiq.log').each do |line| - %li - %p= line + - loggers.each do |klass| + .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''), + id: klass::file_name_noext } + .file-holder#README + .file-title + %i.fa.fa-file + = klass::file_name + .pull-right + = link_to '#', class: 'log-bottom' do + %i.fa.fa-arrow-down + Scroll down + .file-content.logs + %ol + - klass.read_latest.each do |line| + %li + %p= line diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb index 8e4717b46e6..dddcb2538f9 100644 --- a/lib/gitlab/app_logger.rb +++ b/lib/gitlab/app_logger.rb @@ -1,7 +1,7 @@ module Gitlab class AppLogger < Gitlab::Logger - def self.file_name - 'application.log' + def self.file_name_noext + 'application' end def format_message(severity, timestamp, progname, msg) diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb index fbfed205a0f..9e02ccc0f44 100644 --- a/lib/gitlab/git_logger.rb +++ b/lib/gitlab/git_logger.rb @@ -1,7 +1,7 @@ module Gitlab class GitLogger < Gitlab::Logger - def self.file_name - 'githost.log' + def self.file_name_noext + 'githost' end def format_message(severity, timestamp, progname, msg) diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index 8a73ec5038a..59b21149a9a 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -1,5 +1,9 @@ module Gitlab class Logger < ::Logger + def self.file_name + file_name_noext + '.log' + end + def self.error(message) build.error(message) end diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/production_logger.rb new file mode 100644 index 00000000000..89ce7144b1b --- /dev/null +++ b/lib/gitlab/production_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class ProductionLogger < Gitlab::Logger + def self.file_name_noext + 'production' + end + end +end diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb new file mode 100644 index 00000000000..c1dab87a432 --- /dev/null +++ b/lib/gitlab/sidekiq_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class SidekiqLogger < Gitlab::Logger + def self.file_name_noext + 'sidekiq' + end + end +end From ca840e8769fa75f786bf01e1ac839a81bd8ac32b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Mon, 6 Oct 2014 12:28:10 +0000 Subject: [PATCH 016/408] fonts: Added "DejaVu Sans Mono" and "Ubuntu Mono" Everything is better than "Courier New" ... --- app/assets/stylesheets/main/fonts.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss index d90274a0db9..f945aaca848 100644 --- a/app/assets/stylesheets/main/fonts.scss +++ b/app/assets/stylesheets/main/fonts.scss @@ -1,3 +1,3 @@ /** Typo **/ -$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; +$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; From 2aa2d268fd83e318263a2b93aae4f0db3210ef38 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Mon, 6 Oct 2014 09:30:25 -0700 Subject: [PATCH 017/408] support latest firefox esr release --- doc/install/requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 49edf36f574..8acd12ddfe0 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -90,7 +90,7 @@ On a very active server (10.000 active users) the Sidekiq process can use 1GB+ o ## Supported webbrowsers - Chrome (Latest stable version) -- Firefox (Latest released version) +- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Safari 7+ (known problem: required fields in html5 do not work) - Opera (Latest released version) - IE 10+ From 0084b8a7763b04f432579e5a19ce0c6a06136b03 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Mon, 6 Oct 2014 21:42:07 -0500 Subject: [PATCH 018/408] Display renamed files in diff views Show both the old and new filenames when viewing the diff for a renamed file. --- app/views/projects/diffs/_file.html.haml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index c415ae2ddc8..bf7770ceedf 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -10,7 +10,10 @@ - if @commit.parent_ids.present? = view_file_btn(@commit.parent_id, diff_file, project) - else - %span= diff_file.new_path + - if diff_file.renamed_file + %span= "#{diff_file.old_path} renamed to #{diff_file.new_path}" + - else + %span= diff_file.new_path - if diff_file.mode_changed? %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" From 68b5ac7f185b9b30cd862eacf806db726d8ce6e4 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Tue, 7 Oct 2014 14:55:15 -0500 Subject: [PATCH 019/408] Add option to keep repo on project delete Update the project API controller to use `Projects::DestroyService` instead of calling `Project#destroy` directly. Also add an optional parameter, `:keep_repo`, that allows a project to be deleted without deleting the repository, wiki, and satellite from disk. --- app/controllers/projects_controller.rb | 3 ++- app/services/projects/destroy_service.rb | 13 ++++++++----- lib/api/projects.rb | 8 +++++++- spec/features/projects_spec.rb | 7 ++++++- spec/requests/api/projects_spec.rb | 15 +++++++++++++++ 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b3380a6ff23..c881c921ce9 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -100,7 +100,8 @@ class ProjectsController < ApplicationController def destroy return access_denied! unless can?(current_user, :remove_project, project) - ::Projects::DestroyService.new(@project, current_user, {}).execute + ::Projects::DestroyService.new(@project, current_user, + keep_repo: params[:keep_repo]).execute respond_to do |format| format.html do diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 7e1d753b021..7c7892a0b14 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -6,7 +6,10 @@ module Projects project.team.truncate project.repository.expire_cache unless project.empty_repo? - if project.destroy + result = project.destroy + return false unless result + + unless params[:keep_repo] GitlabShellWorker.perform_async( :remove_repository, project.path_with_namespace @@ -18,11 +21,11 @@ module Projects ) project.satellite.destroy - - log_info("Project \"#{project.name}\" was removed") - system_hook_service.execute_hooks_for(project, :destroy) - true end + + log_info("Project \"#{project.name}\" was removed") + system_hook_service.execute_hooks_for(project, :destroy) + result end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 7f7d2f8e9a8..e70548d1e85 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -174,11 +174,17 @@ module API # # Parameters: # id (required) - The ID of a project + # keep_repo (optional) - If true, then delete the project from the + # database but keep the repo, wiki, and satellite on disk. # Example Request: # DELETE /projects/:id delete ":id" do authorize! :remove_project, user_project - user_project.destroy + ::Projects::DestroyService.new( + user_project, + current_user, + keep_repo: params[:keep_repo] + ).execute end # Mark this project as forked from another diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 524c4d5fa21..369db56d493 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -10,7 +10,12 @@ describe "Projects", feature: true do visit edit_project_path(@project) end - it "should be correct path" do + it 'should delete the project from the database and disk' do + expect(GitlabShellWorker).to( + receive(:perform_async).with(:remove_repository, + /#{@project.path_with_namespace}/) + ).twice + expect { click_link "Remove project" }.to change {Project.count}.by(-1) end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index aa1437c71aa..6de37cff0aa 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -632,10 +632,25 @@ describe API::API, api: true do describe "DELETE /projects/:id" do context "when authenticated as user" do it "should remove project" do + expect(GitlabShellWorker).to( + receive(:perform_async).with(:remove_repository, + /#{project.path_with_namespace}/) + ).twice + delete api("/projects/#{project.id}", user) response.status.should == 200 end + it 'should keep repo when "keep_repo" param is true' do + expect(GitlabShellWorker).not_to( + receive(:perform_async).with(:remove_repository, + /#{project.path_with_namespace}/) + ) + + delete api("/projects/#{project.id}?keep_repo=true", user) + response.status.should == 200 + end + it "should not remove a project if not an owner" do user3 = create(:user) project.team << [user3, :developer] From 10783f4d7b1b4b8f1ade255176c0d1b3667c66ae Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 7 Oct 2014 23:41:02 +0200 Subject: [PATCH 020/408] Remove unneeded app/finders config.autoload path Every directory under app/ is searched by default --- config/application.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index e36df913d0b..e29c24249a8 100644 --- a/config/application.rb +++ b/config/application.rb @@ -13,7 +13,6 @@ module Gitlab # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W(#{config.root}/lib - #{config.root}/app/finders #{config.root}/app/models/hooks #{config.root}/app/models/concerns #{config.root}/app/models/project_services From 8a52ff9c293a46fa7d6b4427f5f25992c7dc2c60 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Sat, 11 Oct 2014 12:53:27 -0500 Subject: [PATCH 021/408] Document Markdown table formatting issue Add a note to the Markdown documentation about a quirk of Redcarpet's table parsing. --- doc/markdown/markdown.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 6d96da76ad7..edb7a975503 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -510,6 +510,10 @@ Code above produces next output: | cell 1 | cell 2 | | cell 3 | cell 4 | +**Note** + +The row of dashes between the table header and body must have at least three dashes in each column. + ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). From fc6a291af3fb849ea590481d7df359fe37798458 Mon Sep 17 00:00:00 2001 From: HerrBerg Date: Mon, 13 Oct 2014 16:26:35 +0200 Subject: [PATCH 022/408] fix exclude wiki regex the new regex allows importing repositories with repository name ending with wiki but still exclude gitlab wiki repositories --- lib/tasks/gitlab/import.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index b6ed874e11a..159568f2883 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -34,7 +34,7 @@ namespace :gitlab do puts "Processing #{repo_path}".yellow - if path =~ /.wiki\Z/ + if path =~ /\.wiki\Z/ puts " * Skipping wiki repo" next end From d584c406e8a687ee0f15cc0ed67ae9001784b1d3 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Fri, 3 Oct 2014 00:26:56 +0200 Subject: [PATCH 023/408] Move new blob commit message textarea below editor - match edit blob view - you enter the commit message *after* you make the modifications --- app/views/projects/new_tree/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml index c47c0a3f642..f09d3659774 100644 --- a/app/views/projects/new_tree/show.html.haml +++ b/app/views/projects/new_tree/show.html.haml @@ -19,14 +19,14 @@ Encoding .col-sm-10 = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' - = render 'shared/commit_message_container', params: params, - placeholder: 'Add new file' .file-holder .file-title %i.fa.fa-file .file-content.code %pre#editor= params[:content] + = render 'shared/commit_message_container', params: params, + placeholder: 'Add new file' = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, cancel_path: project_tree_path(@project, @id) From 2d235221079ef6af90bf482a8f563dd409290751 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Thu, 25 Sep 2014 16:43:23 +0200 Subject: [PATCH 024/408] Use :message key, not :error for File::Service. --- app/controllers/projects/blob_controller.rb | 2 +- .../projects/edit_tree_controller.rb | 2 +- app/services/files/base_service.rb | 6 ------ features/project/source/browse_files.feature | 20 +++++++++++++++++++ features/steps/project/source/browse_files.rb | 8 ++++++++ features/steps/shared/paths.rb | 9 +++++++++ lib/api/files.rb | 6 +++--- 7 files changed, 42 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7009e3b1bc8..0944c7421ee 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController flash[:notice] = "Your changes have been successfully committed" redirect_to project_tree_path(@project, @ref) else - flash[:alert] = result[:error] + flash[:alert] = result[:message] render :show end end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index 8976d7c7be8..fdc1a85d8d7 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController redirect_to after_edit_path else - flash[:alert] = result[:error] + flash[:alert] = result[:message] render :show end end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index db6f0831f8b..bd245100955 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -10,12 +10,6 @@ module Files private - def success - out = super() - out[:error] = '' - out - end - def repository project.repository end diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index aca255b9444..b7d70881d56 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -34,6 +34,16 @@ Feature: Project Source Browse Files 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 + And I fill the new file name with an illegal name + And I edit code + And I fill the commit message + And I click on "Commit changes" + Then I am on the new file page + And I see a commit error message + @javascript Scenario: I can edit file Given I click on ".gitignore" file in repo @@ -50,6 +60,16 @@ Feature: Project Source Browse Files Then I am redirected to the ".gitignore" And I should see its new content + @javascript @wip + Scenario: If I don't change the content of the file I see an error message + Given I click on ".gitignore" file in repo + And I click button "edit" + And I fill the commit message + And I click on "Commit changes" + # Test fails because carriage returns are added to the file. + Then I am on the ".gitignore" edit file page + And I see a commit error message + @javascript Scenario: I can see editing preview Given I click on ".gitignore" file in repo diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 20f8f6c24ae..665f5d6d195 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -61,6 +61,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps fill_in :file_name, with: new_file_name end + step 'I fill the new file name with an illegal name' do + fill_in :file_name, with: '.git' + end + step 'I fill the commit message' do fill_in :commit_message, with: 'Not yet a commit message.' end @@ -151,6 +155,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(page).not_to have_link('permalink') end + step 'I see a commit error message' do + expect(page).to have_content('Your changes could not be committed') + end + private def set_new_content diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 1f238f8befd..5f292255ce1 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -265,6 +265,15 @@ module SharedPaths visit project_blob_path(@project, File.join(root_ref, '.gitignore')) end + step 'I am on the new file page' do + current_path.should eq(project_new_tree_path(@project, root_ref)) + end + + step 'I am on the ".gitignore" edit file page' do + current_path.should eq(project_edit_tree_path( + @project, File.join(root_ref, '.gitignore'))) + end + step 'I visit project source page for "6d39438"' do visit project_tree_path(@project, "6d39438") end diff --git a/lib/api/files.rb b/lib/api/files.rb index e63e635a4d3..84e1d311781 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -85,7 +85,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end @@ -117,7 +117,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end @@ -149,7 +149,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end end From c278520f9b96347868ce4f65b0d59aa6197e333d Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 13 Oct 2014 21:21:58 +0200 Subject: [PATCH 025/408] Remove unused dev_tools helper. --- app/controllers/application_controller.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 13d8d2a3e0a..1c7fcac6a53 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,7 +7,6 @@ class ApplicationController < ActionController::Base before_filter :check_password_expiration before_filter :add_abilities before_filter :ldap_security_check - before_filter :dev_tools if Rails.env == 'development' before_filter :default_headers before_filter :add_gon_variables before_filter :configure_permitted_parameters, if: :devise_controller? @@ -170,9 +169,6 @@ class ApplicationController < ActionController::Base response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" end - def dev_tools - end - def default_headers headers['X-Frame-Options'] = 'DENY' headers['X-XSS-Protection'] = '1; mode=block' From a22d4cebb0c7687f7f8d97849e84125a0b3a52eb Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 13 Oct 2014 21:24:42 +0200 Subject: [PATCH 026/408] Remove unused filter from ProjectsController Neither controller nor any of it's descendants have those actions. --- app/controllers/projects_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b3380a6ff23..081df35b6ce 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -6,7 +6,6 @@ class ProjectsController < ApplicationController # Authorize before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import] - before_filter :require_non_empty_project, only: [:blob, :tree, :graph] layout 'navless', only: [:new, :create, :fork] before_filter :set_title, only: [:new, :create] From 4d0d5e79ba4317cedfb2b0304ac5d376ad781b1a Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 13 Oct 2014 21:31:49 +0200 Subject: [PATCH 027/408] Factor authorize_push! and authorize_code_access! with existing method_missing. Pattern already used extensively, so let's be consistent and use it everywhere. --- app/controllers/application_controller.rb | 8 -------- app/controllers/projects/base_tree_controller.rb | 2 +- app/controllers/projects/blame_controller.rb | 2 +- app/controllers/projects/blob_controller.rb | 4 ++-- app/controllers/projects/branches_controller.rb | 4 ++-- app/controllers/projects/commit_controller.rb | 2 +- app/controllers/projects/commits_controller.rb | 2 +- app/controllers/projects/compare_controller.rb | 2 +- app/controllers/projects/edit_tree_controller.rb | 2 +- app/controllers/projects/graphs_controller.rb | 2 +- app/controllers/projects/network_controller.rb | 2 +- app/controllers/projects/new_tree_controller.rb | 2 +- app/controllers/projects/raw_controller.rb | 2 +- app/controllers/projects/refs_controller.rb | 2 +- app/controllers/projects/repositories_controller.rb | 2 +- app/controllers/projects/tags_controller.rb | 4 ++-- 16 files changed, 18 insertions(+), 26 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 13d8d2a3e0a..e05cf623a6f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -119,14 +119,6 @@ class ApplicationController < ActionController::Base return access_denied! unless can?(current_user, action, project) end - def authorize_code_access! - return access_denied! unless can?(current_user, :download_code, project) - end - - def authorize_push! - return access_denied! unless can?(current_user, :push_code, project) - end - def authorize_labels! # Labels should be accessible for issues and/or merge requests authorize_read_issue! || authorize_read_merge_request! diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb index 5e305934433..56c306063c8 100644 --- a/app/controllers/projects/base_tree_controller.rb +++ b/app/controllers/projects/base_tree_controller.rb @@ -2,7 +2,7 @@ class Projects::BaseTreeController < Projects::ApplicationController include ExtractsPath before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project end diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index a3c41301676..bad06e7aa2d 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -4,7 +4,7 @@ class Projects::BlameController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7009e3b1bc8..9234bc8cc1a 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -4,9 +4,9 @@ class Projects::BlobController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project - before_filter :authorize_push!, only: [:destroy] + before_filter :authorize_push_code!, only: [:destroy] before_filter :blob diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index faa0ce67ca8..dd6df5d196b 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -3,8 +3,8 @@ class Projects::BranchesController < Projects::ApplicationController before_filter :authorize_read_project! before_filter :require_non_empty_project - before_filter :authorize_code_access! - before_filter :authorize_push!, only: [:create, :destroy] + before_filter :authorize_download_code! + before_filter :authorize_push_code!, only: [:create, :destroy] def index @sort = params[:sort] || 'name' diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 66c67b661db..8d053f1f03a 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -4,7 +4,7 @@ class Projects::CommitController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project before_filter :commit diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index b7f09eb271d..53a0d063d8e 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -5,7 +5,7 @@ class Projects::CommitsController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 7a671e8455d..6d944025598 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,7 +1,7 @@ class Projects::CompareController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def index diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index 8976d7c7be8..2501561fa34 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -1,7 +1,7 @@ class Projects::EditTreeController < Projects::BaseTreeController before_filter :require_branch_head before_filter :blob - before_filter :authorize_push! + before_filter :authorize_push_code! before_filter :from_merge_request before_filter :after_edit_path diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 610b4967fea..21d3970d65a 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -1,7 +1,7 @@ class Projects::GraphsController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 9832495c64f..009089ee639 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -4,7 +4,7 @@ class Projects::NetworkController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb index 71a5c6499ec..ffba706b2f6 100644 --- a/app/controllers/projects/new_tree_controller.rb +++ b/app/controllers/projects/new_tree_controller.rb @@ -1,6 +1,6 @@ class Projects::NewTreeController < Projects::BaseTreeController before_filter :require_branch_head - before_filter :authorize_push! + before_filter :authorize_push_code! def show end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 5ec9c576a66..f4fdd616c50 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -4,7 +4,7 @@ class Projects::RawController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def show diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 7997c726fbb..9ac189a78b3 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -3,7 +3,7 @@ class Projects::RefsController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def switch diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 4e0f190ed1c..6d8ef0f1ac8 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -1,7 +1,7 @@ class Projects::RepositoriesController < Projects::ApplicationController # Authorize before_filter :authorize_read_project! - before_filter :authorize_code_access! + before_filter :authorize_download_code! before_filter :require_non_empty_project def archive diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 537c94bda20..94794fb5dd0 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -3,8 +3,8 @@ class Projects::TagsController < Projects::ApplicationController before_filter :authorize_read_project! before_filter :require_non_empty_project - before_filter :authorize_code_access! - before_filter :authorize_push!, only: [:create] + before_filter :authorize_download_code! + before_filter :authorize_push_code!, only: [:create] before_filter :authorize_admin_project!, only: [:destroy] def index From 3d705131f7d1a85dbf09053a200fbf36c6225625 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 13 Oct 2014 22:05:21 -0500 Subject: [PATCH 028/408] Added a password strength indicator to the profile page and the sign_up page, added CSS to best display it and created the custom script to load the meter. --- app/assets/javascripts/application.js.coffee | 2 + .../javascripts/password_strength.js.coffee | 33 + app/assets/stylesheets/sections/profile.scss | 9 + app/views/devise/registrations/new.html.haml | 6 +- app/views/profiles/passwords/edit.html.haml | 2 +- .../javascripts/pwstrength-bootstrap-1.2.2.js | 659 ++++++++++++++++++ 6 files changed, 707 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/password_strength.js.coffee create mode 100644 vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index ff0d0bb32b9..493babad856 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -18,6 +18,8 @@ #= require jquery.turbolinks #= require turbolinks #= require bootstrap +#= require pwstrength-bootstrap-1.2.2 +#= require password_strength #= require select2 #= require raphael #= require g.raphael-min diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee new file mode 100644 index 00000000000..e6fec307c59 --- /dev/null +++ b/app/assets/javascripts/password_strength.js.coffee @@ -0,0 +1,33 @@ +overwritten_messages = + wordSimilarToUsername: "Your password should not contain your username" + +overwritten_rules = + wordSequences: false + +$(document).ready -> + profileOptions = {} + profileOptions.ui = + container: "#password-strength" + showVerdictsInsideProgressBar: true + showPopover: true + showErrors: true + errorMessages: overwritten_messages + profileOptions.rules = + activated: overwritten_rules + + signUpOptions = {} + signUpOptions.common = + usernameField: "#user_username" + signUpOptions.ui = + container: "#password-strength" + showPopover: true + showErrors: true + showVerdicts: false + showProgressBar: false + showStatus: true + errorMessages: overwritten_messages + signUpOptions.rules = + activated: overwritten_rules + + $("#user_password").pwstrength profileOptions + $("#user_password_sign_up").pwstrength signUpOptions diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 086875582f3..2c2af7f52c5 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -111,3 +111,12 @@ height: 50px; } } + +//CSS for password-strength indicator +#password-strength { + margin-bottom: 0; +} + +.progress { + margin-top: 10px; +} diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index d6a952f3dc5..806d206d7b9 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -2,7 +2,7 @@ .login-heading %h3 Sign up .login-body - = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| + = form_for(resource, as: resource_name, url: registration_path(resource_name), role: 'form') do |f| .devise-errors = devise_error_messages! %div @@ -11,8 +11,8 @@ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true %div = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true - %div - = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true + .form-group#password-strength + = f.password_field :password, class: "form-control middle", id: "user_password_sign_up", placeholder: "Password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true %div diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 2a7d317aa3e..4440dcf338d 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -21,7 +21,7 @@ %div = link_to "Forgot your password?", reset_profile_password_path, method: :put - .form-group + .form-group#password-strength = f.label :password, 'New password', class: 'control-label' .col-sm-10 = f.password_field :password, required: true, class: 'form-control' diff --git a/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js new file mode 100644 index 00000000000..ee374a07fab --- /dev/null +++ b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js @@ -0,0 +1,659 @@ +/*! + * jQuery Password Strength plugin for Twitter Bootstrap + * + * Copyright (c) 2008-2013 Tane Piper + * Copyright (c) 2013 Alejandro Blanco + * Dual licensed under the MIT and GPL licenses. + */ + +(function (jQuery) { +// Source: src/rules.js + + var rulesEngine = {}; + + try { + if (!jQuery && module && module.exports) { + var jQuery = require("jquery"), + jsdom = require("jsdom").jsdom; + jQuery = jQuery(jsdom().parentWindow); + } + } catch (ignore) {} + + (function ($, rulesEngine) { + "use strict"; + var validation = {}; + + rulesEngine.forbiddenSequences = [ + "0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl", + "zxcvbnm", "!@#$%^&*()_+" + ]; + + validation.wordNotEmail = function (options, word, score) { + if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) { + return score; + } + return 0; + }; + + validation.wordLength = function (options, word, score) { + var wordlen = word.length, + lenScore = Math.pow(wordlen, options.rules.raisePower); + if (wordlen < options.common.minChar) { + lenScore = (lenScore + score); + } + return lenScore; + }; + + validation.wordSimilarToUsername = function (options, word, score) { + var username = $(options.common.usernameField).val(); + if (username && word.toLowerCase().match(username.toLowerCase())) { + return score; + } + return 0; + }; + + validation.wordTwoCharacterClasses = function (options, word, score) { + if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) || + (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) || + (word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) { + return score; + } + return 0; + }; + + validation.wordRepetitions = function (options, word, score) { + if (word.match(/(.)\1\1/)) { return score; } + return 0; + }; + + validation.wordSequences = function (options, word, score) { + var found = false, + j; + if (word.length > 2) { + $.each(rulesEngine.forbiddenSequences, function (idx, seq) { + var sequences = [seq, seq.split('').reverse().join('')]; + $.each(sequences, function (idx, sequence) { + for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3: + if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) { + found = true; + } + } + }); + }); + if (found) { return score; } + } + return 0; + }; + + validation.wordLowercase = function (options, word, score) { + return word.match(/[a-z]/) && score; + }; + + validation.wordUppercase = function (options, word, score) { + return word.match(/[A-Z]/) && score; + }; + + validation.wordOneNumber = function (options, word, score) { + return word.match(/\d+/) && score; + }; + + validation.wordThreeNumbers = function (options, word, score) { + return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score; + }; + + validation.wordOneSpecialChar = function (options, word, score) { + return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score; + }; + + validation.wordTwoSpecialChar = function (options, word, score) { + return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score; + }; + + validation.wordUpperLowerCombo = function (options, word, score) { + return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score; + }; + + validation.wordLetterNumberCombo = function (options, word, score) { + return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score; + }; + + validation.wordLetterNumberCharCombo = function (options, word, score) { + return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score; + }; + + rulesEngine.validation = validation; + + rulesEngine.executeRules = function (options, word) { + var totalScore = 0; + + $.each(options.rules.activated, function (rule, active) { + if (active) { + var score = options.rules.scores[rule], + funct = rulesEngine.validation[rule], + result, + errorMessage; + + if (!$.isFunction(funct)) { + funct = options.rules.extra[rule]; + } + + if ($.isFunction(funct)) { + result = funct(options, word, score); + if (result) { + totalScore += result; + } + if (result < 0 || (!$.isNumeric(result) && !result)) { + errorMessage = options.ui.spanError(options, rule); + if (errorMessage.length > 0) { + options.instances.errors.push(errorMessage); + } + } + } + } + }); + + return totalScore; + }; + }(jQuery, rulesEngine)); + + try { + if (module && module.exports) { + module.exports = rulesEngine; + } + } catch (ignore) {} + +// Source: src/options.js + + + + + var defaultOptions = {}; + + defaultOptions.common = {}; + defaultOptions.common.minChar = 6; + defaultOptions.common.usernameField = "#username"; + defaultOptions.common.userInputs = [ + // Selectors for input fields with user input + ]; + defaultOptions.common.onLoad = undefined; + defaultOptions.common.onKeyUp = undefined; + defaultOptions.common.zxcvbn = false; + defaultOptions.common.debug = false; + + defaultOptions.rules = {}; + defaultOptions.rules.extra = {}; + defaultOptions.rules.scores = { + wordNotEmail: -100, + wordLength: -50, + wordSimilarToUsername: -100, + wordSequences: -50, + wordTwoCharacterClasses: 2, + wordRepetitions: -25, + wordLowercase: 1, + wordUppercase: 3, + wordOneNumber: 3, + wordThreeNumbers: 5, + wordOneSpecialChar: 3, + wordTwoSpecialChar: 5, + wordUpperLowerCombo: 2, + wordLetterNumberCombo: 2, + wordLetterNumberCharCombo: 2 + }; + defaultOptions.rules.activated = { + wordNotEmail: true, + wordLength: true, + wordSimilarToUsername: true, + wordSequences: true, + wordTwoCharacterClasses: false, + wordRepetitions: false, + wordLowercase: true, + wordUppercase: true, + wordOneNumber: true, + wordThreeNumbers: true, + wordOneSpecialChar: true, + wordTwoSpecialChar: true, + wordUpperLowerCombo: true, + wordLetterNumberCombo: true, + wordLetterNumberCharCombo: true + }; + defaultOptions.rules.raisePower = 1.4; + + defaultOptions.ui = {}; + defaultOptions.ui.bootstrap2 = false; + defaultOptions.ui.showProgressBar = true; + defaultOptions.ui.showPopover = false; + defaultOptions.ui.showStatus = false; + defaultOptions.ui.spanError = function (options, key) { + "use strict"; + var text = options.ui.errorMessages[key]; + if (!text) { return ''; } + return '' + text + ''; + }; + defaultOptions.ui.errorMessages = { + wordLength: "Your password is too short", + wordNotEmail: "Do not use your email as your password", + wordSimilarToUsername: "Your password cannot contain your username", + wordTwoCharacterClasses: "Use different character classes", + wordRepetitions: "Too many repetitions", + wordSequences: "Your password contains sequences" + }; + defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"]; + defaultOptions.ui.showVerdicts = true; + defaultOptions.ui.showVerdictsInsideProgressBar = false; + defaultOptions.ui.showErrors = false; + defaultOptions.ui.container = undefined; + defaultOptions.ui.viewports = { + progress: undefined, + verdict: undefined, + errors: undefined + }; + defaultOptions.ui.scores = [14, 26, 38, 50]; + +// Source: src/ui.js + + + + + var ui = {}; + + (function ($, ui) { + "use strict"; + + var barClasses = ["danger", "warning", "success"], + statusClasses = ["error", "warning", "success"]; + + ui.getContainer = function (options, $el) { + var $container; + + $container = $(options.ui.container); + if (!($container && $container.length === 1)) { + $container = $el.parent(); + } + return $container; + }; + + ui.findElement = function ($container, viewport, cssSelector) { + if (viewport) { + return $container.find(viewport).find(cssSelector); + } + return $container.find(cssSelector); + }; + + ui.getUIElements = function (options, $el) { + var $container, result; + + if (options.instances.viewports) { + return options.instances.viewports; + } + + $container = ui.getContainer(options, $el); + + result = {}; + result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress"); + if (options.ui.showVerdictsInsideProgressBar) { + result.$verdict = result.$progressbar.find("span.password-verdict"); + } + + if (!options.ui.showPopover) { + if (!options.ui.showVerdictsInsideProgressBar) { + result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict"); + } + result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list"); + } + + options.instances.viewports = result; + return result; + }; + + ui.initProgressBar = function (options, $el) { + var $container = ui.getContainer(options, $el), + progressbar = "
"; + if (options.ui.showVerdictsInsideProgressBar) { + progressbar += ""; + } + progressbar += "
"; + + if (options.ui.viewports.progress) { + $container.find(options.ui.viewports.progress).append(progressbar); + } else { + $(progressbar).insertAfter($el); + } + }; + + ui.initHelper = function (options, $el, html, viewport) { + var $container = ui.getContainer(options, $el); + if (viewport) { + $container.find(viewport).append(html); + } else { + $(html).insertAfter($el); + } + }; + + ui.initVerdict = function (options, $el) { + ui.initHelper(options, $el, "", + options.ui.viewports.verdict); + }; + + ui.initErrorList = function (options, $el) { + ui.initHelper(options, $el, "
    ", + options.ui.viewports.errors); + }; + + ui.initPopover = function (options, $el) { + $el.popover("destroy"); + $el.popover({ + html: true, + placement: "top", + trigger: "manual", + content: " " + }); + }; + + ui.initUI = function (options, $el) { + if (options.ui.showPopover) { + ui.initPopover(options, $el); + } else { + if (options.ui.showErrors) { ui.initErrorList(options, $el); } + if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) { + ui.initVerdict(options, $el); + } + } + if (options.ui.showProgressBar) { + ui.initProgressBar(options, $el); + } + }; + + ui.possibleProgressBarClasses = ["danger", "warning", "success"]; + + ui.updateProgressBar = function (options, $el, cssClass, percentage) { + var $progressbar = ui.getUIElements(options, $el).$progressbar, + $bar = $progressbar.find(".progress-bar"), + cssPrefix = "progress-"; + + if (options.ui.bootstrap2) { + $bar = $progressbar.find(".bar"); + cssPrefix = ""; + } + + $.each(ui.possibleProgressBarClasses, function (idx, value) { + $bar.removeClass(cssPrefix + "bar-" + value); + }); + $bar.addClass(cssPrefix + "bar-" + barClasses[cssClass]); + $bar.css("width", percentage + '%'); + }; + + ui.updateVerdict = function (options, $el, text) { + var $verdict = ui.getUIElements(options, $el).$verdict; + $verdict.text(text); + }; + + ui.updateErrors = function (options, $el) { + var $errors = ui.getUIElements(options, $el).$errors, + html = ""; + $.each(options.instances.errors, function (idx, err) { + html += "
  • " + err + "
  • "; + }); + $errors.html(html); + }; + + ui.updatePopover = function (options, $el, verdictText) { + var popover = $el.data("bs.popover"), + html = "", + hide = true; + + if (options.ui.showVerdicts && + !options.ui.showVerdictsInsideProgressBar && + verdictText.length > 0) { + html = "
    " + verdictText + + "
    "; + hide = false; + } + if (options.ui.showErrors) { + html += "
      "; + $.each(options.instances.errors, function (idx, err) { + html += "
    • " + err + "
    • "; + hide = false; + }); + html += "
    "; + } + + if (hide) { + $el.popover("hide"); + return; + } + + if (options.ui.bootstrap2) { popover = $el.data("popover"); } + + if (popover.$arrow && popover.$arrow.parents("body").length > 0) { + $el.find("+ .popover .popover-content").html(html); + } else { + // It's hidden + popover.options.content = html; + $el.popover("show"); + } + }; + + ui.updateFieldStatus = function (options, $el, cssClass) { + var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group", + $container = $el.parents(targetClass).first(); + + $.each(statusClasses, function (idx, css) { + if (!options.ui.bootstrap2) { css = "has-" + css; } + $container.removeClass(css); + }); + + cssClass = statusClasses[cssClass]; + if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; } + $container.addClass(cssClass); + }; + + ui.percentage = function (score, maximun) { + var result = Math.floor(100 * score / maximun); + result = result < 0 ? 0 : result; + result = result > 100 ? 100 : result; + return result; + }; + + ui.getVerdictAndCssClass = function (options, score) { + var cssClass, verdictText, level; + + if (score <= 0) { + cssClass = 0; + level = -1; + verdictText = options.ui.verdicts[0]; + } else if (score < options.ui.scores[0]) { + cssClass = 0; + level = 0; + verdictText = options.ui.verdicts[0]; + } else if (score < options.ui.scores[1]) { + cssClass = 0; + level = 1; + verdictText = options.ui.verdicts[1]; + } else if (score < options.ui.scores[2]) { + cssClass = 1; + level = 2; + verdictText = options.ui.verdicts[2]; + } else if (score < options.ui.scores[3]) { + cssClass = 1; + level = 3; + verdictText = options.ui.verdicts[3]; + } else { + cssClass = 2; + level = 4; + verdictText = options.ui.verdicts[4]; + } + + return [verdictText, cssClass, level]; + }; + + ui.updateUI = function (options, $el, score) { + var cssClass, barPercentage, verdictText; + + cssClass = ui.getVerdictAndCssClass(options, score); + verdictText = cssClass[0]; + cssClass = cssClass[1]; + + if (options.ui.showProgressBar) { + barPercentage = ui.percentage(score, options.ui.scores[3]); + ui.updateProgressBar(options, $el, cssClass, barPercentage); + if (options.ui.showVerdictsInsideProgressBar) { + ui.updateVerdict(options, $el, verdictText); + } + } + + if (options.ui.showStatus) { + ui.updateFieldStatus(options, $el, cssClass); + } + + if (options.ui.showPopover) { + ui.updatePopover(options, $el, verdictText); + } else { + if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) { + ui.updateVerdict(options, $el, verdictText); + } + if (options.ui.showErrors) { + ui.updateErrors(options, $el); + } + } + }; + }(jQuery, ui)); + +// Source: src/methods.js + + + + + var methods = {}; + + (function ($, methods) { + "use strict"; + var onKeyUp, applyToAll; + + onKeyUp = function (event) { + var $el = $(event.target), + options = $el.data("pwstrength-bootstrap"), + word = $el.val(), + userInputs, + verdictText, + verdictLevel, + score; + + if (options === undefined) { return; } + + options.instances.errors = []; + if (options.common.zxcvbn) { + userInputs = []; + $.each(options.common.userInputs, function (idx, selector) { + userInputs.push($(selector).val()); + }); + userInputs.push($(options.common.usernameField).val()); + score = zxcvbn(word, userInputs).entropy; + } else { + score = rulesEngine.executeRules(options, word); + } + ui.updateUI(options, $el, score); + verdictText = ui.getVerdictAndCssClass(options, score); + verdictLevel = verdictText[2]; + verdictText = verdictText[0]; + + if (options.common.debug) { console.log(score + ' - ' + verdictText); } + + if ($.isFunction(options.common.onKeyUp)) { + options.common.onKeyUp(event, { + score: score, + verdictText: verdictText, + verdictLevel: verdictLevel + }); + } + }; + + methods.init = function (settings) { + this.each(function (idx, el) { + // Make it deep extend (first param) so it extends too the + // rules and other inside objects + var clonedDefaults = $.extend(true, {}, defaultOptions), + localOptions = $.extend(true, clonedDefaults, settings), + $el = $(el); + + localOptions.instances = {}; + $el.data("pwstrength-bootstrap", localOptions); + $el.on("keyup", onKeyUp); + $el.on("change", onKeyUp); + $el.on("onpaste", onKeyUp); + + ui.initUI(localOptions, $el); + if ($.trim($el.val())) { // Not empty, calculate the strength + $el.trigger("keyup"); + } + + if ($.isFunction(localOptions.common.onLoad)) { + localOptions.common.onLoad(); + } + }); + + return this; + }; + + methods.destroy = function () { + this.each(function (idx, el) { + var $el = $(el), + options = $el.data("pwstrength-bootstrap"), + elements = ui.getUIElements(options, $el); + elements.$progressbar.remove(); + elements.$verdict.remove(); + elements.$errors.remove(); + $el.removeData("pwstrength-bootstrap"); + }); + }; + + methods.forceUpdate = function () { + this.each(function (idx, el) { + var event = { target: el }; + onKeyUp(event); + }); + }; + + methods.addRule = function (name, method, score, active) { + this.each(function (idx, el) { + var options = $(el).data("pwstrength-bootstrap"); + + options.rules.activated[name] = active; + options.rules.scores[name] = score; + options.rules.extra[name] = method; + }); + }; + + applyToAll = function (rule, prop, value) { + this.each(function (idx, el) { + $(el).data("pwstrength-bootstrap").rules[prop][rule] = value; + }); + }; + + methods.changeScore = function (rule, score) { + applyToAll.call(this, rule, "scores", score); + }; + + methods.ruleActive = function (rule, active) { + applyToAll.call(this, rule, "activated", active); + }; + + $.fn.pwstrength = function (method) { + var result; + + if (methods[method]) { + result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === "object" || !method) { + result = methods.init.apply(this, arguments); + } else { + $.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap"); + } + + return result; + }; + }(jQuery, methods)); +}(jQuery)); \ No newline at end of file From 62b322d7b567f1fae2ea8b5a3b0e71a62506e47d Mon Sep 17 00:00:00 2001 From: Kevin Houdebert Date: Tue, 14 Oct 2014 19:07:34 +0200 Subject: [PATCH 029/408] Add Hipchat services API --- CHANGELOG | 1 + doc/api/services.md | 46 ++++++++++++++++++++++++++++++ lib/api/services.rb | 38 ++++++++++++++++++++++-- spec/requests/api/services_spec.rb | 26 +++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 doc/api/services.md diff --git a/CHANGELOG b/CHANGELOG index 316d7af174f..192ff41f1f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 7.4.0 - New milestone and label links on issue edit form - Improved repository graphs - Improve event note display in dashboard and project activity views (Vinnie Okada) + - API: Add support for Hipchat (Kevin Houdebert) v 7.3.2 - Fix creating new file via web editor diff --git a/doc/api/services.md b/doc/api/services.md new file mode 100644 index 00000000000..ab9f9c00c67 --- /dev/null +++ b/doc/api/services.md @@ -0,0 +1,46 @@ +# Services + +## GitLab CI + +### Edit GitLab CI service + +Set GitLab CI service for a project. + +``` +PUT /projects/:id/services/gitlab-ci +``` + +Parameters: + +- `token` (required) - CI project token +- `project_url` (required) - CI project url + +### Delete GitLab CI service + +Delete GitLab CI service settings for a project. + +``` +DELETE /projects/:id/services/gitlab-ci +``` + +## Hipchat + +### Edit Hipchat service + +Set Hipchat service for project. + +``` +PUT /projects/:id/services/hipchat +``` +Parameters: + +- `token` (required) - Hipchat token +- `room` (required) - Hipchat room name + +### Delete Hipchat service + +Delete Hipchat service for a project. + +``` +DELETE /projects/:id/services/hipchat +``` diff --git a/lib/api/services.rb b/lib/api/services.rb index bde502e32e1..3ad59cf3adf 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -28,7 +28,7 @@ module API # Delete GitLab CI service settings # # Example Request: - # DELETE /projects/:id/keys/:id + # DELETE /projects/:id/services/gitlab-ci delete ":id/services/gitlab-ci" do if user_project.gitlab_ci_service user_project.gitlab_ci_service.update_attributes( @@ -38,7 +38,41 @@ module API ) end end + + # Set Hipchat service for project + # + # Parameters: + # token (required) - Hipchat token + # room (required) - Hipchat room name + # + # Example Request: + # PUT /projects/:id/services/hipchat + put ':id/services/hipchat' do + required_attributes! [:token, :room] + attrs = attributes_for_keys [:token, :room] + user_project.build_missing_services + + if user_project.hipchat_service.update_attributes( + attrs.merge(active: true)) + true + else + not_found! + end + end + + # Delete Hipchat service settings + # + # Example Request: + # DELETE /projects/:id/services/hipchat + delete ':id/services/hipchat' do + if user_project.hipchat_service + user_project.hipchat_service.update_attributes( + active: false, + token: nil, + room: nil + ) + end + end end end end - diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index f883c9e028a..d8282d0696b 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -27,4 +27,30 @@ describe API::API, api: true do project.gitlab_ci_service.should be_nil end end + + describe 'PUT /projects/:id/services/hipchat' do + it 'should update hipchat settings' do + put api("/projects/#{project.id}/services/hipchat", user), + token: 'secret-token', room: 'test' + + response.status.should == 200 + project.hipchat_service.should_not be_nil + end + + it 'should return if required fields missing' do + put api("/projects/#{project.id}/services/gitlab-ci", user), + token: 'secret-token', active: true + + response.status.should == 400 + end + end + + describe 'DELETE /projects/:id/services/hipchat' do + it 'should delete hipchat settings' do + delete api("/projects/#{project.id}/services/hipchat", user) + + response.status.should == 200 + project.hipchat_service.should be_nil + end + end end From ace045499a1ac4988401fb5511e7678e6c53108b Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Tue, 14 Oct 2014 22:54:30 -0700 Subject: [PATCH 030/408] fix permission issue in upgrade guides --- doc/update/7.2-to-7.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md index 44f3f8f1a38..ebdd4ff60fa 100644 --- a/doc/update/7.2-to-7.3.md +++ b/doc/update/7.2-to-7.3.md @@ -74,7 +74,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab # Enable Redis socket for default Debian / Ubuntu path echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). - sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf + sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf # Activate the changes to redis.conf sudo service redis-server restart # Add git to the redis group From b5763e91cdeaba55b3c426129ba3c4f9638c5eb1 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 15 Oct 2014 18:26:15 +0300 Subject: [PATCH 031/408] add gitlab-shell identification --- .gitignore | 1 + GITLAB_SHELL_VERSION | 2 +- .../initializers/gitlab_shell_secret_token.rb | 19 +++++++++++++++++++ lib/api/helpers.rb | 8 ++++++++ lib/api/internal.rb | 4 ++++ spec/requests/api/internal_spec.rb | 14 +++++++++----- 6 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 config/initializers/gitlab_shell_secret_token.rb diff --git a/.gitignore b/.gitignore index 4f778371512..2c6b65b7b7d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ public/assets/ .envrc dump.rdb tags +.gitlab_shell_secret diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 38f77a65b30..e9307ca5751 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.0.1 +2.0.2 diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb new file mode 100644 index 00000000000..8d2b771e535 --- /dev/null +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -0,0 +1,19 @@ +# Be sure to restart your server when you modify this file. + +require 'securerandom' + +# Your secret key for verifying the gitlab_shell. + + +secret_file = Rails.root.join('.gitlab_shell_secret') +gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') + +unless File.exist? secret_file + # Generate a new token of 16 random hexadecimal characters and store it in secret_file. + token = SecureRandom.hex(16) + File.write(secret_file, token) +end + +if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink) + FileUtils.symlink(secret_file, gitlab_shell_symlink) +end \ No newline at end of file diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 3262884f6d3..027fb20ec46 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -67,6 +67,10 @@ module API unauthorized! unless current_user end + def authenticate_by_gitlab_shell_token! + unauthorized! unless secret_token == params['secret_token'] + end + def authenticated_as_admin! forbidden! unless current_user.is_admin? end @@ -193,5 +197,9 @@ module API abilities end end + + def secret_token + File.read(Rails.root.join('.gitlab_shell_secret')) + end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 9ac659f50fd..ebf2296097d 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -1,6 +1,10 @@ module API # Internal access API class Internal < Grape::API + before { + authenticate_by_gitlab_shell_token! + } + namespace 'internal' do # Check if git command is allowed to project # diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 6df5ef38961..677b1494041 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -5,10 +5,11 @@ describe API::API, api: true do let(:user) { create(:user) } let(:key) { create(:key, user: user) } let(:project) { create(:project) } + let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') } describe "GET /internal/check", no_db: true do it do - get api("/internal/check") + get api("/internal/check"), secret_token: secret_token response.status.should == 200 json_response['api_version'].should == API::API.version @@ -17,7 +18,7 @@ describe API::API, api: true do describe "GET /internal/discover" do it do - get(api("/internal/discover"), key_id: key.id) + get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) response.status.should == 200 @@ -159,7 +160,8 @@ describe API::API, api: true do api("/internal/allowed"), key_id: key.id, project: project.path_with_namespace, - action: 'git-upload-pack' + action: 'git-upload-pack', + secret_token: secret_token ) end @@ -169,7 +171,8 @@ describe API::API, api: true do changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, project: project.path_with_namespace, - action: 'git-receive-pack' + action: 'git-receive-pack', + secret_token: secret_token ) end @@ -179,7 +182,8 @@ describe API::API, api: true do ref: 'master', key_id: key.id, project: project.path_with_namespace, - action: 'git-upload-archive' + action: 'git-upload-archive', + secret_token: secret_token ) end end From b57ec54fae7f17fad21c564aee1c39625f77ec20 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 15 Oct 2014 20:25:47 +0300 Subject: [PATCH 032/408] Point to correct project on githost.io. --- doc/development/ci_setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md index d74f4852a3d..bbd4bf6b257 100644 --- a/doc/development/ci_setup.md +++ b/doc/development/ci_setup.md @@ -4,7 +4,7 @@ This document describes what services we use for testing GitLab and GitLab CI. We currently use three CI services to test GitLab: -1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/2/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce) +1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce) 2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org 3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) From c2bcdeb95090e2344b90e5babe5b68dfba064130 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 15 Oct 2014 20:52:39 +0300 Subject: [PATCH 033/408] Make semaphore configuration an ordered list. --- doc/development/ci_setup.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md index bbd4bf6b257..ee16aedafe7 100644 --- a/doc/development/ci_setup.md +++ b/doc/development/ci_setup.md @@ -25,9 +25,9 @@ We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master # Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) -Language: Ruby -Ruby verion: 2.1.2 -database.yml: pg +- Language: Ruby +- Ruby verion: 2.1.2 +- database.yml: pg Build commands From 39c66c822ec955205aa69a7d38b16669f488e8da Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Fri, 10 Oct 2014 11:06:08 +0200 Subject: [PATCH 034/408] Use Gitlab.config instead of Settings everywhere --- app/controllers/admin/background_jobs_controller.rb | 2 +- app/views/admin/background_jobs/show.html.haml | 4 ++-- lib/gitlab/url_builder.rb | 2 +- lib/tasks/gitlab/shell.rake | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb index 4c1d0df4110..338496013a0 100644 --- a/app/controllers/admin/background_jobs_controller.rb +++ b/app/controllers/admin/background_jobs_controller.rb @@ -1,6 +1,6 @@ class Admin::BackgroundJobsController < Admin::ApplicationController def show - ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) + ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) @sidekiq_processes = ps_output.split("\n").grep(/sidekiq/) end end diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 9dcf7b488ee..8db2b2a709c 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -25,7 +25,7 @@ - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) - data = process.strip.split(' ') %tr - %td= Settings.gitlab.user + %td= gitlab_config.user - 5.times do %td= data.shift %td= data.join(' ') @@ -36,7 +36,7 @@ If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. %p %i.fa.fa-exclamation-circle - If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab. + If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab. diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index de7e0404086..877488d8471 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -19,7 +19,7 @@ module Gitlab issue = Issue.find(id) project_issue_url(id: issue.iid, project_id: issue.project, - host: Settings.gitlab['url']) + host: Gitlab.config.gitlab['url']) end end end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index a8f26a7c029..646ba98ca48 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -7,9 +7,9 @@ namespace :gitlab do default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") - user = Settings.gitlab.user - home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home - gitlab_url = Settings.gitlab.url + user = Gitlab.config.gitlab.user + home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home + gitlab_url = Gitlab.config.gitlab.url # gitlab-shell requires a / at the end of the url gitlab_url += "/" unless gitlab_url.match(/\/$/) repos_path = Gitlab.config.gitlab_shell.repos_path From 786045ab81887e132a902190abb62b9747420c17 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Thu, 16 Oct 2014 00:00:17 -0500 Subject: [PATCH 035/408] Fix Rspec error when using non-default port Prevent test failures when GitLab is configured to use a port other than 80. --- spec/helpers/gitlab_markdown_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 61751a82369..e933b0842af 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -594,7 +594,7 @@ describe GitlabMarkdownHelper do end it "should generate absolute urls for emoji" do - markdown(":smile:").should include("src=\"http://localhost/assets/emoji/smile.png") + markdown(':smile:').should match(%r{src="http://localhost(:\d+)?/assets/emoji/smile.png}) end it "should generate absolute urls for emoji if relative url is present" do From 5d7e1b6ae2eab12017c6f6cec9ef6cc96bf2666d Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Wed, 15 Oct 2014 23:51:53 -0700 Subject: [PATCH 036/408] match latest config from https://cipherli.st/ --- lib/support/nginx/gitlab-ssl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index d3fb467ef27..42431f54b31 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -60,18 +60,16 @@ server { client_max_body_size 20m; ## Strong SSL Security - ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ ssl on; ssl_certificate /etc/nginx/ssl/gitlab.crt; ssl_certificate_key /etc/nginx/ssl/gitlab.key; # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs - ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4'; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_session_cache builtin:1000 shared:SSL:10m; - - ssl_prefer_server_ciphers on; + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; ## [WARNING] The following header states that the browser should only communicate ## with your server over a secure connection for the next 24 months. @@ -88,7 +86,7 @@ server { # ssl_stapling_verify on; # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired - # resolver_timeout 10s; + # resolver_timeout 5s; ## [Optional] Generate a stronger DHE parameter: ## cd /etc/ssl/certs From 92c184a57f7698e79288b380cebc68b839afb4f5 Mon Sep 17 00:00:00 2001 From: Jan-Willem van der Meer Date: Thu, 16 Oct 2014 11:46:40 +0200 Subject: [PATCH 037/408] Disallow new users from Oauth signup if `allow_single_sign_on` is disabled Because devise will trigger a save, allowing unsaved users to login, behaviour had changed. The current implementation returns a pre-build user, which can be saved without errors. Reported in #1677 --- app/controllers/omniauth_callbacks_controller.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index f46b36568f3..589f8387b03 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -54,11 +54,15 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController @user.save end - if @user.valid? + # Only allow properly saved users to login. + if @user.persisted? && @user.valid? sign_in_and_redirect(@user.gl_user) - else + elsif @user.gl_user.errors.any? error_message = @user.gl_user.errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return + else + flash[:notice] = "There's no such user!" + redirect_to new_user_session_path end end end From 761c2a64cc651e2239aac65c6ebd81d17f444703 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 16 Oct 2014 14:02:24 +0300 Subject: [PATCH 038/408] Add items to changelog --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6ddd59df1c4..05290698320 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,13 @@ v 7.4.0 - Improved repository graphs - Improve event note display in dashboard and project activity views (Vinnie Okada) - Add users sorting to admin area + - UI improvements + - Fix ambiguous sha problem with mentioned commit + - Fixed bug with apostrophe when at mentioning users + - Add active directory ldap option + - Developers can push to wiki repo. Protected branches does not affect wiki repo any more + - Faster rev list + - Fix branch removal v 7.3.2 - Fix creating new file via web editor From 2e485af7b051512f804ae46a81cba480d2eca46f Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 16 Oct 2014 12:16:18 +0000 Subject: [PATCH 039/408] bump gitlab-shelle --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index e9307ca5751..7ec1d6db408 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.0.2 +2.1.0 From 32d0bf3afdd25eaff1f3bd2e80696e39b5b69c35 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 16 Oct 2014 16:50:09 +0300 Subject: [PATCH 040/408] Fix snippets seeds Signed-off-by: Dmitriy Zaporozhets --- db/fixtures/development/12_snippets.rb | 34 ++++++++++++++++++++------ 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb index ff91e8430a4..b3a6f39c7d5 100644 --- a/db/fixtures/development/12_snippets.rb +++ b/db/fixtures/development/12_snippets.rb @@ -1,9 +1,26 @@ Gitlab::Seeder.quiet do - contents = [ - `curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`, - `curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`, - `curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`, - ] + content =< { where(access_level: GUEST) } + scope :reporters, -> { where(access_level: REPORTER) } + scope :developers, -> { where(access_level: DEVELOPER) } + scope :masters, -> { where(access_level: MASTER) } + scope :owners, -> { where(access_level: OWNER) } + + delegate :name, :username, :email, to: :user, prefix: true +end +eos (1..50).each do |i| user = User.all.sample @@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do id: i, author_id: user.id, title: Faker::Lorem.sentence(3), - file_name: Faker::Internet.domain_word + '.sh', - private: [true, false].sample, - content: contents.sample, + file_name: Faker::Internet.domain_word + '.rb', + visibility_level: Gitlab::VisibilityLevel.values.sample, + content: content, }]) + print('.') end end From fad588f2bee102bf4ab090874d041e227d4e2ee4 Mon Sep 17 00:00:00 2001 From: Jan-Willem van der Meer Date: Thu, 16 Oct 2014 17:18:40 +0200 Subject: [PATCH 041/408] Remove LDAP save test This is handled within the LDAP class --- spec/lib/gitlab/oauth/user_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb index e4e96fd9f49..7c7d6babbfd 100644 --- a/spec/lib/gitlab/oauth/user_spec.rb +++ b/spec/lib/gitlab/oauth/user_spec.rb @@ -29,16 +29,16 @@ describe Gitlab::OAuth::User do end describe :save do - context "LDAP" do - let(:provider) { 'ldap' } - it "creates a user from LDAP" do - oauth_user.save + let(:provider) { 'twitter' } - expect(gl_user).to be_valid - expect(gl_user.extern_uid).to eql uid - expect(gl_user.provider).to eql 'ldap' - end + it "creates a user from Omniauth" do + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.extern_uid).to eql uid + expect(gl_user.provider).to eql 'twitter' end + end context "twitter" do let(:provider) { 'twitter' } From d9bfebc0e87ef426aea7eb4fdd1338f04b106354 Mon Sep 17 00:00:00 2001 From: Jan-Willem van der Meer Date: Thu, 16 Oct 2014 20:08:30 +0200 Subject: [PATCH 042/408] Add regressiontest to verify allow_single_sign_on setting verification for #1677 Since testing omniauth_callback_controller.rb is very difficult, the logic is moved to the models --- .../omniauth_callbacks_controller.rb | 13 +++++-------- lib/gitlab/oauth/user.rb | 17 ++++++++++++++--- spec/lib/gitlab/oauth/user_spec.rb | 19 ++++++++----------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 589f8387b03..58d1e37f655 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -49,22 +49,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController redirect_to profile_path else @user = Gitlab::OAuth::User.new(oauth) - - if Gitlab.config.omniauth['allow_single_sign_on'] && @user.new? - @user.save - end + @user.save # Only allow properly saved users to login. if @user.persisted? && @user.valid? sign_in_and_redirect(@user.gl_user) - elsif @user.gl_user.errors.any? + else @user.gl_user.errors.any? error_message = @user.gl_user.errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return - else - flash[:notice] = "There's no such user!" - redirect_to new_user_session_path end end + rescue StandardError + flash[:notice] = "There's no such user!" + redirect_to new_user_session_path end def oauth diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 133445d3d05..18ec63a62a2 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -13,7 +13,7 @@ module Gitlab end def persisted? - gl_user.persisted? + gl_user.try(:persisted?) end def new? @@ -21,10 +21,12 @@ module Gitlab end def valid? - gl_user.valid? + gl_user.try(:valid?) end def save + unauthorized_to_create unless gl_user + gl_user.save! log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" gl_user.block if needs_blocking? @@ -36,7 +38,12 @@ module Gitlab end def gl_user - @user ||= find_by_uid_and_provider || build_new_user + @user ||= find_by_uid_and_provider + + if Gitlab.config.omniauth.allow_single_sign_on + @user ||= build_new_user + end + @user end protected @@ -77,6 +84,10 @@ module Gitlab def model ::User end + + def raise_unauthorized_to_create + raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") + end end end end diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb index 7c7d6babbfd..e004d6edfab 100644 --- a/spec/lib/gitlab/oauth/user_spec.rb +++ b/spec/lib/gitlab/oauth/user_spec.rb @@ -31,17 +31,8 @@ describe Gitlab::OAuth::User do describe :save do let(:provider) { 'twitter' } - it "creates a user from Omniauth" do - oauth_user.save - - expect(gl_user).to be_valid - expect(gl_user.extern_uid).to eql uid - expect(gl_user.provider).to eql 'twitter' - end - end - - context "twitter" do - let(:provider) { 'twitter' } + context "with allow_single_sign_on enabled" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } it "creates a user from Omniauth" do oauth_user.save @@ -51,5 +42,11 @@ describe Gitlab::OAuth::User do expect(gl_user.provider).to eql 'twitter' end end + + context "with allow_single_sign_on disabled (Default)" do + it "throws an error" do + expect{ oauth_user.save }.to raise_error StandardError + end + end end end From 077fc683faa85f9abe4cc40ea1c7877e6b6c1f2a Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Thu, 16 Oct 2014 14:34:03 -0700 Subject: [PATCH 043/408] simplify DHE parameter generation --- lib/support/nginx/gitlab-ssl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index d3fb467ef27..fd4f93c2f92 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -91,8 +91,7 @@ server { # resolver_timeout 10s; ## [Optional] Generate a stronger DHE parameter: - ## cd /etc/ssl/certs - ## sudo openssl dhparam -out dhparam.pem 4096 + ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 ## # ssl_dhparam /etc/ssl/certs/dhparam.pem; From 966f68b33e1f15f08e383ec68346ed1bd690b59b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 17 Oct 2014 13:15:59 +0300 Subject: [PATCH 044/408] Refactor error message a bit Signed-off-by: Dmitriy Zaporozhets --- app/controllers/omniauth_callbacks_controller.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 58d1e37f655..bd4b310fcbf 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -54,8 +54,16 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Only allow properly saved users to login. if @user.persisted? && @user.valid? sign_in_and_redirect(@user.gl_user) - else @user.gl_user.errors.any? - error_message = @user.gl_user.errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") + else + error_message = + if @user.gl_user.errors.any? + @user.gl_user.errors.map do |attribute, message| + "#{attribute} #{message}" + end.join(", ") + else + '' + end + redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return end end From 4da88dbb2d8d8c1201a18829e22e71837e39736e Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 16 Oct 2014 19:45:33 +0300 Subject: [PATCH 045/408] documents updated --- VERSION | 2 +- doc/install/installation.md | 8 +- ...-or-7.x-to-7.3.md => 6.x-or-7.x-to-7.4.md} | 20 +-- doc/update/7.3-to-7.4.md | 148 +++++++++++++++++- 4 files changed, 159 insertions(+), 19 deletions(-) rename doc/update/{6.x-or-7.x-to-7.3.md => 6.x-or-7.x-to-7.4.md} (93%) diff --git a/VERSION b/VERSION index 8b258729874..7b65f139cb2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.0-pre +7.4.0.rc1 diff --git a/doc/install/installation.md b/doc/install/installation.md index 821420e8633..7a39f2eec9f 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -74,8 +74,8 @@ Is the system packaged Git too old? Remove it and compile from source. # Download and compile from source cd /tmp - curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz - cd git-2.0.0/ + curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz + cd git-2.1.2/ make prefix=/usr/local all # Install into /usr/local/bin @@ -165,9 +165,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-3-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-4-stable gitlab -**Note:** You can change `7-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `7-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/update/6.x-or-7.x-to-7.3.md b/doc/update/6.x-or-7.x-to-7.4.md similarity index 93% rename from doc/update/6.x-or-7.x-to-7.3.md rename to doc/update/6.x-or-7.x-to-7.4.md index fe3530ef9c1..e923060223b 100644 --- a/doc/update/6.x-or-7.x-to-7.3.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -1,6 +1,6 @@ -# From 6.x or 7.x to 7.3 +# From 6.x or 7.x to 7.4 -This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3. +This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4. ## Global issue numbers @@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut For GitLab Community Edition: ```bash -sudo -u git -H git checkout 7-3-stable +sudo -u git -H git checkout 7-4-stable ``` OR @@ -78,7 +78,7 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout 7-3-stable-ee +sudo -u git -H git checkout 7-4-stable-ee ``` ## 4. Install additional packages @@ -152,14 +152,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab TIP: to see what changed in `gitlab.yml.example` in this release use next command: ``` -git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example ``` -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings. -* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.0/config.yml.example but with your settings. -* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings. * Copy rack attack middleware config ```bash diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index ba3be5e53b6..193f44bb67e 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -1,14 +1,135 @@ # From 7.3 to 7.4 -## GitLab 7.4 has not been released yet! +### 0. Backup -This document currently just serves as a place to keep track of updates that will be needed for the 7.4 update. +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` -## Update config files +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Get latest code + +```bash +cd /home/git/gitlab +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 7-4-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-4-stable-ee +``` + +### 3. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + + +### 4. Configure Redis to use sockets + + # Configure redis to use sockets + sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig + # Disable Redis listening on TCP by setting 'port' to 0 + sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf + # Enable Redis socket for default Debian / Ubuntu path + echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf + # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). + sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf + # Activate the changes to redis.conf + sudo service redis-server restart + # Add git to the redis group + sudo usermod -aG redis git + + # Configure Redis connection settings + sudo -u git -H cp config/resque.yml.example config/resque.yml + # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration + sudo -u git -H editor config/resque.yml + + # Configure gitlab-shell to use Redis sockets + sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml + +### 5. Update config files + +#### New configuration options for gitlab.yml + +There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml. + +``` +git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example +``` + +#### Change timeout for unicorn + +``` +# config/unicorn.rb +timeout 60 +``` + +#### Change nginx https settings + +* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting + +#### Update database.yml config file(for mysql only) if needed (basically it is required for old gitlab installations) * Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) -## Optional optimizations for GitLab setups with MySQL databases + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +### 8. Update OmniAuth configuration + +When using Google omniauth login, changes of the Google account required. +Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). +More details can be found at the [integration documentation](../integration/google.md). + +### 9. Optional optimizations for GitLab setups with MySQL databases Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. @@ -75,3 +196,22 @@ mysql> \q # Set production -> password: the password your replaced $password with earlier sudo -u git -H editor /home/git/gitlab/config/database.yml ``` + + +## Things went south? Revert to previous version (7.3) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above. + + + + From f8cdd62e2269b6c8243b6d1bc9bf73dc7dd1b535 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 17 Oct 2014 14:08:02 +0300 Subject: [PATCH 046/408] Fix account existing blocking Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/oauth/user.rb | 32 +++++++++---- spec/lib/gitlab/oauth/user_spec.rb | 76 ++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 18ec63a62a2..47f62153a50 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -17,7 +17,7 @@ module Gitlab end def new? - !gl_user.persisted? + !persisted? end def valid? @@ -27,10 +27,14 @@ module Gitlab def save unauthorized_to_create unless gl_user - gl_user.save! - log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" - gl_user.block if needs_blocking? + if needs_blocking? + gl_user.save! + gl_user.block + else + gl_user.save! + end + log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" gl_user rescue ActiveRecord::RecordInvalid => e log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}" @@ -40,13 +44,27 @@ module Gitlab def gl_user @user ||= find_by_uid_and_provider - if Gitlab.config.omniauth.allow_single_sign_on + if signup_enabled? @user ||= build_new_user end + @user end protected + + def needs_blocking? + new? && block_after_signup? + end + + def signup_enabled? + Gitlab.config.omniauth.allow_single_sign_on + end + + def block_after_signup? + Gitlab.config.omniauth.block_auto_created_users + end + def auth_hash=(auth_hash) @auth_hash = AuthHash.new(auth_hash) end @@ -77,10 +95,6 @@ module Gitlab Gitlab::AppLogger end - def needs_blocking? - Gitlab.config.omniauth['block_auto_created_users'] - end - def model ::User end diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb index e004d6edfab..8a83a1b2588 100644 --- a/spec/lib/gitlab/oauth/user_spec.rb +++ b/spec/lib/gitlab/oauth/user_spec.rb @@ -31,21 +31,77 @@ describe Gitlab::OAuth::User do describe :save do let(:provider) { 'twitter' } - context "with allow_single_sign_on enabled" do - before { Gitlab.config.omniauth.stub allow_single_sign_on: true } + describe 'signup' do + context "with allow_single_sign_on enabled" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } - it "creates a user from Omniauth" do - oauth_user.save + it "creates a user from Omniauth" do + oauth_user.save - expect(gl_user).to be_valid - expect(gl_user.extern_uid).to eql uid - expect(gl_user.provider).to eql 'twitter' + expect(gl_user).to be_valid + expect(gl_user.extern_uid).to eql uid + expect(gl_user.provider).to eql 'twitter' + end + end + + context "with allow_single_sign_on disabled (Default)" do + it "throws an error" do + expect{ oauth_user.save }.to raise_error StandardError + end end end - context "with allow_single_sign_on disabled (Default)" do - it "throws an error" do - expect{ oauth_user.save }.to raise_error StandardError + describe 'blocking' do + let(:provider) { 'twitter' } + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } + + context 'signup' do + context 'dont block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: false } + + it do + oauth_user.save + gl_user.should be_valid + gl_user.should_not be_blocked + end + end + + context 'block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: true } + + it do + oauth_user.save + gl_user.should be_valid + gl_user.should be_blocked + end + end + end + + context 'sign-in' do + before do + oauth_user.save + oauth_user.gl_user.activate + end + + context 'dont block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: false } + + it do + oauth_user.save + gl_user.should be_valid + gl_user.should_not be_blocked + end + end + + context 'block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: true } + + it do + oauth_user.save + gl_user.should be_valid + gl_user.should_not be_blocked + end + end end end end From 6cff68fb30ef63af127a27293688e4fc40a9cef9 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Fri, 17 Oct 2014 16:27:44 +0200 Subject: [PATCH 047/408] Link to trending public projects so more relevant projects are shown to new users. --- app/views/dashboard/_zero_authorized_projects.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml index 711e607f0bc..5d133cd8285 100644 --- a/app/views/dashboard/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/_zero_authorized_projects.html.haml @@ -46,5 +46,5 @@ %br Public projects are an easy way to allow everyone to have read-only access. .link_holder - = link_to explore_projects_path, class: "btn btn-new" do + = link_to trending_explore_projects_path, class: "btn btn-new" do Browse public projects » From d1c3864778d06b8e47b478caf4ff6f61c573151e Mon Sep 17 00:00:00 2001 From: Jan-Willem van der Meer Date: Fri, 17 Oct 2014 18:03:34 +0200 Subject: [PATCH 048/408] Prevent redeclaration of LDAP strategy --- config/initializers/7_omniauth.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb index b8ac87fbd5a..18759f0cfb0 100644 --- a/config/initializers/7_omniauth.rb +++ b/config/initializers/7_omniauth.rb @@ -1,7 +1,8 @@ if Gitlab::LDAP::Config.enabled? module OmniAuth::Strategies server = Gitlab.config.ldap.servers.values.first - const_set(server['provider_class'], Class.new(LDAP)) + klass = server['provider_class'] + const_set(klass, Class.new(LDAP)) unless klass == 'LDAP' end OmniauthCallbacksController.class_eval do From 61d9d4e2eb2a51243276422d901b158abbb2f0da Mon Sep 17 00:00:00 2001 From: Jan-Willem van der Meer Date: Fri, 17 Oct 2014 18:08:26 +0200 Subject: [PATCH 049/408] Default the LDAP server label to LDAP --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 7e7c91ced77..88cbaefea7d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -61,7 +61,6 @@ Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil? if Settings.ldap['enabled'] || Rails.env.test? if Settings.ldap['host'].present? server = Settings.ldap.except('sync_time') - server['label'] = 'LDAP' server['provider_name'] = 'ldap' Settings.ldap['servers'] = { 'ldap' => server @@ -69,6 +68,7 @@ if Settings.ldap['enabled'] || Rails.env.test? end Settings.ldap['servers'].each do |key, server| + server['label'] ||= 'LDAP' server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil? server['active_directory'] = true if server['active_directory'].nil? server['provider_name'] ||= "ldap#{key}".downcase From 6797c59e6e5ceac9f087a63b62fbc148e7a7d5b9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 17 Oct 2014 20:27:30 +0300 Subject: [PATCH 050/408] Improve visual detection of CI status Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/sections/merge_requests.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 22f20a7df4d..ec844cc00b0 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -113,30 +113,36 @@ font-size: 15px; border-bottom: 1px solid #BBB; color: #777; + background-color: #F5F5F5; &.ci-success { color: $bg_success; border-color: $border_success; + background-color: #F1FAF1; } &.ci-pending { color: #548; border-color: #548; + background-color: #F4F1FA; } &.ci-running { color: $bg_warning; border-color: $border_warning; + background-color: #FAF5F1; } &.ci-failed { color: $bg_danger; border-color: $border_danger; + background-color: #FAF1F1; } &.ci-error { color: $bg_danger; border-color: $border_danger; + background-color: #FAF1F1; } } From 9e6e0171ce4262f10d45ee828773f01fa372cf45 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 17 Oct 2014 20:41:27 +0300 Subject: [PATCH 051/408] Increase participants block margin Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/sections/issues.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index a7fa715d2e0..ebf8a6125c7 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -75,7 +75,7 @@ } .participants { - margin-bottom: 10px; + margin-bottom: 20px; } .issues_bulk_update { From f2764e566e80b3618f5b8508877b9d52021e9f83 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Sat, 18 Oct 2014 00:04:21 -0500 Subject: [PATCH 052/408] Use actual GitLab URL for test assertion Assert the full GitLab root URL, including the port, instead of using a regexp to tolerate whatever port is returned. --- spec/helpers/gitlab_markdown_helper_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index e933b0842af..3c636b747d1 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -594,7 +594,9 @@ describe GitlabMarkdownHelper do end it "should generate absolute urls for emoji" do - markdown(':smile:').should match(%r{src="http://localhost(:\d+)?/assets/emoji/smile.png}) + markdown(':smile:').should( + include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/smile.png)) + ) end it "should generate absolute urls for emoji if relative url is present" do From 768da57fe4aeb9fddc96620d5a91d5a2974e438d Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 18 Oct 2014 01:28:50 -0700 Subject: [PATCH 053/408] clarify when database.yml needs to be updated Clarify that database.yml should be updated if you are running a MySQL Database. Remove wording that de-emphasises importance of update. --- doc/update/7.3-to-7.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 193f44bb67e..c50eb01d279 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -101,7 +101,7 @@ timeout 60 * HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting -#### Update database.yml config file(for mysql only) if needed (basically it is required for old gitlab installations) +#### MySQL Databases: Update database.yml config file * Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) From 35b1a036d79382c9311d4c6fac0cdbefb067e940 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 18 Oct 2014 01:33:51 -0700 Subject: [PATCH 054/408] stop gitlab before mysql optimizations, run checks Update MySQL optimizations to reflect doing updates after GitLab has already been started back up. --- doc/update/7.3-to-7.4.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 193f44bb67e..e8d72bf2310 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -134,6 +134,9 @@ More details can be found at the [integration documentation](../integration/goog Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. ``` +# Stop GitLab +sudo service gitlab stop + # Secure your MySQL installation (added in GitLab 6.2) sudo mysql_secure_installation @@ -195,6 +198,9 @@ mysql> \q # Set production -> username: git # Set production -> password: the password your replaced $password with earlier sudo -u git -H editor /home/git/gitlab/config/database.yml + +# Run thorough check +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production ``` From 76e2ae8148e8fd72048cc1c9d57e5b5f5452aae9 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 18 Oct 2014 01:39:01 -0700 Subject: [PATCH 055/408] actually give command necessary to update unicorn Give command to update unicorn.rb rather than just say you need to do it. --- doc/update/7.3-to-7.4.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 193f44bb67e..045e611b3e9 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -93,8 +93,8 @@ git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gi #### Change timeout for unicorn ``` -# config/unicorn.rb -timeout 60 +# set timeout to 60 +sudo -u git -H editor config/unicorn.rb ``` #### Change nginx https settings From 9a92e53f622f29388851f25abe191dad38e35516 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 18 Oct 2014 01:49:59 -0700 Subject: [PATCH 056/408] stop gitlab before backup Stopping gitlab before backup ensures that backup has everything before upgrade incase something goes wrong. Also remove extra cd. --- doc/update/7.3-to-7.4.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 193f44bb67e..6ef91913bbf 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -1,22 +1,18 @@ # From 7.3 to 7.4 -### 0. Backup +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup ```bash cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production -``` - -### 1. Stop server - -```bash -sudo service gitlab stop -``` ### 2. Get latest code ```bash -cd /home/git/gitlab sudo -u git -H git fetch --all sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically ``` From 3e6b284bd0cf597f6446e1990a53acb806fb359a Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 18 Oct 2014 01:51:35 -0700 Subject: [PATCH 057/408] stop gitlab before backup --- doc/update/6.x-or-7.x-to-7.4.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.4.md index e923060223b..c332e5fe267 100644 --- a/doc/update/6.x-or-7.x-to-7.4.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -13,7 +13,11 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are no longer allowed however so those will be removed from your tags during the database migrations for GitLab 7.2. -## 0. Backup +## 0. Stop server + + sudo service gitlab stop + +## 1. Backup It's useful to make a backup just in case things go south: (With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) @@ -23,10 +27,6 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` -## 1. Stop server - - sudo service gitlab stop - ## 2. Update Ruby If you are still using Ruby 1.9.3 or below, you will need to update Ruby. From 0ff6105589960bc617b8e974874f1f22a5841ad0 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 18 Oct 2014 02:08:01 -0700 Subject: [PATCH 058/408] add missing configure Redis to use sockets Add details from 7.2-to-7.3.md. Replaces https://github.com/gitlabhq/gitlabhq/pull/8047. --- doc/update/6.x-or-7.x-to-7.4.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.4.md index e923060223b..4b7ed03f49e 100644 --- a/doc/update/6.x-or-7.x-to-7.4.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -99,6 +99,8 @@ sudo apt-get install pkg-config cmake sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf # Enable Redis socket for default Debian / Ubuntu path echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf + # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). + sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf # Activate the changes to redis.conf sudo service redis-server restart # Add git to the redis group From 4880b91ff1be9dda28dfe2b3feb0cd746cadf73e Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 18 Oct 2014 02:13:15 -0700 Subject: [PATCH 059/408] add optimizations for mysql to 6.x->7.4 guide Add mysql optimizations from 7.3-to-7.4.md. --- doc/update/6.x-or-7.x-to-7.4.md | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.4.md index e923060223b..5d2264c108b 100644 --- a/doc/update/6.x-or-7.x-to-7.4.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -196,6 +196,76 @@ When using Google omniauth login, changes of the Google account required. Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). More details can be found at the [integration documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/google.md). +## 12. Optional optimizations for GitLab setups with MySQL databases + +Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. + +``` +# Stop GitLab +sudo service gitlab stop + +# Secure your MySQL installation (added in GitLab 6.2) +sudo mysql_secure_installation + +# Login to MySQL +mysql -u root -p + +# do not type the 'mysql>', this is part of the prompt + +# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8) +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all outputed SQL statements + +# Convert all tables to correct character set +SET foreign_key_checks = 0; +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all outputed SQL statements + +# turn foreign key checks back on +SET foreign_key_checks = 1; + +# Find MySQL users +mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%'; + +# If git user exists and gitlab user does not exist +# you are done with the database cleanup tasks +mysql> \q + +# If both users exist skip to Delete gitlab user + +# Create new user for GitLab (changed in GitLab 6.4) +# change $password in the command below to a real password you pick +mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; + +# Grant the git user necessary permissions on the database +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Delete the old gitlab user +mysql> DELETE FROM mysql.user WHERE user='gitlab'; + +# Quit the database session +mysql> \q + +# Try connecting to the new database with the new user +sudo -u git -H mysql -u git -p -D gitlabhq_production + +# Type the password you replaced $password with earlier + +# You should now see a 'mysql>' prompt + +# Quit the database session +mysql> \q + +# Update database configuration details +# See config/database.yml.mysql for latest recommended configuration details +# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8) +# Set production -> pool: 10 (updated in GitLab 5.3) +# Set production -> username: git +# Set production -> password: the password your replaced $password with earlier +sudo -u git -H editor /home/git/gitlab/config/database.yml + ## Things went south? Revert to previous version (6.0) ### 1. Revert the code to the previous version From 290104219652592a221bfe100a7bbbbee69390fb Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sat, 18 Oct 2014 22:36:00 +0200 Subject: [PATCH 060/408] Replace match with end_with: more readable, faster --- lib/tasks/gitlab/shell.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index a8f26a7c029..c3d1aa0125d 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -11,7 +11,7 @@ namespace :gitlab do home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home gitlab_url = Settings.gitlab.url # gitlab-shell requires a / at the end of the url - gitlab_url += "/" unless gitlab_url.match(/\/$/) + gitlab_url += '/' unless gitlab_url.end_with?('/') repos_path = Gitlab.config.gitlab_shell.repos_path target_dir = Gitlab.config.gitlab_shell.path From a3623a9691b4cba19338fb6fe1c7acf64d5e8e70 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sat, 18 Oct 2014 22:49:56 +0200 Subject: [PATCH 061/408] Use argument list for sh instead of string Faster, more portable and less error prone since no shell expansion. --- lib/tasks/gitlab/shell.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index a8f26a7c029..21c3c698245 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -17,7 +17,7 @@ namespace :gitlab do # Clone if needed unless File.directory?(target_dir) - sh "git clone '#{args.repo}' '#{target_dir}'" + sh(*%W(git clone #{args.repo} #{target_dir})) end # Make sure we're on the right tag From b66a1527356d808f418bab273f821c83a4365c90 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sun, 19 Oct 2014 10:50:23 +0200 Subject: [PATCH 062/408] Factor abilities methods in app controller, user model and services. --- app/controllers/application_controller.rb | 7 +------ app/controllers/explore/groups_controller.rb | 3 +-- app/controllers/explore/projects_controller.rb | 3 +-- app/models/ability.rb | 8 ++++++++ app/models/user.rb | 6 +----- app/services/base_service.rb | 6 +----- 6 files changed, 13 insertions(+), 20 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 13d8d2a3e0a..f50889d62b9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,7 +5,6 @@ class ApplicationController < ActionController::Base before_filter :authenticate_user! before_filter :reject_blocked! before_filter :check_password_expiration - before_filter :add_abilities before_filter :ldap_security_check before_filter :dev_tools if Rails.env == 'development' before_filter :default_headers @@ -73,7 +72,7 @@ class ApplicationController < ActionController::Base end def abilities - @abilities ||= Six.new + Ability.abilities end def can?(object, action, subject) @@ -111,10 +110,6 @@ class ApplicationController < ActionController::Base nil end - def add_abilities - abilities << Ability - end - def authorize_project!(action) return access_denied! unless can?(current_user, action, project) end diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index f8e1a31e0b3..ada7031fea4 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,7 +1,6 @@ class Explore::GroupsController < ApplicationController skip_before_filter :authenticate_user!, - :reject_blocked, :set_current_user_for_observers, - :add_abilities + :reject_blocked, :set_current_user_for_observers layout "explore" diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index b6fa8b7e387..d75fd8e72fa 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,7 +1,6 @@ class Explore::ProjectsController < ApplicationController skip_before_filter :authenticate_user!, - :reject_blocked, - :add_abilities + :reject_blocked layout 'explore' diff --git a/app/models/ability.rb b/app/models/ability.rb index e155abc1449..97a72bf3635 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -262,5 +262,13 @@ class Ability end rules end + + def abilities + @abilities ||= begin + abilities = Six.new + abilities << self + abilities + end + end end end diff --git a/app/models/user.rb b/app/models/user.rb index 42faea0070e..154cc0f3e16 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -330,11 +330,7 @@ class User < ActiveRecord::Base end def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end + Ability.abilities end def can_select_namespace? diff --git a/app/services/base_service.rb b/app/services/base_service.rb index ed286c04095..0d46eeaa18f 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -6,11 +6,7 @@ class BaseService end def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end + Ability.abilities end def can?(object, action, subject) From b011052ce7ae714e762a611bad1b9e8866fdf7cd Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sun, 19 Oct 2014 11:46:57 +0200 Subject: [PATCH 063/408] Remove unused authenticate_user from project#show Redundant with the authorize_read_project! filter --- app/controllers/projects_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b3380a6ff23..42ab6d3d133 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -53,8 +53,6 @@ class ProjectsController < ApplicationController return end - return authenticate_user! unless @project.public? || current_user - limit = (params[:limit] || 20).to_i @events = @project.events.recent @events = event_filter.apply_filter(@events) From f808ecf11e5e5664b9617112691efece5ed01980 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sun, 19 Oct 2014 16:24:22 +0200 Subject: [PATCH 064/408] DRY mentioned in magic note constant --- app/models/note.rb | 16 ++++++++++++++-- app/services/notification_service.rb | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 6f1b1a4da94..f0ed7580b4c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -80,7 +80,7 @@ class Note < ActiveRecord::Base note_options = { project: project, author: author, - note: "_mentioned in #{gfm_reference}_", + note: cross_reference_note_content(gfm_reference), system: true } @@ -174,7 +174,7 @@ class Note < ActiveRecord::Base where(noteable_id: noteable.id) end - notes.where('note like ?', "_mentioned in #{gfm_reference}_"). + notes.where('note like ?', cross_reference_note_content(gfm_reference)). system.any? end @@ -182,8 +182,16 @@ class Note < ActiveRecord::Base where("note like :query", query: "%#{query}%") end + def cross_reference_note_prefix + '_mentioned in ' + end + private + def cross_reference_note_content(gfm_reference) + cross_reference_note_prefix + "#{gfm_reference}_" + end + # Prepend the mentioner's namespaced project path to the GFM reference for # cross-project references. For same-project references, return the # unmodified GFM reference. @@ -249,6 +257,10 @@ class Note < ActiveRecord::Base nil end + def cross_reference? + note.start_with?(self.class.cross_reference_note_prefix) + end + def find_diff return nil unless noteable && noteable.diffs.present? diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index fe39f83b400..36781314278 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -119,7 +119,7 @@ class NotificationService # ignore gitlab service messages return true if note.note =~ /\A_Status changed to closed_/ - return true if note.note =~ /\A_mentioned in / && note.system == true + return true if note.cross_reference? && note.system == true opts = { noteable_type: note.noteable_type, project_id: note.project_id } From 6a73b76c5f2e1559ba771e5910852ac4e1283b58 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sun, 19 Oct 2014 23:02:19 +0200 Subject: [PATCH 065/408] Remove param[:project_id] at admin controller The route never passes that parameter to the helpers. --- app/controllers/admin/projects_controller.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 2f0d344802f..bdb11e9bee5 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -31,9 +31,7 @@ class Admin::ProjectsController < Admin::ApplicationController protected def project - id = params[:project_id] || params[:id] - - @project = Project.find_with_namespace(id) + @project = Project.find_with_namespace(params[:id]) @project || render_404 end From 9e1b97ad99b239ace4a9383ef9d2bf0855c0dfd7 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sun, 19 Oct 2014 23:20:55 +0200 Subject: [PATCH 066/408] Use @project on controllers, don't call method Also memoize the method to ensure that other methods in ApplicationController that rely on it can call it efficiently. --- app/controllers/admin/projects_controller.rb | 4 +- app/controllers/application_controller.rb | 43 ++++++++++--------- app/controllers/projects/commit_controller.rb | 12 +++--- .../projects/deploy_keys_controller.rb | 2 +- .../projects/team_members_controller.rb | 8 ++-- app/controllers/projects_controller.rb | 12 +++--- 6 files changed, 42 insertions(+), 39 deletions(-) diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 2f0d344802f..51193b91d20 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -38,10 +38,10 @@ class Admin::ProjectsController < Admin::ApplicationController end def group - @group ||= project.group + @group ||= @project.group end def repository - @repository ||= project.repository + @repository ||= @project.repository end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 13d8d2a3e0a..955f3a14af5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -81,28 +81,31 @@ class ApplicationController < ActionController::Base end def project - id = params[:project_id] || params[:id] + unless @project + id = params[:project_id] || params[:id] - # Redirect from - # localhost/group/project.git - # to - # localhost/group/project - # - if id =~ /\.git\Z/ - redirect_to request.original_url.gsub(/\.git\Z/, '') and return - end - - @project = Project.find_with_namespace(id) - - if @project and can?(current_user, :read_project, @project) - @project - elsif current_user.nil? - @project = nil - authenticate_user! - else - @project = nil - render_404 and return + # Redirect from + # localhost/group/project.git + # to + # localhost/group/project + # + if id =~ /\.git\Z/ + redirect_to request.original_url.gsub(/\.git\Z/, '') and return + end + + @project = Project.find_with_namespace(id) + + if @project and can?(current_user, :read_project, @project) + @project + elsif current_user.nil? + @project = nil + authenticate_user! + else + @project = nil + render_404 and return + end end + @project end def repository diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 66c67b661db..df09ee7ed9d 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -11,12 +11,12 @@ class Projects::CommitController < Projects::ApplicationController def show return git_not_found! unless @commit - @line_notes = project.notes.for_commit_id(commit.id).inline - @branches = project.repository.branch_names_contains(commit.id) + @line_notes = @project.notes.for_commit_id(commit.id).inline + @branches = @project.repository.branch_names_contains(commit.id) @diffs = @commit.diffs - @note = project.build_commit_note(commit) - @notes_count = project.notes.for_commit_id(commit.id).count - @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh + @note = @project.build_commit_note(commit) + @notes_count = @project.notes.for_commit_id(commit.id).count + @notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh @noteable = @commit @comments_allowed = @reply_allowed = true @comments_target = { @@ -32,6 +32,6 @@ class Projects::CommitController < Projects::ApplicationController end def commit - @commit ||= project.repository.commit(params[:id]) + @commit ||= @project.repository.commit(params[:id]) end end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index d20937ea8ea..024b9520d30 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def enable - project.deploy_keys << available_keys.find(params[:id]) + @project.deploy_keys << available_keys.find(params[:id]) redirect_to project_deploy_keys_path(@project) end diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb index 7bb799eba64..0791e6080fb 100644 --- a/app/controllers/projects/team_members_controller.rb +++ b/app/controllers/projects/team_members_controller.rb @@ -10,7 +10,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def new - @user_project_relation = project.project_members.new + @user_project_relation = @project.project_members.new end def create @@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def update - @user_project_relation = project.project_members.find_by(user_id: member) + @user_project_relation = @project.project_members.find_by(user_id: member) @user_project_relation.update_attributes(member_params) unless @user_project_relation.valid? @@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def destroy - @user_project_relation = project.project_members.find_by(user_id: member) + @user_project_relation = @project.project_members.find_by(user_id: member) @user_project_relation.destroy respond_to do |format| @@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController end def leave - project.project_members.find_by(user_id: current_user).destroy + @project.project_members.find_by(user_id: current_user).destroy respond_to do |format| format.html { redirect_to :back } diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b3380a6ff23..75495a3c3a9 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -76,7 +76,7 @@ class ProjectsController < ApplicationController end def import - if project.import_finished? + if @project.import_finished? redirect_to @project return end @@ -98,7 +98,7 @@ class ProjectsController < ApplicationController end def destroy - return access_denied! unless can?(current_user, :remove_project, project) + return access_denied! unless can?(current_user, :remove_project, @project) ::Projects::DestroyService.new(@project, current_user, {}).execute @@ -148,8 +148,8 @@ class ProjectsController < ApplicationController end def archive - return access_denied! unless can?(current_user, :archive_project, project) - project.archive! + return access_denied! unless can?(current_user, :archive_project, @project) + @project.archive! respond_to do |format| format.html { redirect_to @project } @@ -157,8 +157,8 @@ class ProjectsController < ApplicationController end def unarchive - return access_denied! unless can?(current_user, :archive_project, project) - project.unarchive! + return access_denied! unless can?(current_user, :archive_project, @project) + @project.unarchive! respond_to do |format| format.html { redirect_to @project } From 8ad1330b6a8648406bcd392ad5884498a25fbceb Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 20 Oct 2014 10:52:29 +0200 Subject: [PATCH 067/408] Ask the wiki repo, not Gollum, if it's empty We need to skip empty repositories when creating a backup. Before this change, we were asking gollum-lib if the wiki contains any _pages_. Now we ask gitlab_git if the repository contains _files_. This should resolve gollum_lib Grit timeouts in the backup script. --- lib/backup/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 4e99d4bbe5c..380beac708d 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -30,7 +30,7 @@ module Backup if File.exists?(path_to_repo(wiki)) print " * #{wiki.path_with_namespace} ... " - if wiki.empty? + if wiki.repository.empty? puts " [SKIPPED]".cyan else output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)) From f50c0e5af11c07b637c84d95622a315b71eebe97 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 Oct 2014 12:59:16 +0300 Subject: [PATCH 068/408] Fix group user removal from admin area Signed-off-by: Dmitriy Zaporozhets --- app/views/admin/groups/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index c1a9214b77a..1da6e4c5f1a 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -80,7 +80,7 @@ = link_to user.name, admin_user_path(user) %span.pull-right.light = member.human_access - = link_to group_group_members_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.fa.fa-minus.fa-inverse .panel-footer = paginate @members, param_name: 'members_page', theme: 'gitlab' From 2064a147249ab5984d90980bb9ce44f788a9b941 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 Oct 2014 13:18:07 +0300 Subject: [PATCH 069/408] Add tests for remove group member feature in admin area Signed-off-by: Dmitriy Zaporozhets --- .../groups/group_members_controller.rb | 1 + app/views/admin/groups/show.html.haml | 2 +- features/admin/groups.feature | 7 ++++++ features/steps/admin/groups.rb | 23 +++++++++++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 63c05d4f33b..ca88d033878 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController def destroy @users_group = @group.group_members.find(params[:id]) + if can?(current_user, :destroy, @users_group) # May fail if last owner. @users_group.destroy respond_to do |format| diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 1da6e4c5f1a..4494acc4842 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -74,7 +74,7 @@ %ul.well-list.group-users-list - @members.each do |member| - user = member.user - %li{class: dom_class(user)} + %li{class: dom_class(member), id: dom_id(user)} .list-item-name %strong = link_to user.name, admin_user_path(user) diff --git a/features/admin/groups.feature b/features/admin/groups.feature index 1a465c1be55..aa365a6ea1a 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -20,3 +20,10 @@ Feature: Admin Groups When I visit admin group page When I select user "John Doe" from user list as "Reporter" Then I should see "John Doe" in team list in every project as "Reporter" + + @javascript + Scenario: Remove user from group + Given we have user "John Doe" in group + When I visit admin group page + And I remove user "John Doe" from group + Then I should not see "John Doe" in team list diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 4f0ba05606d..d69a87cd07e 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -37,8 +37,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end When 'I select user "John Doe" from user list as "Reporter"' do - user = User.find_by(name: "John Doe") - select2(user.id, from: "#user_ids", multiple: true) + select2(user_john.id, from: "#user_ids", multiple: true) within "#new_team_member" do select "Reporter", from: "access_level" end @@ -58,9 +57,29 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end end + step 'we have user "John Doe" in group' do + current_group.add_user(user_john, Gitlab::Access::REPORTER) + end + + step 'I remove user "John Doe" from group' do + within "#user_#{user_john.id}" do + click_link 'Remove user from group' + end + end + + step 'I should not see "John Doe" in team list' do + within ".group-users-list" do + page.should_not have_content "John Doe" + end + end + protected def current_group @group ||= Group.first end + + def user_john + @user_john ||= User.find_by(name: "John Doe") + end end From c0a5d043819d6ad52912e2ba68c7fcb12e237978 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 Oct 2014 13:51:02 +0300 Subject: [PATCH 070/408] 7.5.0 started Signed-off-by: Dmitriy Zaporozhets --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7b65f139cb2..027a8b7b332 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.0.rc1 +7.5.0.pre From 082d59d21f7f8857eca2715c1a58fbce30f9b92d Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 20 Oct 2014 10:52:29 +0200 Subject: [PATCH 071/408] Ask the wiki repo, not Gollum, if it's empty We need to skip empty repositories when creating a backup. Before this change, we were asking gollum-lib if the wiki contains any _pages_. Now we ask gitlab_git if the repository contains _files_. This should resolve gollum_lib Grit timeouts in the backup script. --- lib/backup/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 4e99d4bbe5c..380beac708d 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -30,7 +30,7 @@ module Backup if File.exists?(path_to_repo(wiki)) print " * #{wiki.path_with_namespace} ... " - if wiki.empty? + if wiki.repository.empty? puts " [SKIPPED]".cyan else output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)) From ebc0a7050afe71f5c30341dcf6b15da76b810408 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 Oct 2014 12:59:16 +0300 Subject: [PATCH 072/408] Fix group user removal from admin area Signed-off-by: Dmitriy Zaporozhets --- app/views/admin/groups/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index c1a9214b77a..1da6e4c5f1a 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -80,7 +80,7 @@ = link_to user.name, admin_user_path(user) %span.pull-right.light = member.human_access - = link_to group_group_members_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do + = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do %i.fa.fa-minus.fa-inverse .panel-footer = paginate @members, param_name: 'members_page', theme: 'gitlab' From 644fd232dbe827aaae46068119345c2344495239 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 Oct 2014 13:18:07 +0300 Subject: [PATCH 073/408] Add tests for remove group member feature in admin area Signed-off-by: Dmitriy Zaporozhets --- .../groups/group_members_controller.rb | 1 + app/views/admin/groups/show.html.haml | 2 +- features/admin/groups.feature | 7 ++++++ features/steps/admin/groups.rb | 23 +++++++++++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 63c05d4f33b..ca88d033878 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController def destroy @users_group = @group.group_members.find(params[:id]) + if can?(current_user, :destroy, @users_group) # May fail if last owner. @users_group.destroy respond_to do |format| diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 1da6e4c5f1a..4494acc4842 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -74,7 +74,7 @@ %ul.well-list.group-users-list - @members.each do |member| - user = member.user - %li{class: dom_class(user)} + %li{class: dom_class(member), id: dom_id(user)} .list-item-name %strong = link_to user.name, admin_user_path(user) diff --git a/features/admin/groups.feature b/features/admin/groups.feature index 1a465c1be55..aa365a6ea1a 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -20,3 +20,10 @@ Feature: Admin Groups When I visit admin group page When I select user "John Doe" from user list as "Reporter" Then I should see "John Doe" in team list in every project as "Reporter" + + @javascript + Scenario: Remove user from group + Given we have user "John Doe" in group + When I visit admin group page + And I remove user "John Doe" from group + Then I should not see "John Doe" in team list diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 4f0ba05606d..d69a87cd07e 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -37,8 +37,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end When 'I select user "John Doe" from user list as "Reporter"' do - user = User.find_by(name: "John Doe") - select2(user.id, from: "#user_ids", multiple: true) + select2(user_john.id, from: "#user_ids", multiple: true) within "#new_team_member" do select "Reporter", from: "access_level" end @@ -58,9 +57,29 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end end + step 'we have user "John Doe" in group' do + current_group.add_user(user_john, Gitlab::Access::REPORTER) + end + + step 'I remove user "John Doe" from group' do + within "#user_#{user_john.id}" do + click_link 'Remove user from group' + end + end + + step 'I should not see "John Doe" in team list' do + within ".group-users-list" do + page.should_not have_content "John Doe" + end + end + protected def current_group @group ||= Group.first end + + def user_john + @user_john ||= User.find_by(name: "John Doe") + end end From 49bd9812000626af71c1b7b00d9f998fcaca2a46 Mon Sep 17 00:00:00 2001 From: Jan-Willem van der Meer Date: Fri, 17 Oct 2014 18:03:34 +0200 Subject: [PATCH 074/408] Prevent redeclaration of LDAP strategy --- config/initializers/7_omniauth.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb index b8ac87fbd5a..18759f0cfb0 100644 --- a/config/initializers/7_omniauth.rb +++ b/config/initializers/7_omniauth.rb @@ -1,7 +1,8 @@ if Gitlab::LDAP::Config.enabled? module OmniAuth::Strategies server = Gitlab.config.ldap.servers.values.first - const_set(server['provider_class'], Class.new(LDAP)) + klass = server['provider_class'] + const_set(klass, Class.new(LDAP)) unless klass == 'LDAP' end OmniauthCallbacksController.class_eval do From 3d3726a026f465a441abd7438de85790d9d84d94 Mon Sep 17 00:00:00 2001 From: Jan-Willem van der Meer Date: Fri, 17 Oct 2014 18:08:26 +0200 Subject: [PATCH 075/408] Default the LDAP server label to LDAP --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 7e7c91ced77..88cbaefea7d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -61,7 +61,6 @@ Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil? if Settings.ldap['enabled'] || Rails.env.test? if Settings.ldap['host'].present? server = Settings.ldap.except('sync_time') - server['label'] = 'LDAP' server['provider_name'] = 'ldap' Settings.ldap['servers'] = { 'ldap' => server @@ -69,6 +68,7 @@ if Settings.ldap['enabled'] || Rails.env.test? end Settings.ldap['servers'].each do |key, server| + server['label'] ||= 'LDAP' server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil? server['active_directory'] = true if server['active_directory'].nil? server['provider_name'] ||= "ldap#{key}".downcase From 1768e3eccb91689405e411a3ebcb2622e16dcdd8 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 20 Oct 2014 16:18:45 +0200 Subject: [PATCH 076/408] Update the documentation for the LDAP user filter --- doc/integration/ldap.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index ee472ac3e3b..a89c2d38779 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -26,13 +26,20 @@ The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). ```ruby # For omnibus-gitlab gitlab_rails['ldap_user_filter'] = '(employeeType=developer)' +gitlab_rails['ldap_servers'] = YAML.load <<-EOS +main: + # snip... + user_filter: '(employeeType=developer)' +EOS ``` ```yaml # For installations from source production: ldap: - user_filter: '(employeeType=developer)' + servers: + main: + user_filter: '(employeeType=developer)' ``` Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax: From 0b78bd7a42a341bb227fb544b5b12abf8e152a41 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 20 Oct 2014 16:22:36 +0200 Subject: [PATCH 077/408] Keep the legacy LDAP syntax in the documentation --- doc/integration/ldap.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index a89c2d38779..869850d29d7 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -24,22 +24,31 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). ```ruby -# For omnibus-gitlab -gitlab_rails['ldap_user_filter'] = '(employeeType=developer)' +# For omnibus packages; new LDAP server syntax gitlab_rails['ldap_servers'] = YAML.load <<-EOS main: # snip... user_filter: '(employeeType=developer)' EOS + +# omnibus package; legacy syntax +gitlab_rails['ldap_user_filter'] = '(employeeType=developer)' ``` ```yaml -# For installations from source +# For installations from source; new LDAP server syntax production: ldap: servers: main: + # snip... user_filter: '(employeeType=developer)' + +# installations from source; legacy syntax +production: + ldap: + # snip... + user_filter: '(employeeType=developer)' ``` Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax: From 5ed7c20150928194306ad51263b6a9b7fb4b4cfd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 Oct 2014 17:22:39 +0300 Subject: [PATCH 078/408] Prevent 500 error when filter projects with push in admin area Signed-off-by: Dmitriy Zaporozhets --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 90d2649ba23..613f98ba44b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -173,7 +173,7 @@ class Project < ActiveRecord::Base end def with_push - includes(:events).where('events.action = ?', Event::PUSHED) + joins(:events).where('events.action = ?', Event::PUSHED) end def active From 46cdb931d8a48febd459cf932b6e7c7b626ec452 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 20 Oct 2014 16:41:28 +0200 Subject: [PATCH 079/408] Remove legacy LDAP configuration examples --- doc/integration/ldap.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index 869850d29d7..df72b17ab1a 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -30,9 +30,6 @@ main: # snip... user_filter: '(employeeType=developer)' EOS - -# omnibus package; legacy syntax -gitlab_rails['ldap_user_filter'] = '(employeeType=developer)' ``` ```yaml @@ -43,12 +40,6 @@ production: main: # snip... user_filter: '(employeeType=developer)' - -# installations from source; legacy syntax -production: - ldap: - # snip... - user_filter: '(employeeType=developer)' ``` Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax: From b1b6761e05de0b675e31fb227939fff36618a282 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 20 Oct 2014 16:41:37 +0200 Subject: [PATCH 080/408] Add LDAP configuration documentation --- doc/integration/ldap.md | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index df72b17ab1a..56b0d826adb 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -6,6 +6,95 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G GitLab user attributes such as nickname and email will be copied from the LDAP user entry. +## Configuring GitLab for LDAP integration + +To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`. +In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server. + +Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration. +The old LDAP integration syntax still works in GitLab 7.4. +If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab. + +```ruby +# For omnibus packages +gitlab_rails['ldap_enabled'] = true +gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below +main: # 'main' is the GitLab 'provider ID' of this LDAP server + ## label + # + # A human-friendly name for your LDAP server. It is OK to change the label later, + # for instance if you find out it is too large to fit on the web page. + # + # Example: 'Paris' or 'Acme, Ltd.' + label: 'LDAP' + + host: '_your_ldap_server' + port: 636 + uid: 'sAMAccountName' + method: 'ssl' # "tls" or "ssl" or "plain" + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' + + # This setting specifies if LDAP server is Active Directory LDAP server. + # For non AD servers it skips the AD specific queries. + # If your LDAP server is not AD, set this to false. + active_directory: true + + # If allow_username_or_email_login is enabled, GitLab will ignore everything + # after the first '@' in the LDAP username submitted by the user on login. + # + # Example: + # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials; + # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'. + # + # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to + # disable this setting, because the userPrincipalName contains an '@'. + allow_username_or_email_login: false + + # Base where we can search for users + # + # Ex. ou=People,dc=gitlab,dc=example + # + base: '' + + # Filter LDAP users + # + # Format: RFC 4515 http://tools.ietf.org/search/rfc4515 + # Ex. (employeeType=developer) + # + # Note: GitLab does not support omniauth-ldap's custom filter syntax. + # + user_filter: '' + +# GitLab EE only: add more LDAP servers +# Choose an ID made of a-z and 0-9 . This ID will be stored in the database +# so that GitLab can remember which LDAP server a user belongs to. +# uswest2: +# label: +# host: +# .... +EOS +``` + +If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`: + +``` +production: + # snip... + ldap: + enabled: false + servers: + main: # 'main' is the GitLab 'provider ID' of this LDAP server + ## label + # + # A human-friendly name for your LDAP server. It is OK to change the label later, + # for instance if you find out it is too large to fit on the web page. + # + # Example: 'Paris' or 'Acme, Ltd.' + label: 'LDAP' + # snip... +``` + ## Enabling LDAP sign-in for existing GitLab users When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user. From c0c8dccf2e36c269cfb26b31b86cf60d262c4843 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 20 Oct 2014 22:48:07 +0200 Subject: [PATCH 081/408] Export all coffee classes with @ --- app/assets/javascripts/activities.js.coffee | 4 +--- app/assets/javascripts/admin.js.coffee | 4 +--- app/assets/javascripts/blob.js.coffee | 5 +---- app/assets/javascripts/commit.js.coffee | 4 +--- app/assets/javascripts/commit/file.js.coffee | 4 +--- app/assets/javascripts/commit/image-file.js.coffee | 4 +--- app/assets/javascripts/commits.js.coffee | 4 +--- app/assets/javascripts/confirm_danger_modal.js.coffee | 4 +--- app/assets/javascripts/dashboard.js.coffee | 5 +---- app/assets/javascripts/diff.js.coffee | 5 +---- app/assets/javascripts/flash.js.coffee | 4 +--- app/assets/javascripts/groups.js.coffee | 4 +--- app/assets/javascripts/issue.js.coffee | 4 +--- app/assets/javascripts/labels.js.coffee | 4 +--- app/assets/javascripts/merge_request.js.coffee | 4 +--- app/assets/javascripts/milestone.js.coffee | 4 +--- app/assets/javascripts/notes.js.coffee | 6 +----- app/assets/javascripts/notes_votes.js.coffee | 4 +--- app/assets/javascripts/project.js.coffee | 5 +---- app/assets/javascripts/project_import.js.coffee | 4 +--- app/assets/javascripts/search_autocomplete.js.coffee | 4 +--- app/assets/javascripts/stat_graph.js.coffee | 2 +- app/assets/javascripts/stat_graph_contributors.js.coffee | 2 +- .../javascripts/stat_graph_contributors_graph.js.coffee | 6 +++--- app/assets/javascripts/team_members.js.coffee | 4 +--- app/assets/javascripts/tree.js.coffee | 4 +--- app/assets/javascripts/wikis.js.coffee | 5 +---- 27 files changed, 29 insertions(+), 84 deletions(-) diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee index fdefbfb92bd..4f76d8ce486 100644 --- a/app/assets/javascripts/activities.js.coffee +++ b/app/assets/javascripts/activities.js.coffee @@ -1,4 +1,4 @@ -class Activities +class @Activities constructor: -> Pager.init 20, true $(".event_filter_link").bind "click", (event) => @@ -27,5 +27,3 @@ class Activities event_filters.splice index, 1 $.cookie "event_filter", event_filters.join(","), { path: '/' } - -@Activities = Activities diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index a333eed87f2..bcb2e6df7c0 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -1,4 +1,4 @@ -class Admin +class @Admin constructor: -> $('input#user_force_random_password').on 'change', (elem) -> elems = $('#user_password, #user_password_confirmation') @@ -51,5 +51,3 @@ class Admin $('li.group_member').bind 'ajax:success', -> Turbolinks.visit(location.href) - -@Admin = Admin diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 9db919e5a62..a5f15f80c5c 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/app/assets/javascripts/blob.js.coffee @@ -1,4 +1,4 @@ -class BlobView +class @BlobView constructor: -> # handle multi-line select handleMultiSelect = (e) -> @@ -71,6 +71,3 @@ class BlobView # Highlight the correct lines when the hash part of the URL changes $(window).on("hashchange", highlightBlobLines) - - -@BlobView = BlobView diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee index 5f53439ca4b..0566e239191 100644 --- a/app/assets/javascripts/commit.js.coffee +++ b/app/assets/javascripts/commit.js.coffee @@ -1,6 +1,4 @@ -class Commit +class @Commit constructor: -> $('.files .diff-file').each -> new CommitFile(this) - -@Commit = Commit diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee index 4db9116a9de..83e793863b6 100644 --- a/app/assets/javascripts/commit/file.js.coffee +++ b/app/assets/javascripts/commit/file.js.coffee @@ -1,7 +1,5 @@ -class CommitFile +class @CommitFile constructor: (file) -> if $('.image', file).length new ImageFile(file) - -@CommitFile = CommitFile diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee index 607b85eb45c..9e5f49b1f69 100644 --- a/app/assets/javascripts/commit/image-file.js.coffee +++ b/app/assets/javascripts/commit/image-file.js.coffee @@ -1,4 +1,4 @@ -class ImageFile +class @ImageFile # Width where images must fits in, for 2-up this gets divided by 2 @availWidth = 900 @@ -124,5 +124,3 @@ class ImageFile else img.on 'load', => callback.call(this, domImg.naturalWidth, domImg.naturalHeight) - -@ImageFile = ImageFile diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index 784d7d20bb1..c183e78e513 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -1,4 +1,4 @@ -class CommitsList +class @CommitsList @data = ref: null limit: 0 @@ -53,5 +53,3 @@ class CommitsList @disable callback: => this.getOld() - -this.CommitsList = CommitsList diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee index 1687b7d961c..bb99edbd09e 100644 --- a/app/assets/javascripts/confirm_danger_modal.js.coffee +++ b/app/assets/javascripts/confirm_danger_modal.js.coffee @@ -1,4 +1,4 @@ -class ConfirmDangerModal +class @ConfirmDangerModal constructor: (form, text) -> @form = form $('.js-confirm-text').text(text || '') @@ -16,5 +16,3 @@ class ConfirmDangerModal $('.js-confirm-danger-submit').on 'click', => @form.submit() - -@ConfirmDangerModal = ConfirmDangerModal diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee index c4a0ccd9c2a..6ef5a539b8f 100644 --- a/app/assets/javascripts/dashboard.js.coffee +++ b/app/assets/javascripts/dashboard.js.coffee @@ -1,4 +1,4 @@ -class Dashboard +class @Dashboard constructor: -> @initSidebarTab() @@ -28,6 +28,3 @@ class Dashboard # show tab from cookie sidebar_filter = $.cookie(key) $("#" + sidebar_filter).tab('show') if sidebar_filter - - -@Dashboard = Dashboard diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee index dbe00c487dc..52b4208524f 100644 --- a/app/assets/javascripts/diff.js.coffee +++ b/app/assets/javascripts/diff.js.coffee @@ -1,4 +1,4 @@ -class Diff +class @Diff UNFOLD_COUNT = 20 constructor: -> $(document).on('click', '.js-unfold', (event) => @@ -41,6 +41,3 @@ class Diff lines = line.children().slice(0, 2) line_numbers = ($(l).attr('data-linenumber') for l in lines) (parseInt(line_number) for line_number in line_numbers) - - -@Diff = Diff diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index cf1a37eae3e..b39ab0c4475 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -1,4 +1,4 @@ -class Flash +class @Flash constructor: (message, type)-> flash = $(".flash-container") flash.html("") @@ -10,5 +10,3 @@ class Flash flash.click -> $(@).fadeOut() flash.show() - -@Flash = Flash diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index 4b1000f9a6a..90122044248 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -1,10 +1,8 @@ -class GroupMembers +class @GroupMembers constructor: -> $('li.group_member').bind 'ajax:success', -> $(this).fadeOut() -@GroupMembers = GroupMembers - $ -> # avatar $('.js-choose-group-avatar-button').bind "click", -> diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 0e2a2fa792a..597b4695a6d 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -1,4 +1,4 @@ -class Issue +class @Issue constructor: -> $('.edit-issue.inline-update input[type="submit"]').hide() $(".issue-box .inline-update").on "change", "select", -> @@ -15,5 +15,3 @@ class Issue "issue" updateTaskState ) - -@Issue = Issue diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee index d306ad64f5b..1bc8840f9ac 100644 --- a/app/assets/javascripts/labels.js.coffee +++ b/app/assets/javascripts/labels.js.coffee @@ -1,4 +1,4 @@ -class Labels +class @Labels constructor: -> form = $('.label-form') @setupLabelForm(form) @@ -31,5 +31,3 @@ class Labels # Notify the form, that color has changed $('.label-form').trigger('keyup') e.preventDefault() - -@Labels = Labels diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 9f99ff403f8..46e06424e5a 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -1,4 +1,4 @@ -class MergeRequest +class @MergeRequest constructor: (@opts) -> @initContextWidget() this.$el = $('.merge-request') @@ -132,5 +132,3 @@ class MergeRequest this.$('.automerge_widget').hide() this.$('.merge-in-progress').hide() this.$('.automerge_widget.already_cannot_be_merged').show() - -this.MergeRequest = MergeRequest diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee index ea01c318d4f..c42f31933d3 100644 --- a/app/assets/javascripts/milestone.js.coffee +++ b/app/assets/javascripts/milestone.js.coffee @@ -1,4 +1,4 @@ -class Milestone +class @Milestone @updateIssue: (li, issue_url, data) -> $.ajax type: "PUT" @@ -115,5 +115,3 @@ class Milestone Milestone.updateMergeRequest(ui.item, merge_request_url, data) ).disableSelection() - -@Milestone = Milestone diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ba8d7a9a2f5..978f83dd442 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -1,4 +1,4 @@ -class Notes +class @Notes @interval: null constructor: (notes_url, note_ids, last_fetched_at) -> @@ -514,7 +514,3 @@ class Notes else form.find('.js-note-target-reopen').text('Reopen') form.find('.js-note-target-close').text('Close') - - - -@Notes = Notes diff --git a/app/assets/javascripts/notes_votes.js.coffee b/app/assets/javascripts/notes_votes.js.coffee index b31eb9ac9de..65c149b7886 100644 --- a/app/assets/javascripts/notes_votes.js.coffee +++ b/app/assets/javascripts/notes_votes.js.coffee @@ -1,4 +1,4 @@ -class NotesVotes +class @NotesVotes updateVotes: -> votes = $("#votes .votes") notes = $("#notes-list .note .vote") @@ -18,5 +18,3 @@ class NotesVotes # replace vote numbers votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes) votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes) - -@NotesVotes = NotesVotes diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index f4a8a178e76..aba40742e5f 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,4 +1,4 @@ -class Project +class @Project constructor: -> $('.project-edit-container').on 'ajax:before', => $('.project-edit-container').hide() @@ -24,9 +24,6 @@ class Project else $('#project_issues_tracker_id').removeAttr('disabled') - -@Project = Project - $ -> # Git clone panel switcher scope = $ '.git-clone-holder' diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee index 7cf44da99fe..6633564a079 100644 --- a/app/assets/javascripts/project_import.js.coffee +++ b/app/assets/javascripts/project_import.js.coffee @@ -1,7 +1,5 @@ -class ProjectImport +class @ProjectImport constructor: -> setTimeout -> Turbolinks.visit(location.href) , 5000 - -@ProjectImport = ProjectImport diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index e144dfa1d68..c1801365266 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -1,4 +1,4 @@ -class SearchAutocomplete +class @SearchAutocomplete constructor: (search_autocomplete_path, project_id, project_ref) -> project_id = '' unless project_id project_ref = '' unless project_ref @@ -9,5 +9,3 @@ class SearchAutocomplete minLength: 1 select: (event, ui) -> location.href = ui.item.url - -@SearchAutocomplete = SearchAutocomplete diff --git a/app/assets/javascripts/stat_graph.js.coffee b/app/assets/javascripts/stat_graph.js.coffee index b129619696f..f36c71fd25e 100644 --- a/app/assets/javascripts/stat_graph.js.coffee +++ b/app/assets/javascripts/stat_graph.js.coffee @@ -1,4 +1,4 @@ -class window.StatGraph +class @StatGraph @log: {} @get_log: -> @log diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee index ab785a54543..27f0fd31d50 100644 --- a/app/assets/javascripts/stat_graph_contributors.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors.js.coffee @@ -1,4 +1,4 @@ -class window.ContributorsStatGraph +class @ContributorsStatGraph init: (log) -> @parsed_log = ContributorsStatGraphUtil.parse_log(log) @set_current_field("commits") diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee index 834c7e5dab0..9952fa0b00a 100644 --- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee @@ -1,4 +1,4 @@ -class window.ContributorsGraph +class @ContributorsGraph MARGIN: top: 20 right: 20 @@ -44,7 +44,7 @@ class window.ContributorsGraph set_data: (data) -> @data = data -class window.ContributorsMasterGraph extends ContributorsGraph +class @ContributorsMasterGraph extends ContributorsGraph constructor: (@data) -> @width = $('.container').width() - 70 @height = 200 @@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph @svg.select("path").attr("d", @area) @svg.select(".y.axis").call(@y_axis) -class window.ContributorsAuthorGraph extends ContributorsGraph +class @ContributorsAuthorGraph extends ContributorsGraph constructor: (@data) -> @width = $('.container').width()/2 - 100 @height = 200 diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee index 5eaa8ad4ff9..32486f7da54 100644 --- a/app/assets/javascripts/team_members.js.coffee +++ b/app/assets/javascripts/team_members.js.coffee @@ -1,6 +1,4 @@ -class TeamMembers +class @TeamMembers constructor: -> $('.team-members .project-access-select').on "change", -> $(this.form).submit() - -@TeamMembers = TeamMembers diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index 4852e879b68..d428db5b422 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -1,4 +1,4 @@ -class TreeView +class @TreeView constructor: -> @initKeyNav() @@ -39,5 +39,3 @@ class TreeView else if e.which is 13 path = $('.tree-item.selected .tree-item-file-name a').attr('href') Turbolinks.visit(path) - -@TreeView = TreeView diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee index 17e790e5b7c..66757565d3a 100644 --- a/app/assets/javascripts/wikis.js.coffee +++ b/app/assets/javascripts/wikis.js.coffee @@ -1,4 +1,4 @@ -class Wikis +class @Wikis constructor: -> $('.build-new-wiki').bind "click", -> field = $('#new_wiki_path') @@ -7,6 +7,3 @@ class Wikis if(slug.length > 0) location.href = path + "/" + slug - - -@Wikis = Wikis From f7d01f2067048e2dbdff14ef081a66d89316824c Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 20 Oct 2014 23:43:51 +0200 Subject: [PATCH 082/408] Factor group tips --- app/views/admin/groups/_form.html.haml | 7 +------ app/views/groups/new.html.haml | 7 +------ app/views/shared/_group_tips.html.haml | 6 ++++++ 3 files changed, 8 insertions(+), 12 deletions(-) create mode 100644 app/views/shared/_group_tips.html.haml diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index c56863ce274..7b55249bdca 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -29,12 +29,7 @@ .col-sm-2 .col-sm-10 .bs-callout.bs-callout-info - %ul - %li A group is a collection of several projects - %li Groups are private by default - %li Members of a group may only view projects they have permission to access - %li Group project URLs are prefixed with the group namespace - %li Existing projects may be moved into a group + = render 'shared/group_tips' .form-actions = f.submit 'Create group', class: "btn btn-create" = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 235e299343a..ccc17dc436b 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -27,12 +27,7 @@ .form-group .col-sm-2 .col-sm-10 - %ul - %li A group is a collection of several projects - %li Groups are private by default - %li Members of a group may only view projects they have permission to access - %li Group project URLs are prefixed with the group namespace - %li Existing projects may be moved into a group + = render 'shared/group_tips' .form-actions = f.submit 'Create group', class: "btn btn-create", tabindex: 3 diff --git a/app/views/shared/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml new file mode 100644 index 00000000000..e5cf783beb7 --- /dev/null +++ b/app/views/shared/_group_tips.html.haml @@ -0,0 +1,6 @@ +%ul + %li A group is a collection of several projects + %li Groups are private by default + %li Members of a group may only view projects they have permission to access + %li Group project URLs are prefixed with the group namespace + %li Existing projects may be moved into a group From 01db264ffcaaa5579c63b1d33f97d446cc726c3e Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 20 Oct 2014 23:29:49 +0200 Subject: [PATCH 083/408] Factor choose group avatar button --- app/views/admin/groups/_form.html.haml | 8 +------- app/views/groups/edit.html.haml | 8 +------- app/views/groups/new.html.haml | 8 +------- app/views/shared/_choose_group_avatar_button.html.haml | 7 +++++++ 4 files changed, 10 insertions(+), 21 deletions(-) create mode 100644 app/views/shared/_choose_group_avatar_button.html.haml diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index c56863ce274..37ce68adc3a 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -16,13 +16,7 @@ .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' .col-sm-10 - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.fa.fa-paperclip - %span Choose File ... -   - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f - if @group.new_record? .form-group diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 0b15affe785..b40c164f91a 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -31,13 +31,7 @@ You can change your group avatar here - else You can upload a group avatar here - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.fa.fa-paperclip - %span Choose File ... -   - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f - if @group.avatar? %hr = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 235e299343a..df5c9541994 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -16,13 +16,7 @@ .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' .col-sm-10 - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.fa.fa-paperclip - %span Choose File ... -   - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f .form-group .col-sm-2 diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml new file mode 100644 index 00000000000..f32c2d388a7 --- /dev/null +++ b/app/views/shared/_choose_group_avatar_button.html.haml @@ -0,0 +1,7 @@ +%a.choose-btn.btn.btn-small.js-choose-group-avatar-button + %i.fa.fa-paperclip + %span Choose File ... +  +%span.file_name.js-avatar-filename File name... += f.file_field :avatar, class: 'js-group-avatar-input hidden' +.light The maximum file size allowed is 100KB. From 3a47ed979a62e91fdcd7d05ebb309759788a23b6 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 21 Oct 2014 00:25:44 +0200 Subject: [PATCH 084/408] Factor group forms --- app/views/admin/groups/_form.html.haml | 10 +--------- app/views/groups/edit.html.haml | 11 +---------- app/views/groups/new.html.haml | 10 +--------- app/views/shared/_group_form.html.haml | 12 ++++++++++++ 4 files changed, 15 insertions(+), 28 deletions(-) create mode 100644 app/views/shared/_group_form.html.haml diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index c56863ce274..bc612307de6 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -2,16 +2,8 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group.group_name_holder - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Example Group", class: "form-control" - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + = render 'shared/group_form', f: f .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 0b15affe785..c2fcace8200 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -11,16 +11,7 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left" - - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + = render 'shared/group_form', f: f .form-group .col-sm-2 diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 235e299343a..2116d21ac41 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -2,16 +2,8 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2 + = render 'shared/group_form', f: f, autofocus: true .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml new file mode 100644 index 00000000000..93294e42505 --- /dev/null +++ b/app/views/shared/_group_form.html.haml @@ -0,0 +1,12 @@ +.form-group + = f.label :name, class: 'control-label' do + Group name + .col-sm-10 + = f.text_field :name, placeholder: 'Example Group', class: 'form-control', + autofocus: local_assigns[:autofocus] || false + +.form-group.group-description-holder + = f.label :description, 'Details', class: 'control-label' + .col-sm-10 + = f.text_area :description, maxlength: 250, + class: 'form-control js-gfm-input', rows: 4 From 19ab9b40b800e15a1a07b00b9adc6534ada12dd1 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 20 Oct 2014 18:03:12 +0200 Subject: [PATCH 085/408] State on CONTRIBUTING fix line style --- CONTRIBUTING.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce454a11a08..d8d3c251080 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,11 @@ For examples of feedback on merge requests please look at already [closed merge 1. Contains functionality we think other users will benefit from too 1. Doesn't add configuration options since they complicate future changes 1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging. -1. It conforms to the following style guides +1. It conforms to the following style guides. + If your change touches a line that does not follow the style, + modify the entire line to follow it. This prevents linting tools from generating warnings. + Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications + may leave style non-compliant. ## Style guides From db5ea013f4a75b6f6b6cf7fd43011f5c2c29fda1 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Thu, 25 Sep 2014 16:43:23 +0200 Subject: [PATCH 086/408] Use :message key, not :error for File::Service. --- app/controllers/projects/blob_controller.rb | 2 +- .../projects/edit_tree_controller.rb | 2 +- app/services/files/base_service.rb | 6 ------ features/project/source/browse_files.feature | 20 +++++++++++++++++++ features/steps/project/source/browse_files.rb | 8 ++++++++ features/steps/shared/paths.rb | 9 +++++++++ lib/api/files.rb | 6 +++--- 7 files changed, 42 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7009e3b1bc8..0944c7421ee 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController flash[:notice] = "Your changes have been successfully committed" redirect_to project_tree_path(@project, @ref) else - flash[:alert] = result[:error] + flash[:alert] = result[:message] render :show end end diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index 8976d7c7be8..fdc1a85d8d7 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController redirect_to after_edit_path else - flash[:alert] = result[:error] + flash[:alert] = result[:message] render :show end end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index db6f0831f8b..bd245100955 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -10,12 +10,6 @@ module Files private - def success - out = super() - out[:error] = '' - out - end - def repository project.repository end diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index aca255b9444..b7d70881d56 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -34,6 +34,16 @@ Feature: Project Source Browse Files 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 + And I fill the new file name with an illegal name + And I edit code + And I fill the commit message + And I click on "Commit changes" + Then I am on the new file page + And I see a commit error message + @javascript Scenario: I can edit file Given I click on ".gitignore" file in repo @@ -50,6 +60,16 @@ Feature: Project Source Browse Files Then I am redirected to the ".gitignore" And I should see its new content + @javascript @wip + Scenario: If I don't change the content of the file I see an error message + Given I click on ".gitignore" file in repo + And I click button "edit" + And I fill the commit message + And I click on "Commit changes" + # Test fails because carriage returns are added to the file. + Then I am on the ".gitignore" edit file page + And I see a commit error message + @javascript Scenario: I can see editing preview Given I click on ".gitignore" file in repo diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 20f8f6c24ae..665f5d6d195 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -61,6 +61,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps fill_in :file_name, with: new_file_name end + step 'I fill the new file name with an illegal name' do + fill_in :file_name, with: '.git' + end + step 'I fill the commit message' do fill_in :commit_message, with: 'Not yet a commit message.' end @@ -151,6 +155,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(page).not_to have_link('permalink') end + step 'I see a commit error message' do + expect(page).to have_content('Your changes could not be committed') + end + private def set_new_content diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 1f238f8befd..5f292255ce1 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -265,6 +265,15 @@ module SharedPaths visit project_blob_path(@project, File.join(root_ref, '.gitignore')) end + step 'I am on the new file page' do + current_path.should eq(project_new_tree_path(@project, root_ref)) + end + + step 'I am on the ".gitignore" edit file page' do + current_path.should eq(project_edit_tree_path( + @project, File.join(root_ref, '.gitignore'))) + end + step 'I visit project source page for "6d39438"' do visit project_tree_path(@project, "6d39438") end diff --git a/lib/api/files.rb b/lib/api/files.rb index e63e635a4d3..84e1d311781 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -85,7 +85,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end @@ -117,7 +117,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end @@ -149,7 +149,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end end From 38670a663c30ed2fb2b10ac51d5d6bc56b4101a3 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 21 Oct 2014 00:49:59 +0200 Subject: [PATCH 087/408] Only run avatar chooser Js on pages that need it --- app/assets/javascripts/dispatcher.js.coffee | 2 ++ app/assets/javascripts/group_avatar.js.coffee | 9 +++++++++ app/assets/javascripts/groups.js.coffee | 11 ----------- 3 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/group_avatar.js.coffee diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 00b52758fa8..61f272fda30 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -67,6 +67,8 @@ class Dispatcher new TeamMembers() when 'groups:members' new GroupMembers() + when 'groups:new', 'groups:edit', 'admin:groups:edit' + new GroupAvatar() when 'projects:tree:show' new TreeView() shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/group_avatar.js.coffee b/app/assets/javascripts/group_avatar.js.coffee new file mode 100644 index 00000000000..0825fd3ce52 --- /dev/null +++ b/app/assets/javascripts/group_avatar.js.coffee @@ -0,0 +1,9 @@ +class @GroupAvatar + constructor: -> + $('.js-choose-group-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-group-avatar-input").click() + $('.js-group-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index 90122044248..cc905e91ea2 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -2,14 +2,3 @@ class @GroupMembers constructor: -> $('li.group_member').bind 'ajax:success', -> $(this).fadeOut() - -$ -> - # avatar - $('.js-choose-group-avatar-button').bind "click", -> - form = $(this).closest("form") - form.find(".js-group-avatar-input").click() - - $('.js-group-avatar-input').bind "change", -> - form = $(this).closest("form") - filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) From 2c98584a9c0c019af58012865ae4425df5127ac6 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sun, 19 Oct 2014 11:25:43 +0200 Subject: [PATCH 088/408] Remove unused admin/projects#repository method Already defined on the ApplicationController base class. --- app/controllers/admin/projects_controller.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 23d4a9860a1..7c2388e81be 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -38,8 +38,4 @@ class Admin::ProjectsController < Admin::ApplicationController def group @group ||= @project.group end - - def repository - @repository ||= @project.repository - end end From 6c476a8dac8411f9a9d5a2cbf9ff088dc55369d9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 21 Oct 2014 12:04:09 +0300 Subject: [PATCH 089/408] Improved release documentation --- doc/release/monthly.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c46a3ed9c93..f972fd20364 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -11,7 +11,7 @@ NOTE: This is a guide for GitLab developers. A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. ### **3. Create an overall issue** -Name it "Release x.x.x" for easier searching. +Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching. ``` 15th: @@ -156,6 +156,12 @@ Create an annotated tag that points to the version change commit: git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1' ``` +Tags should be created for both GitLab CE and GitLab EE. Don't forget to push tags to all remotes. + +``` +git push remote_name vx.x.0.rc1 +``` + ### **6. Create stable branches** For GitLab EE, append `-ee` to the branch. From 593a287c8d0cfcc22ca2db35dc9a72140e296c2e Mon Sep 17 00:00:00 2001 From: Sullivan SENECHAL Date: Sat, 11 Oct 2014 13:10:41 +0200 Subject: [PATCH 090/408] Add timezone configuration to gitlab.yml --- CHANGELOG | 3 +++ config/application.rb | 1 + config/gitlab.yml.example | 5 +++++ config/initializers/1_settings.rb | 1 + config/initializers/time_zone.rb | 1 + 5 files changed, 11 insertions(+) create mode 100644 config/initializers/time_zone.rb diff --git a/CHANGELOG b/CHANGELOG index 05290698320..f82ef4b4c78 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +v 7.5.0 + - Add time zone configuration on gitlab.yml (Sullivan Senechal) + v 7.4.0 - Refactored membership logic - Improve error reporting on users API (Julien Bianchi) diff --git a/config/application.rb b/config/application.rb index e36df913d0b..85c83f74a95 100644 --- a/config/application.rb +++ b/config/application.rb @@ -25,6 +25,7 @@ module Gitlab # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # NOTE: Please prefer set time zone on config/gitlab.yml configuration file. # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e7a8d08dc83..2ca6abac576 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -33,6 +33,11 @@ production: &base # Uncomment and customize if you can't use the default user to run GitLab (default: 'git') # user: git + ## Date & Time settings + # Uncomment and customize if you want to change the default time zone of GitLab application. + # To see all available zones, run `bundle exec rake time:zones:all` + # time_zone: 'UTC' + ## Email settings # Email address used in the "From" field in mails sent by GitLab email_from: example@example.com diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 88cbaefea7d..4670791ddb0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -103,6 +103,7 @@ Settings.gitlab['user_home'] ||= begin rescue ArgumentError # no user configured '/home/' + Settings.gitlab['user'] end +Settings.gitlab['time_zone'] ||= nil Settings.gitlab['signup_enabled'] ||= false Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb new file mode 100644 index 00000000000..ee246e67d66 --- /dev/null +++ b/config/initializers/time_zone.rb @@ -0,0 +1 @@ +Time.zone = Gitlab.config.gitlab.time_zone || Time.zone From cccfede34cac854b4a6cbbe64d83647ee0c0af35 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 21 Oct 2014 11:33:26 +0200 Subject: [PATCH 091/408] Add test for allowed team name of slack. --- spec/models/slack_service_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb index 95df38d9400..526165e397c 100644 --- a/spec/models/slack_service_spec.rb +++ b/spec/models/slack_service_spec.rb @@ -77,5 +77,25 @@ describe SlackService do WebMock.should have_requested(:post, api_url).once end end + + context 'with new webhook syntax with slack allowed team name' do + before do + @allowed_webhook = 'https://gitlab-hq-123.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI' + slack_service.stub( + project: project, + project_id: project.id, + service_hook: true, + webhook: @allowed_webhook + ) + + WebMock.stub_request(:post, @allowed_webhook) + end + + it "should call Slack API" do + slack_service.execute(sample_data) + + WebMock.should have_requested(:post, @allowed_webhook).once + end + end end end From ce61de68ba43bd59dcec607dddae49591459bf93 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 21 Oct 2014 11:38:41 +0200 Subject: [PATCH 092/408] Use allowed slack team name. --- app/models/project_services/slack_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 95f3ddcef45..837002ef3c8 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -40,7 +40,8 @@ class SlackService < Service project_name: project_name )) - credentials = webhook.match(/(\w*).slack.com.*services\/(.*)/) + credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/) + if credentials.present? subdomain = credentials[1] token = credentials[2].split("token=").last From b1e60cfa1b56d01c2328ebe2a3d49cd82058f981 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 21 Oct 2014 13:20:59 +0300 Subject: [PATCH 093/408] remove unnecessary parts from update doc --- doc/update/7.3-to-7.4.md | 37 ++++--------------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index c1a70ba4e60..b3eaa3bdce5 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -52,31 +52,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` - -### 4. Configure Redis to use sockets - - # Configure redis to use sockets - sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig - # Disable Redis listening on TCP by setting 'port' to 0 - sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf - # Enable Redis socket for default Debian / Ubuntu path - echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf - # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). - sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf - # Activate the changes to redis.conf - sudo service redis-server restart - # Add git to the redis group - sudo usermod -aG redis git - - # Configure Redis connection settings - sudo -u git -H cp config/resque.yml.example config/resque.yml - # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration - sudo -u git -H editor config/resque.yml - - # Configure gitlab-shell to use Redis sockets - sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml - -### 5. Update config files +### 4. Update config files #### New configuration options for gitlab.yml @@ -102,12 +78,12 @@ sudo -u git -H editor config/unicorn.rb * Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) -### 6. Start application +### 5. Start application sudo service gitlab start sudo service nginx restart -### 7. Check application status +### 6. Check application status Check if GitLab and its environment are configured correctly: @@ -119,13 +95,8 @@ To make sure you didn't miss anything run a more thorough check with: If all items are green, then congratulations upgrade is complete! -### 8. Update OmniAuth configuration -When using Google omniauth login, changes of the Google account required. -Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). -More details can be found at the [integration documentation](../integration/google.md). - -### 9. Optional optimizations for GitLab setups with MySQL databases +### 7. Optional optimizations for GitLab setups with MySQL databases Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. From ce056d80748da32e20c3bfab1bff9567a812bfe1 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 21 Oct 2014 12:36:09 +0200 Subject: [PATCH 094/408] Improve grack auth hooks comment. --- lib/gitlab/backend/grack_auth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index c2f3b851c07..df1461a45c9 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -90,7 +90,7 @@ module Grack when *Gitlab::GitAccess::PUSH_COMMANDS if user # Skip user authorization on upload request. - # It will be serverd by update hook in repository + # It will be done by the pre-receive hook in the repository. true else false From e6631c87860c182ce9c838da6b4ad8d570061dfb Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 21 Oct 2014 13:21:58 +0200 Subject: [PATCH 095/408] Merge request for blog post on gitlab.com next time. --- doc/release/monthly.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c46a3ed9c93..a9253339e5a 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -191,6 +191,7 @@ It is important to do this as soon as possible, so we can catch any errors befor - Ask Dmitriy to add screenshots to the WIP MR. - Decide with team who will be the MVP user. - Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. +- Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master) - Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor) - After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments From 9f54397f3a3e094665d25109a63f24757a19df3a Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 21 Oct 2014 11:38:41 +0200 Subject: [PATCH 096/408] Use allowed slack team name. --- app/models/project_services/slack_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 95f3ddcef45..837002ef3c8 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -40,7 +40,8 @@ class SlackService < Service project_name: project_name )) - credentials = webhook.match(/(\w*).slack.com.*services\/(.*)/) + credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/) + if credentials.present? subdomain = credentials[1] token = credentials[2].split("token=").last From 536f61e0e77227828d363a7008bd39b0e9fd43a7 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 21 Oct 2014 11:33:26 +0200 Subject: [PATCH 097/408] Add test for allowed team name of slack. --- spec/models/slack_service_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb index 95df38d9400..526165e397c 100644 --- a/spec/models/slack_service_spec.rb +++ b/spec/models/slack_service_spec.rb @@ -77,5 +77,25 @@ describe SlackService do WebMock.should have_requested(:post, api_url).once end end + + context 'with new webhook syntax with slack allowed team name' do + before do + @allowed_webhook = 'https://gitlab-hq-123.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI' + slack_service.stub( + project: project, + project_id: project.id, + service_hook: true, + webhook: @allowed_webhook + ) + + WebMock.stub_request(:post, @allowed_webhook) + end + + it "should call Slack API" do + slack_service.execute(sample_data) + + WebMock.should have_requested(:post, @allowed_webhook).once + end + end end end From da21b9e7d045a1f9b044563b62f09992ac685065 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 21 Oct 2014 18:26:40 +0300 Subject: [PATCH 098/408] Fix rake gitlab:ldap:check Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/ldap/adapter.rb | 6 ++++- lib/tasks/gitlab/check.rake | 44 ++++++++++--------------------------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index c4d0a20d89a..256cdb4c2f1 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -22,7 +22,7 @@ module Gitlab Gitlab::LDAP::Config.new(provider) end - def users(field, value) + def users(field, value, limit = nil) if field.to_sym == :dn options = { base: value, @@ -45,6 +45,10 @@ module Gitlab end end + if limit.present? + options.merge!(size: limit) + end + entries = ldap_search(options).select do |entry| entry.respond_to? config.uid end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 9ec368254ac..707d236068e 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -664,7 +664,7 @@ namespace :gitlab do warn_user_is_not_gitlab start_checking "LDAP" - if ldap_config.enabled + if Gitlab::LDAP::Config.enabled? print_users(args.limit) else puts 'LDAP is disabled in config/gitlab.yml' @@ -675,39 +675,19 @@ namespace :gitlab do def print_users(limit) puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" - ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry| - puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}" + + servers = Gitlab.config.ldap.servers.keys + + servers.each do |server| + puts "Server: #{server}" + Gitlab::LDAP::Adapter.open("ldap#{server}") do |adapter| + users = adapter.users(adapter.config.uid, '*', 100) + users.each do |user| + puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}" + end + end end end - - def attributes - [ldap_config.uid] - end - - def filter - uid_filter = Net::LDAP::Filter.present?(ldap_config.uid) - if user_filter - Net::LDAP::Filter.join(uid_filter, user_filter) - else - uid_filter - end - end - - def user_filter - if ldap_config['user_filter'] && ldap_config.user_filter.present? - Net::LDAP::Filter.construct(ldap_config.user_filter) - else - nil - end - end - - def ldap - @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection - end - - def ldap_config - @ldap_config ||= Gitlab.config.ldap - end end # Helper methods From be80837a6941abe48d99acad1c3eb8a9957a0b42 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 21 Oct 2014 19:12:52 +0300 Subject: [PATCH 099/408] Update Guide: Change path to nginx config --- doc/update/6.x-or-7.x-to-7.4.md | 4 ++-- doc/update/7.3-to-7.4.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.4.md index 8516c3babaa..2fa6889af73 100644 --- a/doc/update/6.x-or-7.x-to-7.4.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -160,8 +160,8 @@ git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.examp * Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings. * Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings. * Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings. -* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings. * Copy rack attack middleware config ```bash diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index b3eaa3bdce5..69d86fb06ed 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -71,7 +71,7 @@ sudo -u git -H editor config/unicorn.rb #### Change nginx https settings -* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting #### MySQL Databases: Update database.yml config file From 0e70e3b557ccc660c97e7dc7938e53c17faac479 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 21 Oct 2014 20:18:29 +0200 Subject: [PATCH 100/408] Remove whitespace link between user group avatars --- app/views/users/_groups.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index 09b2985d498..ea008c2dede 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,3 +1,3 @@ - groups.each do |group| = link_to group, class: 'profile-groups-avatars', :title => group.name do - = image_tag group_icon(group.path) + - image_tag group_icon(group.path) From 93e8a0563da89a59e35a876788d0a8bd442640da Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 21 Oct 2014 10:40:36 +0200 Subject: [PATCH 101/408] Only run profile js on pages that need it --- app/assets/javascripts/dispatcher.js.coffee | 4 ++ app/assets/javascripts/profile.js.coffee | 45 ++++++++++----------- app/assets/javascripts/user.js.coffee | 3 ++ 3 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/user.js.coffee diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 00b52758fa8..1c52933f186 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -79,11 +79,15 @@ class Dispatcher # Ensure we don't create a particular shortcut handler here. This is # already created, where the network graph is created. shortcut_handler = true + when 'users:show' + new User() switch path.first() when 'admin' then new Admin() when 'dashboard' shortcut_handler = new ShortcutsDashboardNavigation() + when 'profiles' + new Profile() when 'projects' switch path[1] when 'wikis' diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 0e99921f899..de356fbec77 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -1,30 +1,29 @@ -$ -> - $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> - # Submit the form - $('.edit_user').submit() +class @Profile + constructor: -> + $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> + # Submit the form + $('.edit_user').submit() - new Flash("Appearance settings saved", "notice") + new Flash("Appearance settings saved", "notice") - $('.update-username form').on 'ajax:before', -> - $('.loading-gif').show() - $(this).find('.update-success').hide() - $(this).find('.update-failed').hide() + $('.update-username form').on 'ajax:before', -> + $('.loading-gif').show() + $(this).find('.update-success').hide() + $(this).find('.update-failed').hide() - $('.update-username form').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() - $(this).find('.loading-gif').hide() + $('.update-username form').on 'ajax:complete', -> + $(this).find('.btn-save').enableButton() + $(this).find('.loading-gif').hide() - $('.update-notifications').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() + $('.update-notifications').on 'ajax:complete', -> + $(this).find('.btn-save').enableButton() - $('.js-choose-user-avatar-button').bind "click", -> - form = $(this).closest("form") - form.find(".js-user-avatar-input").click() + $('.js-choose-user-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-user-avatar-input").click() - $('.js-user-avatar-input').bind "change", -> - form = $(this).closest("form") - filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) - - $('.profile-groups-avatars').tooltip("placement": "top") + $('.js-user-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee new file mode 100644 index 00000000000..8a2e2421c2e --- /dev/null +++ b/app/assets/javascripts/user.js.coffee @@ -0,0 +1,3 @@ +class @User + constructor: -> + $('.profile-groups-avatars').tooltip("placement": "top") From 3dbe0810f503ae51fc5e9310d753278c5b645b11 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Tue, 21 Oct 2014 13:36:09 -0700 Subject: [PATCH 102/408] cleanup time zone settings time zone settings moved to gitlab.yml in https://github.com/gitlabhq/gitlabhq/pull/8015 --- config/application.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/application.rb b/config/application.rb index 85c83f74a95..e8841e6be42 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,11 +23,6 @@ module Gitlab # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # NOTE: Please prefer set time zone on config/gitlab.yml configuration file. - # config.time_zone = 'Central Time (US & Canada)' - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de From 3418f56a6915d11023a07c4ea8ecc535ec52871a Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 21 Oct 2014 22:39:34 +0200 Subject: [PATCH 103/408] Better js -> URL map to reduce unneeded execution --- app/assets/javascripts/dispatcher.js.coffee | 13 ++-- app/assets/javascripts/project.js.coffee | 71 +++++-------------- app/assets/javascripts/project_new.js.coffee | 25 +++++++ app/assets/javascripts/project_show.js.coffee | 15 ++++ 4 files changed, 64 insertions(+), 60 deletions(-) create mode 100644 app/assets/javascripts/project_new.js.coffee create mode 100644 app/assets/javascripts/project_show.js.coffee diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 00b52758fa8..a78970ff318 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -58,11 +58,6 @@ class Dispatcher when 'groups:show', 'projects:show' new Activities() shortcut_handler = new ShortcutsNavigation() - when 'projects:new' - new Project() - when 'projects:edit' - new Project() - shortcut_handler = new ShortcutsNavigation() when 'projects:teams:members:index' new TeamMembers() when 'groups:members' @@ -85,7 +80,15 @@ class Dispatcher when 'dashboard' shortcut_handler = new ShortcutsDashboardNavigation() when 'projects' + new Project() switch path[1] + when 'edit' + shortcut_handler = new ShortcutsNavigation() + new ProjectNew() + when 'new' + new ProjectNew() + when 'show' + new ProjectShow() when 'wikis' new Wikis() shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index aba40742e5f..5a9cc66c8f0 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,59 +1,20 @@ class @Project constructor: -> - $('.project-edit-container').on 'ajax:before', => - $('.project-edit-container').hide() - $('.save-project-loader').show() + # Git clone panel switcher + scope = $ '.git-clone-holder' + if scope.length > 0 + $('a, button', scope).click -> + $('a, button', scope).removeClass 'active' + $(@).addClass 'active' + $('#project_clone', scope).val $(@).data 'clone' + $(".clone").text("").append $(@).data 'clone' - @initEvents() + # Ref switcher + $('.project-refs-select').on 'change', -> + $(@).parents('form').submit() - - initEvents: -> - disableButtonIfEmptyField '#project_name', '.project-submit' - - $('#project_issues_enabled').change -> - if ($(this).is(':checked') == true) - $('#project_issues_tracker').removeAttr('disabled') - else - $('#project_issues_tracker').attr('disabled', 'disabled') - - $('#project_issues_tracker').change() - - $('#project_issues_tracker').change -> - if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) - $('#project_issues_tracker_id').attr('disabled', 'disabled') - else - $('#project_issues_tracker_id').removeAttr('disabled') - -$ -> - # Git clone panel switcher - scope = $ '.git-clone-holder' - if scope.length > 0 - $('a, button', scope).click -> - $('a, button', scope).removeClass 'active' - $(@).addClass 'active' - $('#project_clone', scope).val $(@).data 'clone' - $(".clone").text("").append $(@).data 'clone' - - # Ref switcher - $('.project-refs-select').on 'change', -> - $(@).parents('form').submit() - - $('.hide-no-ssh-message').on 'click', (e) -> - path = '/' - $.cookie('hide_no_ssh_message', 'false', { path: path }) - $(@).parents('.no-ssh-key-message').hide() - e.preventDefault() - - $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) -> - $(@).toggleClass('on').find('.count').html(data.star_count) - .on 'ajax:error', (e, xhr, status, error) -> - new Flash('Star toggle failed. Try again later.', 'alert') - - $("a[data-toggle='tab']").on "shown.bs.tab", (e) -> - $.cookie "default_view", $(e.target).attr("href") - - defaultView = $.cookie("default_view") - if defaultView - $("a[href=" + defaultView + "]").tab "show" - else - $("a[data-toggle='tab']:first").tab "show" + $('.hide-no-ssh-message').on 'click', (e) -> + path = '/' + $.cookie('hide_no_ssh_message', 'false', { path: path }) + $(@).parents('.no-ssh-key-message').hide() + e.preventDefault() diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee new file mode 100644 index 00000000000..f4a2ca813d2 --- /dev/null +++ b/app/assets/javascripts/project_new.js.coffee @@ -0,0 +1,25 @@ +class @ProjectNew + constructor: -> + $('.project-edit-container').on 'ajax:before', => + $('.project-edit-container').hide() + $('.save-project-loader').show() + + @initEvents() + + + initEvents: -> + disableButtonIfEmptyField '#project_name', '.project-submit' + + $('#project_issues_enabled').change -> + if ($(this).is(':checked') == true) + $('#project_issues_tracker').removeAttr('disabled') + else + $('#project_issues_tracker').attr('disabled', 'disabled') + + $('#project_issues_tracker').change() + + $('#project_issues_tracker').change -> + if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) + $('#project_issues_tracker_id').attr('disabled', 'disabled') + else + $('#project_issues_tracker_id').removeAttr('disabled') diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee new file mode 100644 index 00000000000..02a7d7b731d --- /dev/null +++ b/app/assets/javascripts/project_show.js.coffee @@ -0,0 +1,15 @@ +class @ProjectShow + constructor: -> + $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) -> + $(@).toggleClass('on').find('.count').html(data.star_count) + .on 'ajax:error', (e, xhr, status, error) -> + new Flash('Star toggle failed. Try again later.', 'alert') + + $("a[data-toggle='tab']").on "shown.bs.tab", (e) -> + $.cookie "default_view", $(e.target).attr("href") + + defaultView = $.cookie("default_view") + if defaultView + $("a[href=" + defaultView + "]").tab "show" + else + $("a[data-toggle='tab']:first").tab "show" From ab1ad3bd18d9b9359fc361d7dab3b317fdd38984 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 21 Oct 2014 22:52:38 +0200 Subject: [PATCH 104/408] Only run namespace select js when needed Only needed in admin/projects. --- app/assets/javascripts/dispatcher.js.coffee | 6 ++- .../javascripts/namespace_select.js.coffee | 43 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 00b52758fa8..72bff2d8ab4 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -81,7 +81,11 @@ class Dispatcher shortcut_handler = true switch path.first() - when 'admin' then new Admin() + when 'admin' + new Admin() + switch path[1] + when 'projects' + new NamespaceSelect() when 'dashboard' shortcut_handler = new ShortcutsDashboardNavigation() when 'projects' diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index 00d135d1449..a02c4515ccc 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -1,24 +1,25 @@ -$ -> - namespaceFormatResult = (namespace) -> - markup = "
    " - markup += "" + namespace.kind + "" - markup += "" + namespace.path + "" - markup += "
    " - markup +class @NamespaceSelect + constructor: -> + namespaceFormatResult = (namespace) -> + markup = "
    " + markup += "" + namespace.kind + "" + markup += "" + namespace.path + "" + markup += "
    " + markup - formatSelection = (namespace) -> - namespace.kind + ": " + namespace.path + formatSelection = (namespace) -> + namespace.kind + ": " + namespace.path - $('.ajax-namespace-select').each (i, select) -> - $(select).select2 - placeholder: "Search for namespace" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.namespaces query.term, (namespaces) -> - data = { results: namespaces } - query.callback(data) + $('.ajax-namespace-select').each (i, select) -> + $(select).select2 + placeholder: "Search for namespace" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.namespaces query.term, (namespaces) -> + data = { results: namespaces } + query.callback(data) - dropdownCssClass: "ajax-namespace-dropdown" - formatResult: namespaceFormatResult - formatSelection: formatSelection + dropdownCssClass: "ajax-namespace-dropdown" + formatResult: namespaceFormatResult + formatSelection: formatSelection From aa923847b2ad2734d28b50546674d3264a7969bb Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Tue, 21 Oct 2014 14:23:58 -0700 Subject: [PATCH 105/408] cleanup monthly release details --- doc/release/monthly.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index a9253339e5a..5bb63037d6e 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -4,7 +4,7 @@ NOTE: This is a guide for GitLab developers. # **15th - Code Freeze & Release Manager** -### **1. Stop merging in code, except for important bugfixes** +### **1. Stop merging in code, except for important bug fixes** ### **2. Release Manager** @@ -52,7 +52,7 @@ Name it "Release x.x.x" for easier searching. * Deploy to GitLab.com (#LINK) ``` -### **4. Update Changelog** +### **4. Update changelog** Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing. @@ -71,15 +71,15 @@ The RC1 release comes with the task to update the installation and upgrade docs. ### **1. Update the installation guide** 1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay) -1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) -1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) +1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) +1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794) 1. There might be other changes. Ask around. -### **2. Create an update guides** +### **2. Create update guides** -1. Create: CE update guide from previous version. Like `from-6-8-to-6.9` +1. Create: CE update guide from previous version. Like `7.3-to-7.4.md` 1. Create: CE to EE update guide in EE repository for latest version. -1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version. +1. Update: `6.x-or-7.x-to-7.x.md` to latest version. It's best to copy paste the previous guide and make changes where necessary. The typical steps are listed below with any points you should specifically look at. @@ -98,9 +98,9 @@ List any major changes here, so the user is aware of them before starting to upg #### 3. Do users need to update dependencies like `git`? -- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release. +- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release. -- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release. +- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release. #### 4. Get latest code @@ -112,19 +112,19 @@ List any major changes here, so the user is aware of them before starting to upg Check if any of these changed since last release: -- -- +- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab) +- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl) - -- -- -- -- -- -- +- [config/gitlab.yml.example](/config/gitlab.yml.example) +- [config/unicorn.rb.example](/config/unicorn.rb.example) +- [config/database.yml.mysql](/config/database.yml.mysql) +- [config/database.yml.postgresql](/config/database.yml.postgresql) +- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example) +- [config/resque.yml.example](/config/resque.yml.example) #### 8. Need to update init script? -Check if the `init.d/gitlab` script changed since last release: +Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab) #### 9. Start application @@ -252,7 +252,7 @@ Note: Merge CE into EE if needed. ### **2. Update installation.md** -Update [installation.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) to the newest version in master. +Update [installation.md](/doc/install/installation.md) to the newest version in master. ### **3. Push latest changes from x-x-stable branch to dev.gitlab.org** From 05f19392b76d9fbe40d97547ee2a3c87883c9639 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 22 Oct 2014 11:11:18 +0300 Subject: [PATCH 106/408] Make gitlab ldap check work for old and new syntax Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/gitlab/check.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 707d236068e..56e8ff44988 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -676,11 +676,11 @@ namespace :gitlab do def print_users(limit) puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" - servers = Gitlab.config.ldap.servers.keys + servers = Gitlab::LDAP::Config.providers servers.each do |server| puts "Server: #{server}" - Gitlab::LDAP::Adapter.open("ldap#{server}") do |adapter| + Gitlab::LDAP::Adapter.open(server) do |adapter| users = adapter.users(adapter.config.uid, '*', 100) users.each do |user| puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}" From 37e09858e8f6dec949f004a933eef8346ddc97d4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 21 Oct 2014 18:26:40 +0300 Subject: [PATCH 107/408] Fix rake gitlab:ldap:check Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/ldap/adapter.rb | 6 ++++- lib/tasks/gitlab/check.rake | 44 ++++++++++--------------------------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index c4d0a20d89a..256cdb4c2f1 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -22,7 +22,7 @@ module Gitlab Gitlab::LDAP::Config.new(provider) end - def users(field, value) + def users(field, value, limit = nil) if field.to_sym == :dn options = { base: value, @@ -45,6 +45,10 @@ module Gitlab end end + if limit.present? + options.merge!(size: limit) + end + entries = ldap_search(options).select do |entry| entry.respond_to? config.uid end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 9ec368254ac..707d236068e 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -664,7 +664,7 @@ namespace :gitlab do warn_user_is_not_gitlab start_checking "LDAP" - if ldap_config.enabled + if Gitlab::LDAP::Config.enabled? print_users(args.limit) else puts 'LDAP is disabled in config/gitlab.yml' @@ -675,39 +675,19 @@ namespace :gitlab do def print_users(limit) puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" - ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry| - puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}" + + servers = Gitlab.config.ldap.servers.keys + + servers.each do |server| + puts "Server: #{server}" + Gitlab::LDAP::Adapter.open("ldap#{server}") do |adapter| + users = adapter.users(adapter.config.uid, '*', 100) + users.each do |user| + puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}" + end + end end end - - def attributes - [ldap_config.uid] - end - - def filter - uid_filter = Net::LDAP::Filter.present?(ldap_config.uid) - if user_filter - Net::LDAP::Filter.join(uid_filter, user_filter) - else - uid_filter - end - end - - def user_filter - if ldap_config['user_filter'] && ldap_config.user_filter.present? - Net::LDAP::Filter.construct(ldap_config.user_filter) - else - nil - end - end - - def ldap - @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection - end - - def ldap_config - @ldap_config ||= Gitlab.config.ldap - end end # Helper methods From 4c034142a13c2e82e62b6f27a61e371b463310fb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 22 Oct 2014 11:11:18 +0300 Subject: [PATCH 108/408] Make gitlab ldap check work for old and new syntax Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/gitlab/check.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 707d236068e..56e8ff44988 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -676,11 +676,11 @@ namespace :gitlab do def print_users(limit) puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" - servers = Gitlab.config.ldap.servers.keys + servers = Gitlab::LDAP::Config.providers servers.each do |server| puts "Server: #{server}" - Gitlab::LDAP::Adapter.open("ldap#{server}") do |adapter| + Gitlab::LDAP::Adapter.open(server) do |adapter| users = adapter.users(adapter.config.uid, '*', 100) users.each do |user| puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}" From b0ef23c1936577b0d3a9d5e58c808bb24b41b0ea Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 22 Oct 2014 13:38:47 +0300 Subject: [PATCH 109/408] Fix 500 error on login page if ldap enabled and sign-in disabled Signed-off-by: Dmitriy Zaporozhets --- app/views/devise/sessions/new.html.haml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index b9832787446..ca7e9570b43 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -2,22 +2,22 @@ .login-heading %h3 Sign in .login-body - - if ldap_enabled? && gitlab_config.signin_enabled + - if ldap_enabled? %ul.nav.nav-tabs - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i==0)} + %li{class: (:active if i.zero?)} = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + - if gitlab_config.signin_enabled + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' .tab-content - - @ldap_servers.each_with_index do |server,i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i==0)} + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} = render 'devise/sessions/new_ldap', provider: server['provider_name'] - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' + - if gitlab_config.signin_enabled + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - - elsif ldap_enabled? - = render 'devise/sessions/new_ldap', ldap_servers: @ldap_servers - elsif gitlab_config.signin_enabled = render 'devise/sessions/new_base' - else From ed00ab75214389d108a857a22027f221a6649fbe Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 22 Oct 2014 12:42:41 +0000 Subject: [PATCH 110/408] Merge branch 'fix-500-login-disabled' into 'master' Fix 500 error on login page if ldap enabled and sign-in disabled Related to gitlab/gitlabhq#1701 See merge request !1209 --- app/views/devise/sessions/new.html.haml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index b9832787446..ca7e9570b43 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -2,22 +2,22 @@ .login-heading %h3 Sign in .login-body - - if ldap_enabled? && gitlab_config.signin_enabled + - if ldap_enabled? %ul.nav.nav-tabs - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i==0)} + %li{class: (:active if i.zero?)} = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + - if gitlab_config.signin_enabled + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' .tab-content - - @ldap_servers.each_with_index do |server,i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i==0)} + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} = render 'devise/sessions/new_ldap', provider: server['provider_name'] - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' + - if gitlab_config.signin_enabled + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - - elsif ldap_enabled? - = render 'devise/sessions/new_ldap', ldap_servers: @ldap_servers - elsif gitlab_config.signin_enabled = render 'devise/sessions/new_base' - else From ba76dbc3667c2eb0a1a3687f8b0481e619946d73 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 22 Oct 2014 15:42:43 +0200 Subject: [PATCH 111/408] Version 7.4.0. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7b65f139cb2..ba7f754d0c3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.0.rc1 +7.4.0 From 91c96b3714a8f5753d9851ee8e2a859b201f6905 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 22 Oct 2014 12:40:41 -0500 Subject: [PATCH 112/408] Added a password strength indicator to the reset password view and the change password view after first login. Updated JS to work with the updated views. --- app/assets/javascripts/password_strength.js.coffee | 11 ++++++----- app/views/devise/passwords/edit.html.haml | 4 ++-- app/views/profiles/passwords/new.html.haml | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee index e6fec307c59..61e25deac4c 100644 --- a/app/assets/javascripts/password_strength.js.coffee +++ b/app/assets/javascripts/password_strength.js.coffee @@ -15,10 +15,10 @@ $(document).ready -> profileOptions.rules = activated: overwritten_rules - signUpOptions = {} - signUpOptions.common = + deviseOptions = {} + deviseOptions.common = usernameField: "#user_username" - signUpOptions.ui = + deviseOptions.ui = container: "#password-strength" showPopover: true showErrors: true @@ -26,8 +26,9 @@ $(document).ready -> showProgressBar: false showStatus: true errorMessages: overwritten_messages - signUpOptions.rules = + deviseOptions.rules = activated: overwritten_rules $("#user_password").pwstrength profileOptions - $("#user_password_sign_up").pwstrength signUpOptions + $("#user_password_sign_up").pwstrength deviseOptions + $("#user_password_recover").pwstrength deviseOptions diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 1326cc0aac9..f6cbf9b82ba 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -6,8 +6,8 @@ .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token - %div - = f.password_field :password, class: "form-control top", placeholder: "New password", required: true + .form-group#password-strength + = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true .clearfix.append-bottom-10 diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index aef7348fd20..b52514668e3 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -14,7 +14,7 @@ .form-group = f.label :current_password, class: 'control-label' .col-sm-10= f.password_field :current_password, required: true, class: 'form-control' - .form-group + .form-group#password-strength = f.label :password, class: 'control-label' .col-sm-10= f.password_field :password, required: true, class: 'form-control' .form-group From 5d5b3b8273297f9fe70a8c14376da7e6e528e2c5 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Wed, 22 Oct 2014 22:52:50 +0200 Subject: [PATCH 113/408] Fix doc raketasts import md style [ci skip] --- doc/raketasks/import.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 39b1a52a44d..5dba8de6d56 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -1,18 +1,18 @@ # Import -### Import bare repositories into GitLab project instance +## Import bare repositories into GitLab project instance Notes: -* project owner will be a first admin -* groups will be created as needed -* group owner will be the first admin -* existing projects will be skipped +- project owner will be a first admin +- groups will be created as needed +- group owner will be the first admin +- existing projects will be skipped How to use: 1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path) -2. run the command below +1. run the command below ``` # omnibus-gitlab From 7039c9868a3209d89f8306c65ca5b74f8e2ea2c0 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 22 Oct 2014 20:39:02 -0500 Subject: [PATCH 114/408] Updated the IDs of the fields, so that it wouldn't mess with many tests Updated some tests to match new IDs --- app/assets/javascripts/password_strength.js.coffee | 2 +- app/views/profiles/passwords/edit.html.haml | 2 +- app/views/profiles/passwords/new.html.haml | 2 +- features/steps/profile/profile.rb | 6 +++--- spec/features/users_spec.rb | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee index 61e25deac4c..696a5ccf0b9 100644 --- a/app/assets/javascripts/password_strength.js.coffee +++ b/app/assets/javascripts/password_strength.js.coffee @@ -29,6 +29,6 @@ $(document).ready -> deviseOptions.rules = activated: overwritten_rules - $("#user_password").pwstrength profileOptions + $("#user_password_profile").pwstrength profileOptions $("#user_password_sign_up").pwstrength deviseOptions $("#user_password_recover").pwstrength deviseOptions diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 4440dcf338d..8e84d312194 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -24,7 +24,7 @@ .form-group#password-strength = f.label :password, 'New password', class: 'control-label' .col-sm-10 - = f.password_field :password, required: true, class: 'form-control' + = f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile' .form-group = f.label :password_confirmation, class: 'control-label' .col-sm-10 diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index b52514668e3..746b3a721e1 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -16,7 +16,7 @@ .col-sm-10= f.password_field :current_password, required: true, class: 'form-control' .form-group#password-strength = f.label :password, class: 'control-label' - .col-sm-10= f.password_field :password, required: true, class: 'form-control' + .col-sm-10= f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile' .form-group = f.label :password_confirmation, class: 'control-label' .col-sm-10 diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index adfaefb1644..0f7f33fe8ce 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -58,7 +58,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I try change my password w/o old one' do within '.update-password' do - fill_in "user_password", with: "22233344" + fill_in "user_password_profile", with: "22233344" fill_in "user_password_confirmation", with: "22233344" click_button "Save" end @@ -67,7 +67,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I change my password' do within '.update-password' do fill_in "user_current_password", with: "12345678" - fill_in "user_password", with: "22233344" + fill_in "user_password_profile", with: "22233344" fill_in "user_password_confirmation", with: "22233344" click_button "Save" end @@ -76,7 +76,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I unsuccessfully change my password' do within '.update-password' do fill_in "user_current_password", with: "12345678" - fill_in "user_password", with: "password" + fill_in "user_password_profile", with: "password" fill_in "user_password_confirmation", with: "confirmation" click_button "Save" end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 7b831c48611..a1206989d39 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -11,7 +11,7 @@ describe 'Users', feature: true do fill_in "user_name", with: "Name Surname" fill_in "user_username", with: "Great" fill_in "user_email", with: "name@mail.com" - fill_in "user_password", with: "password1234" + fill_in "user_password_sign_up", with: "password1234" fill_in "user_password_confirmation", with: "password1234" expect { click_button "Sign up" }.to change {User.count}.by(1) end From 41518a467dcef61deca24ad2f6205c6fd5706e1b Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Wed, 22 Oct 2014 21:08:19 -0500 Subject: [PATCH 115/408] Remove :keep_repo option Always delete repositories from the filesystem when deleting a project. --- app/controllers/projects_controller.rb | 3 +-- app/services/projects/destroy_service.rb | 13 +++++-------- lib/api/projects.rb | 8 +------- spec/requests/api/projects_spec.rb | 10 ---------- 4 files changed, 7 insertions(+), 27 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c881c921ce9..b3380a6ff23 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -100,8 +100,7 @@ class ProjectsController < ApplicationController def destroy return access_denied! unless can?(current_user, :remove_project, project) - ::Projects::DestroyService.new(@project, current_user, - keep_repo: params[:keep_repo]).execute + ::Projects::DestroyService.new(@project, current_user, {}).execute respond_to do |format| format.html do diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 7c7892a0b14..7e1d753b021 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -6,10 +6,7 @@ module Projects project.team.truncate project.repository.expire_cache unless project.empty_repo? - result = project.destroy - return false unless result - - unless params[:keep_repo] + if project.destroy GitlabShellWorker.perform_async( :remove_repository, project.path_with_namespace @@ -21,11 +18,11 @@ module Projects ) project.satellite.destroy - end - log_info("Project \"#{project.name}\" was removed") - system_hook_service.execute_hooks_for(project, :destroy) - result + log_info("Project \"#{project.name}\" was removed") + system_hook_service.execute_hooks_for(project, :destroy) + true + end end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index e70548d1e85..7fcf97d1ad6 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -174,17 +174,11 @@ module API # # Parameters: # id (required) - The ID of a project - # keep_repo (optional) - If true, then delete the project from the - # database but keep the repo, wiki, and satellite on disk. # Example Request: # DELETE /projects/:id delete ":id" do authorize! :remove_project, user_project - ::Projects::DestroyService.new( - user_project, - current_user, - keep_repo: params[:keep_repo] - ).execute + ::Projects::DestroyService.new(user_project, current_user, {}).execute end # Mark this project as forked from another diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 6de37cff0aa..ba7ec7b2be9 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -641,16 +641,6 @@ describe API::API, api: true do response.status.should == 200 end - it 'should keep repo when "keep_repo" param is true' do - expect(GitlabShellWorker).not_to( - receive(:perform_async).with(:remove_repository, - /#{project.path_with_namespace}/) - ) - - delete api("/projects/#{project.id}?keep_repo=true", user) - response.status.should == 200 - end - it "should not remove a project if not an owner" do user3 = create(:user) project.team << [user3, :developer] From 6704792933575846c55724a8fe667edb4ddcb490 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 23 Oct 2014 11:11:53 +0200 Subject: [PATCH 116/408] link third applications to website --- README.md | 10 ++-------- doc/api/README.md | 12 +++--------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2c0643cf598..63fa5e3da86 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,8 @@ Since a manual installation is a lot of work and error prone we strongly recomme ## Third-party applications -Access GitLab from multiple platforms with applications below. -These applications are maintained by contributors, GitLab B.V. does not offer support for them. - -- [iPhone app](http://gitlabcontrol.com/) -- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) -- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi) -- [Command line client](https://github.com/drewblessing/gitlab-cli) -- [Ruby API wrapper](https://github.com/NARKOZ/gitlab) +There are a lot of applications and API wrappers for GitLab. +Find them [on our website](https://about.gitlab.com/applications/). ### New versions diff --git a/doc/api/README.md b/doc/api/README.md index f76a253083f..ffe250df3ff 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -21,13 +21,7 @@ ## Clients -- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP -- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com) -- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby -- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python -- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java -- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js -- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET +Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients). ## Introduction @@ -158,7 +152,7 @@ When an attribute is missing, you will get something like: HTTP/1.1 400 Bad Request Content-Type: application/json - + { "message":"400 (Bad request) \"title\" not given" } @@ -167,7 +161,7 @@ When a validation error occurs, error messages will be different. They will hold HTTP/1.1 400 Bad Request Content-Type: application/json - + { "message": { "bio": [ From 40815b2bafb209c258048ef08e7506559f1bfa90 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Thu, 23 Oct 2014 13:12:21 +0200 Subject: [PATCH 117/408] Remove unused variable user at lib/gitlab/markdown --- lib/gitlab/markdown.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index ddcce7557a0..068c342398b 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -202,7 +202,7 @@ module Gitlab if identifier == "all" link_to("@all", project_url(project), options) - elsif user = User.find_by(username: identifier) + elsif User.find_by(username: identifier) link_to("@#{identifier}", user_url(identifier), options) end end From 6b2b20af417b09e0c6a404206b89e7e2ab7be0ed Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 23 Oct 2014 14:21:21 +0200 Subject: [PATCH 118/408] Fix LDAP authentication for Git HTTP access --- CHANGELOG | 1 + lib/gitlab/ldap/authentication.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3b0a351c86d..e7708bd0c1d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ v 7.5.0 - API: Add support for Hipchat (Kevin Houdebert) - Add time zone configuration on gitlab.yml (Sullivan Senechal) + - Fix LDAP authentication for Git HTTP access v 7.4.0 - Refactored membership logic diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb index a5944f96983..8af2c74e959 100644 --- a/lib/gitlab/ldap/authentication.rb +++ b/lib/gitlab/ldap/authentication.rb @@ -42,7 +42,7 @@ module Gitlab end def adapter - OmniAuth::LDAP::Adaptor.new(config.options) + OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys) end def config @@ -68,4 +68,4 @@ module Gitlab end end end -end \ No newline at end of file +end From 6e9fb7facce76999b0a13fd676a701ea5580b76c Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 23 Oct 2014 16:24:44 +0200 Subject: [PATCH 119/408] Added relative dates, removed packages for GitLab.com. --- doc/release/monthly.md | 46 ++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index f972fd20364..36fc0b1dea6 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -2,7 +2,7 @@ NOTE: This is a guide for GitLab developers. -# **15th - Code Freeze & Release Manager** +# **7 workdays before release - Code Freeze & Release Manager** ### **1. Stop merging in code, except for important bugfixes** @@ -12,30 +12,31 @@ A release manager is selected that coordinates the entire release of this versio ### **3. Create an overall issue** Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching. +Replace the dates with actual dates based on the number of workdays before the release. ``` -15th: +Xth: * Update the changelog (#LINK) * Triage the omnibus-gitlab milestone -16th: +Xth: * Merge CE in to EE (#LINK) * Close the omnibus-gitlab milestone -17th: +Xth: * Create x.x.0.rc1 (#LINK) * Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) -18th: +Xth: * Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) * Regression issue and tweet about rc1 (#LINK) * Start blog post (#LINK) -21th: +Xth: * Do QA and fix anything coming out of it (#LINK) @@ -43,13 +44,10 @@ Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier * Release CE and EE (#LINK) -23rd: +Xth: -* Prepare package for GitLab.com release (#LINK) +* * Deploy to GitLab.com (#LINK) -24th: - -* Deploy to GitLab.com (#LINK) ``` ### **4. Update Changelog** @@ -60,11 +58,11 @@ Any changes not yet added to the changelog are added by lead developer and in th Ensure that there is enough time to incorporate the findings of the release candidate, etc. -# **16th - Merge the CE into EE** +# **6 workdays before release- Merge the CE into EE** Do this via a merge request. -# **17th - Create RC1** +# **5 workdays before release - Create RC1** The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub. @@ -179,7 +177,7 @@ Now developers can use master for merging new features. So you should use stable branch for future code chages related to release. -# **18th - Release RC1** +# **4 workdays before release - Release RC1** ### **1. Update GitLab.com** @@ -217,7 +215,7 @@ Tweet about the RC release: > GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE -# **21st - Preparation** +# **1 workdays before release - Preparation** ### **1. Pre QA merge** @@ -309,22 +307,8 @@ List the most important features and link to the blog post. Proposed tweet for CE "GitLab X.X is released! It brings *** " -### **10. Send out the newsletter** +# **1 workday after release - Update GitLab.com** -Send out an email to the 'GitLab Newsletter' mailing list on MailChimp. -Replicate the former release newsletter and modify it accordingly. -**Do not forget to edit `Subject line` and regenerate `Plain-Text Email` from HTML source** - -Include a link to the blog post and keep it short. - -Proposed email text: -"We have released a new version of GitLab. See our blog post() for more information." - - -# **23rd - Optional Patch Release** - -# **24th - Update GitLab.com** - -Merge the stable release into GitLab.com. Once the build is green deploy the next morning. +Update GitLab.com from RC1 to the released package. # **25th - Release GitLab CI** From 2c14f9c41f195b8c914adb10c9a315d301944608 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Thu, 23 Oct 2014 16:25:09 +0200 Subject: [PATCH 120/408] Show nothing instead of unassigned on issues --- app/views/projects/issues/_issue.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index b125706781c..7525812696f 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -14,8 +14,6 @@ .issue-info - if issue.assignee assigned to #{link_to_member(@project, issue.assignee)} - - else - unassigned - if issue.votes_count > 0 = render 'votes/votes_inline', votable: issue - if issue.notes.any? From e8da077d4f9b63d7d7157416dbf0dad010ea90bd Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 23 Oct 2014 14:21:21 +0200 Subject: [PATCH 121/408] Fix LDAP authentication for Git HTTP access Conflicts: CHANGELOG --- CHANGELOG | 3 +++ lib/gitlab/ldap/authentication.rb | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 05290698320..561a23538e7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +v 7.4.1 + - Fix LDAP authentication for Git HTTP access + v 7.4.0 - Refactored membership logic - Improve error reporting on users API (Julien Bianchi) diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb index a5944f96983..8af2c74e959 100644 --- a/lib/gitlab/ldap/authentication.rb +++ b/lib/gitlab/ldap/authentication.rb @@ -42,7 +42,7 @@ module Gitlab end def adapter - OmniAuth::LDAP::Adaptor.new(config.options) + OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys) end def config @@ -68,4 +68,4 @@ module Gitlab end end end -end \ No newline at end of file +end From add5d43b6ea0f4fbe85af4c07afe9e549b700205 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Thu, 23 Oct 2014 14:01:44 -0300 Subject: [PATCH 122/408] Close #717 Add documentation changes to: * Set the permissions of the unix socket properly * Create the directory for the socket * Make sure tmpfiles.d persists said directory The first change makes sure gitlabl can connect to the socket. The latter two avoid redis from failing to start on systemd-based systems, including recent versions of Debian. --- doc/install/installation.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/install/installation.md b/doc/install/installation.md index 7a39f2eec9f..ac6535b0c86 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -150,6 +150,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Enable Redis socket for default Debian / Ubuntu path echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf + # Grant permission to the socket to all members of the redis group + echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf + + # Create the directory which contains the socket + mkdir /var/run/redis + chown redis:redis /var/run/redis + chmod 755 /var/run/redis + # Persist the directory which contains the socket, if applicable + if [ -d /etc/tmpfiles.d ]; then + echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf + fi # Activate the changes to redis.conf sudo service redis-server restart From 7b339e61e8e4a93798807f3c90bf7179a0ecd28b Mon Sep 17 00:00:00 2001 From: Steven Sloan Date: Thu, 23 Oct 2014 14:47:28 -0400 Subject: [PATCH 123/408] update slack-notifier to 1.0.0, use raw webhook_url per slack recommendation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit per changes with slack, they’re now using “static” web hook urls that describe the team & service with IDs that don’t change if the team or service name change. their recommendation is to use the raw webhook_url instead of building it out of components to allow more flexibility this should also prevent issues cropping up with mistakes in how the urls are parsed --- Gemfile | 2 +- Gemfile.lock | 4 +- app/models/project_services/slack_service.rb | 14 ++--- doc/integration/slack.md | 30 ++++++----- features/steps/project/services.rb | 4 +- spec/models/slack_service_spec.rb | 56 +++----------------- 6 files changed, 34 insertions(+), 76 deletions(-) diff --git a/Gemfile b/Gemfile index c6be76f4ecc..c4e8511e0c4 100644 --- a/Gemfile +++ b/Gemfile @@ -143,7 +143,7 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2" gem "gemnasium-gitlab-service", "~> 0.2" # Slack integration -gem "slack-notifier", "~> 0.3.2" +gem "slack-notifier", "~> 1.0.0" # d3 gem "d3_rails", "~> 3.1.4" diff --git a/Gemfile.lock b/Gemfile.lock index 0e82f14ca9d..003d931fc43 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -488,7 +488,7 @@ GEM rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) six (0.2.0) - slack-notifier (0.3.2) + slack-notifier (1.0.0) slim (2.0.2) temple (~> 0.6.6) tilt (>= 1.3.3, < 2.1) @@ -688,7 +688,7 @@ DEPENDENCIES simplecov sinatra six - slack-notifier (~> 0.3.2) + slack-notifier (~> 1.0.0) slim spinach-rails spring (= 1.1.3) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 837002ef3c8..963f5440b6f 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -30,24 +30,20 @@ class SlackService < Service def fields [ - { type: 'text', name: 'webhook', placeholder: '' } + { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' } ] end def execute(push_data) + return unless webhook.present? + message = SlackMessage.new(push_data.merge( project_url: project_url, project_name: project_name )) - credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/) - - if credentials.present? - subdomain = credentials[1] - token = credentials[2].split("token=").last - notifier = Slack::Notifier.new(subdomain, token) - notifier.ping(message.pretext, attachments: message.attachments) - end + notifier = Slack::Notifier.new(webhook) + notifier.ping(message.pretext, attachments: message.attachments) end private diff --git a/doc/integration/slack.md b/doc/integration/slack.md index 95cb0c6fae2..f2e73f272ef 100644 --- a/doc/integration/slack.md +++ b/doc/integration/slack.md @@ -4,15 +4,23 @@ To enable Slack integration you must create an Incoming WebHooks integration on Slack; -1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services) -1. Click on the Integrations menu at the top of the page. -1. Add a new Integration. -1. Pick Incoming WebHooks -1. Choose the channel name you want to send notifications to, in the Settings section -1. Add Integrations. - - Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings". +1. [Sign in to Slack](https://slack.com/signin) + +1. Select **Configure Integrations** from the dropdown next to your team name. + +1. Select the **All Services** tab + +1. Click **Add** next to Incoming Webhooks + +1. Pick Incoming WebHooks + +1. Choose the channel name you want to send notifications to + +1. Click **Add Incoming WebHooks Integration**Add Integrations. + - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**. + +1. Copy the **Webhook URL**, we'll need this later for GitLab. -Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long. ## On GitLab @@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this 1. Fill in your Slack details - - Mark as active it - - Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain) - - Type in the token you got from Slack - - Type in the channel name you want to use (eg. #announcements) + - Mark it as active + - Paste in the webhook url you got from Slack Have fun :) diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 5bd60f99c84..aaa7d8261e0 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -108,12 +108,12 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps step 'I fill Slack settings' do check 'Active' - fill_in 'Webhook', with: 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' + fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' click_button 'Save' end step 'I should see Slack service settings saved' do - find_field('Webhook').value.should == 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' + find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' end step 'I click Pushover service link' do diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb index 526165e397c..d4840391967 100644 --- a/spec/models/slack_service_spec.rb +++ b/spec/models/slack_service_spec.rb @@ -31,71 +31,27 @@ describe SlackService do end describe "Execute" do - let(:slack) { SlackService.new } - let(:slack_service) { SlackService.new } - let(:user) { create(:user) } + let(:slack) { SlackService.new } + let(:user) { create(:user) } let(:project) { create(:project) } let(:sample_data) { GitPushService.new.sample_data(project, user) } - let(:webhook) { 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' } - let(:new_webhook) { 'https://hooks.gitlabhq.slack.com/services/cdIj4r4LfXUOySDUjp0tk3OI' } - let(:api_url) { - 'https://gitlabhq.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI' - } + let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } before do slack.stub( project: project, project_id: project.id, service_hook: true, - webhook: webhook + webhook: webhook_url ) - WebMock.stub_request(:post, api_url) + WebMock.stub_request(:post, webhook_url) end it "should call Slack API" do slack.execute(sample_data) - WebMock.should have_requested(:post, api_url).once - end - - context 'with new webhook syntax' do - before do - slack_service.stub( - project: project, - project_id: project.id, - service_hook: true, - webhook: new_webhook - ) - - WebMock.stub_request(:post, api_url) - end - - it "should call Slack API" do - slack_service.execute(sample_data) - - WebMock.should have_requested(:post, api_url).once - end - end - - context 'with new webhook syntax with slack allowed team name' do - before do - @allowed_webhook = 'https://gitlab-hq-123.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI' - slack_service.stub( - project: project, - project_id: project.id, - service_hook: true, - webhook: @allowed_webhook - ) - - WebMock.stub_request(:post, @allowed_webhook) - end - - it "should call Slack API" do - slack_service.execute(sample_data) - - WebMock.should have_requested(:post, @allowed_webhook).once - end + WebMock.should have_requested(:post, webhook_url).once end end end From 472a6621e969f3b74fa21325722e65f446912f2a Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 23 Oct 2014 22:57:16 +0200 Subject: [PATCH 124/408] Fix LDAP config lookup for provider 'ldap' --- CHANGELOG | 1 + lib/gitlab/ldap/config.rb | 27 ++++++++++++++++----------- spec/lib/gitlab/ldap/config_spec.rb | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e7708bd0c1d..69419b0adfe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ v 7.5.0 - API: Add support for Hipchat (Kevin Houdebert) - Add time zone configuration on gitlab.yml (Sullivan Senechal) - Fix LDAP authentication for Git HTTP access + - Fix LDAP config lookup for provider 'ldap' v 7.4.0 - Refactored membership logic diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index d41bfba9b0f..0cb24d0ccc1 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -16,10 +16,23 @@ module Gitlab servers.map {|server| server['provider_name'] } end + def self.valid_provider?(provider) + providers.include?(provider) + end + + def self.invalid_provider(provider) + raise "Unknown provider (#{provider}). Available providers: #{providers}" + end + def initialize(provider) - @provider = provider - invalid_provider unless valid_provider? - @options = config_for(provider) + if self.class.valid_provider?(provider) + @provider = provider + elsif provider == 'ldap' + @provider = self.class.providers.first + else + self.class.invalid_provider(provider) + end + @options = config_for(@provider) # Use @provider, not provider end def enabled? @@ -89,14 +102,6 @@ module Gitlab end end - def valid_provider? - self.class.providers.include?(provider) - end - - def invalid_provider - raise "Unknown provider (#{provider}). Available providers: #{self.class.providers}" - end - def auth_options { auth: { diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb index 76cc7f95c47..3ebb8aae243 100644 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -16,5 +16,19 @@ describe Gitlab::LDAP::Config do it "raises an error if a unknow provider is used" do expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error end + + context "if 'ldap' is the provider name" do + let(:provider) { 'ldap' } + + context "and 'ldap' is not in defined as a provider" do + before { Gitlab::LDAP::Config.stub(providers: %w{ldapmain}) } + + it "uses the first provider" do + # Fetch the provider_name attribute from 'options' so that we know + # that the 'options' Hash is not empty/nil. + expect(config.options['provider_name']).to eq('ldapmain') + end + end + end end -end \ No newline at end of file +end From 9be12dd9ca171a4665e0efe4d26d44366302fc43 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Thu, 23 Oct 2014 23:10:54 +0200 Subject: [PATCH 125/408] Merge File basename and direname into split Faster because only does one split in that case. --- lib/tasks/gitlab/import.rake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index b6ed874e11a..296ad181963 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -22,8 +22,7 @@ namespace :gitlab do repo_path[0..git_base_path.length] = '' path = repo_path.sub(/\.git$/, '') - name = File.basename path - group_name = File.dirname path + group_name, name = File.split(path) group_name = nil if group_name == '.' # Skip if group or user From 7fbc4f5b3ac2abfa8ad94cc228114462b7b94601 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 23 Oct 2014 17:49:48 -0500 Subject: [PATCH 126/408] Corrected the layout of the strength indicator to be more consistent throughout the application. Fixed a test that was looking for an outdated HTML ID --- app/assets/javascripts/password_strength.js.coffee | 6 +++--- app/assets/stylesheets/sections/profile.scss | 12 ++++++++++-- app/views/devise/passwords/edit.html.haml | 2 +- app/views/profiles/passwords/new.html.haml | 2 +- features/steps/profile/profile.rb | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee index 696a5ccf0b9..7f4a9180ae9 100644 --- a/app/assets/javascripts/password_strength.js.coffee +++ b/app/assets/javascripts/password_strength.js.coffee @@ -7,10 +7,11 @@ overwritten_rules = $(document).ready -> profileOptions = {} profileOptions.ui = - container: "#password-strength" - showVerdictsInsideProgressBar: true + showProgressBar: false + showVerdicts: false showPopover: true showErrors: true + showStatus: true errorMessages: overwritten_messages profileOptions.rules = activated: overwritten_rules @@ -19,7 +20,6 @@ $(document).ready -> deviseOptions.common = usernameField: "#user_username" deviseOptions.ui = - container: "#password-strength" showPopover: true showErrors: true showVerdicts: false diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index 2c2af7f52c5..fce0a703a0a 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -117,6 +117,14 @@ margin-bottom: 0; } -.progress { - margin-top: 10px; +.has-success input { + background-color: #C3FF88 !important; +} + +.has-error input { + background-color: #FFA0A0 !important; +} + +.has-warning input { + background-color: #FFEC8B !important; } diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index f6cbf9b82ba..182ca5e774b 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -6,7 +6,7 @@ .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token - .form-group#password-strength + .form-group = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index 746b3a721e1..42d2d0db29c 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -14,7 +14,7 @@ .form-group = f.label :current_password, class: 'control-label' .col-sm-10= f.password_field :current_password, required: true, class: 'form-control' - .form-group#password-strength + .form-group = f.label :password, class: 'control-label' .col-sm-10= f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile' .form-group diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 0f7f33fe8ce..7d3bea7878e 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -146,7 +146,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I submit new password' do fill_in :user_current_password, with: '12345678' - fill_in :user_password, with: '12345678' + fill_in :user_password_profile, with: '12345678' fill_in :user_password_confirmation, with: '12345678' click_button "Set new password" end From 705652d62538ec29d125e0ec62bebc99ddc3af71 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 24 Oct 2014 11:52:52 +0300 Subject: [PATCH 127/408] fix for public snippet --- app/controllers/snippets_controller.rb | 2 +- features/snippets/public_snippets.feature | 5 +++++ features/snippets/snippets.feature | 2 +- features/steps/shared/snippet.rb | 9 +++++++++ features/steps/snippets/public_snippets.rb | 17 +++++++++++++++++ 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 features/snippets/public_snippets.feature create mode 100644 features/steps/snippets/public_snippets.rb diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 30fb4c5552d..987694260c6 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -9,7 +9,7 @@ class SnippetsController < ApplicationController before_filter :set_title - skip_before_filter :authenticate_user!, only: [:index, :user_index] + skip_before_filter :authenticate_user!, only: [:index, :user_index, :show] respond_to :html diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature new file mode 100644 index 00000000000..6964badc413 --- /dev/null +++ b/features/snippets/public_snippets.feature @@ -0,0 +1,5 @@ +Feature: Public snippets + Scenario: Unauthenticated user should see public snippets + Given There is public "Personal snippet one" snippet + And I visit snippet page "Personal snippet one" + Then I should see snippet "Personal snippet one" diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature index 4c4e3ee2cff..6e8019c326f 100644 --- a/features/snippets/snippets.feature +++ b/features/snippets/snippets.feature @@ -25,4 +25,4 @@ Feature: Snippets Scenario: I destroy "Personal snippet one" Given I visit snippet page "Personal snippet one" And I click link "Destroy" - Then I should not see "Personal snippet one" in snippets + Then I should not see "Personal snippet one" in snippets \ No newline at end of file diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb index 432f32defce..bb596c1620a 100644 --- a/features/steps/shared/snippet.rb +++ b/features/steps/shared/snippet.rb @@ -51,4 +51,13 @@ module SharedSnippet visibility_level: Snippet::PUBLIC, author: current_user) end + + step 'There is public "Personal snippet one" snippet' do + create(:personal_snippet, + title: "Personal snippet one", + content: "Test content", + file_name: "snippet.rb", + visibility_level: Snippet::PUBLIC, + author: create(:user)) + end end diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb new file mode 100644 index 00000000000..956aa4a3e7e --- /dev/null +++ b/features/steps/snippets/public_snippets.rb @@ -0,0 +1,17 @@ +class Spinach::Features::PublicSnippets < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedSnippet + + step 'I should see snippet "Personal snippet one"' do + page.should have_no_xpath("//i[@class='public-snippet']") + end + + step 'I visit snippet page "Personal snippet one"' do + visit snippet_path(snippet) + end + + def snippet + @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") + end +end From 2bca55197bb195a5b72af7053b31249dc8c921cb Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 24 Oct 2014 11:25:39 +0200 Subject: [PATCH 128/408] Bump gitlab_git to 7.0.0.rc10 (submodules fix) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c6be76f4ecc..f6f3607cbd1 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,7 @@ gem 'omniauth-shibboleth' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '7.0.0.rc9' +gem "gitlab_git", '7.0.0.rc10' # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' diff --git a/Gemfile.lock b/Gemfile.lock index 0e82f14ca9d..314884fa36e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -179,7 +179,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.0.1.1) emoji (~> 1.0.1) - gitlab_git (7.0.0.rc9) + gitlab_git (7.0.0.rc10) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -624,7 +624,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.0.pre) gitlab-linguist (~> 3.0.0) gitlab_emoji (~> 0.0.1.1) - gitlab_git (= 7.0.0.rc9) + gitlab_git (= 7.0.0.rc10) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.1.0) gollum-lib (~> 3.0.0) From 5f7906e1635baa1aca12527ac9d9f8e84323e95d Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 24 Oct 2014 11:52:52 +0300 Subject: [PATCH 129/408] fix for public snippet --- app/controllers/snippets_controller.rb | 2 +- features/snippets/public_snippets.feature | 5 +++++ features/snippets/snippets.feature | 2 +- features/steps/shared/snippet.rb | 9 +++++++++ features/steps/snippets/public_snippets.rb | 17 +++++++++++++++++ 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 features/snippets/public_snippets.feature create mode 100644 features/steps/snippets/public_snippets.rb diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 30fb4c5552d..987694260c6 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -9,7 +9,7 @@ class SnippetsController < ApplicationController before_filter :set_title - skip_before_filter :authenticate_user!, only: [:index, :user_index] + skip_before_filter :authenticate_user!, only: [:index, :user_index, :show] respond_to :html diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature new file mode 100644 index 00000000000..6964badc413 --- /dev/null +++ b/features/snippets/public_snippets.feature @@ -0,0 +1,5 @@ +Feature: Public snippets + Scenario: Unauthenticated user should see public snippets + Given There is public "Personal snippet one" snippet + And I visit snippet page "Personal snippet one" + Then I should see snippet "Personal snippet one" diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature index 4c4e3ee2cff..6e8019c326f 100644 --- a/features/snippets/snippets.feature +++ b/features/snippets/snippets.feature @@ -25,4 +25,4 @@ Feature: Snippets Scenario: I destroy "Personal snippet one" Given I visit snippet page "Personal snippet one" And I click link "Destroy" - Then I should not see "Personal snippet one" in snippets + Then I should not see "Personal snippet one" in snippets \ No newline at end of file diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb index 432f32defce..bb596c1620a 100644 --- a/features/steps/shared/snippet.rb +++ b/features/steps/shared/snippet.rb @@ -51,4 +51,13 @@ module SharedSnippet visibility_level: Snippet::PUBLIC, author: current_user) end + + step 'There is public "Personal snippet one" snippet' do + create(:personal_snippet, + title: "Personal snippet one", + content: "Test content", + file_name: "snippet.rb", + visibility_level: Snippet::PUBLIC, + author: create(:user)) + end end diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb new file mode 100644 index 00000000000..956aa4a3e7e --- /dev/null +++ b/features/steps/snippets/public_snippets.rb @@ -0,0 +1,17 @@ +class Spinach::Features::PublicSnippets < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedSnippet + + step 'I should see snippet "Personal snippet one"' do + page.should have_no_xpath("//i[@class='public-snippet']") + end + + step 'I visit snippet page "Personal snippet one"' do + visit snippet_path(snippet) + end + + def snippet + @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") + end +end From 16a10eb1cd4a5447da9d50b1eba25f020dc8f6b7 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 23 Oct 2014 22:57:16 +0200 Subject: [PATCH 130/408] Fix LDAP config lookup for provider 'ldap' --- CHANGELOG | 1 + lib/gitlab/ldap/config.rb | 27 ++++++++++++++++----------- spec/lib/gitlab/ldap/config_spec.rb | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 561a23538e7..5a494cccc69 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ v 7.4.1 - Fix LDAP authentication for Git HTTP access + - Fix LDAP config lookup for provider 'ldap' v 7.4.0 - Refactored membership logic diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index d41bfba9b0f..0cb24d0ccc1 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -16,10 +16,23 @@ module Gitlab servers.map {|server| server['provider_name'] } end + def self.valid_provider?(provider) + providers.include?(provider) + end + + def self.invalid_provider(provider) + raise "Unknown provider (#{provider}). Available providers: #{providers}" + end + def initialize(provider) - @provider = provider - invalid_provider unless valid_provider? - @options = config_for(provider) + if self.class.valid_provider?(provider) + @provider = provider + elsif provider == 'ldap' + @provider = self.class.providers.first + else + self.class.invalid_provider(provider) + end + @options = config_for(@provider) # Use @provider, not provider end def enabled? @@ -89,14 +102,6 @@ module Gitlab end end - def valid_provider? - self.class.providers.include?(provider) - end - - def invalid_provider - raise "Unknown provider (#{provider}). Available providers: #{self.class.providers}" - end - def auth_options { auth: { diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb index 76cc7f95c47..3ebb8aae243 100644 --- a/spec/lib/gitlab/ldap/config_spec.rb +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -16,5 +16,19 @@ describe Gitlab::LDAP::Config do it "raises an error if a unknow provider is used" do expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error end + + context "if 'ldap' is the provider name" do + let(:provider) { 'ldap' } + + context "and 'ldap' is not in defined as a provider" do + before { Gitlab::LDAP::Config.stub(providers: %w{ldapmain}) } + + it "uses the first provider" do + # Fetch the provider_name attribute from 'options' so that we know + # that the 'options' Hash is not empty/nil. + expect(config.options['provider_name']).to eq('ldapmain') + end + end + end end -end \ No newline at end of file +end From d4ae4fe670c75ffbb3974734aafbcdc667e53172 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 24 Oct 2014 11:25:39 +0200 Subject: [PATCH 131/408] Bump gitlab_git to 7.0.0.rc10 (submodules fix) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c6be76f4ecc..f6f3607cbd1 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,7 @@ gem 'omniauth-shibboleth' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '7.0.0.rc9' +gem "gitlab_git", '7.0.0.rc10' # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' diff --git a/Gemfile.lock b/Gemfile.lock index 0e82f14ca9d..314884fa36e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -179,7 +179,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.0.1.1) emoji (~> 1.0.1) - gitlab_git (7.0.0.rc9) + gitlab_git (7.0.0.rc10) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -624,7 +624,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.0.pre) gitlab-linguist (~> 3.0.0) gitlab_emoji (~> 0.0.1.1) - gitlab_git (= 7.0.0.rc9) + gitlab_git (= 7.0.0.rc10) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.1.0) gollum-lib (~> 3.0.0) From 9712fbcdd366c173e2ec277a617a4e690f6a86e9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 24 Oct 2014 13:30:04 +0300 Subject: [PATCH 132/408] Bump to 7.4.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ba7f754d0c3..815da58b7a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.0 +7.4.1 From 706b6b5acb8e900e3d43e810d83829f9931bb9ec Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Thu, 23 Oct 2014 23:39:48 +0200 Subject: [PATCH 133/408] Fix import.rake failed import if project name is also an existing namespace. E.g., when trying to import group/root.git, that would fail if there is an user called root. --- lib/tasks/gitlab/import.rake | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index 159568f2883..e0297023bfc 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -15,8 +15,6 @@ namespace :gitlab do git_base_path = Gitlab.config.gitlab_shell.repos_path repos_to_import = Dir.glob(git_base_path + '/**/*.git') - namespaces = Namespace.pluck(:path) - repos_to_import.each do |repo_path| # strip repo base path repo_path[0..git_base_path.length] = '' @@ -26,12 +24,6 @@ namespace :gitlab do group_name = File.dirname path group_name = nil if group_name == '.' - # Skip if group or user - if namespaces.include?(name) - puts "Skipping #{project.name} due to namespace conflict with group or user".yellow - next - end - puts "Processing #{repo_path}".yellow if path =~ /\.wiki\Z/ @@ -53,9 +45,9 @@ namespace :gitlab do # find group namespace if group_name - group = Group.find_by(path: group_name) + group = Namespace.find_by(path: group_name) # create group namespace - if !group + unless group group = Group.new(:name => group_name) group.path = group_name group.owner = user From 7f97a1277de78bcd86d68978e9ec29a2548fc144 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 24 Oct 2014 19:24:49 +0300 Subject: [PATCH 134/408] internal snippets: fix exposing of title --- CHANGELOG | 5 +++++ VERSION | 2 +- app/finders/snippets_finder.rb | 2 ++ spec/finders/snippets_finder_spec.rb | 7 +++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5a494cccc69..4428bae4ebd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ +v 7.4.2 + - Fix internal snippet exposing for unauthenticated users + v 7.4.1 - Fix LDAP authentication for Git HTTP access - Fix LDAP config lookup for provider 'ldap' + - Fix public snippets + - Fix 500 error on projects with nested submodules v 7.4.0 - Refactored membership logic diff --git a/VERSION b/VERSION index 815da58b7a9..f8cb1fa110d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.1 +7.4.2 diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index b29ab6cf40b..4b0c69f2d2f 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -29,6 +29,8 @@ class SnippetsFinder def by_user(current_user, user, scope) snippets = user.snippets.fresh.non_expired + return snippets.are_public unless current_user + if user == current_user case scope when 'are_internal' then diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 5af76968183..c645cbc964c 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -64,6 +64,13 @@ describe SnippetsFinder do snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user) snippets.should include(@snippet1, @snippet2, @snippet3) end + + it "returns only public snippets if unauthenticated user" do + snippets = SnippetsFinder.new.execute(nil, filter: :by_user, user: user) + snippets.should include(@snippet3) + snippets.should_not include(@snippet2, @snippet1) + end + end context 'by_project filter' do From 250d582bdd40e25f0aa16c76fc7a6ff31b113c22 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 24 Oct 2014 21:48:01 +0300 Subject: [PATCH 135/408] update patch document --- doc/release/patch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release/patch.md b/doc/release/patch.md index bcc14568fc8..3ee55028b1f 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -26,6 +26,6 @@ Otherwise include it in the monthly release and note there was a regression fix 1. Apply the patch to GitLab Cloud and the private GitLab development server 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. Cherry-pick the changelog update back into master +1. Create blog post 1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) -1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only) From 5c74abb590b7a519b0e135665841b98f3095295c Mon Sep 17 00:00:00 2001 From: jmsche Date: Fri, 24 Oct 2014 22:00:21 +0200 Subject: [PATCH 136/408] Fixed missing end-of-code line in 7.4 upgrade doc --- doc/update/7.3-to-7.4.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 69d86fb06ed..3f471500c82 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -9,6 +9,7 @@ ```bash cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` ### 2. Get latest code From e4912243c1110f7194ff4e9a3da6f23a3ccac113 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Fri, 26 Sep 2014 16:11:17 +0200 Subject: [PATCH 137/408] Transform remove blob link into button. --- app/views/projects/blob/_actions.html.haml | 3 ++- features/steps/project/source/browse_files.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 64c19a57803..812d88a8730 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -23,5 +23,6 @@ tree_join(@commit.sha, @path)), class: 'btn btn-small' - if allowed_tree_edit? - = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do + = button_tag class: 'remove-blob btn btn-small btn-remove', + 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do Remove diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 665f5d6d195..ddd501d4f88 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -78,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I click on "Remove"' do - click_link 'Remove' + click_button 'Remove' end step 'I click on "Remove file"' do From 000af8d5d7087cadbfd8ad677fb941e74a8ee3c7 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 24 Oct 2014 17:19:35 -0500 Subject: [PATCH 138/408] Moved require from application.js to password_strength.js Corrected div id for profile password/edit Added first spinach tests --- app/assets/javascripts/application.js.coffee | 1 - .../javascripts/password_strength.js.coffee | 1 + app/views/profiles/passwords/edit.html.haml | 2 +- features/profile/profile.feature | 19 +++++++++++ features/steps/profile/profile.rb | 34 +++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 493babad856..faf725109f5 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -18,7 +18,6 @@ #= require jquery.turbolinks #= require turbolinks #= require bootstrap -#= require pwstrength-bootstrap-1.2.2 #= require password_strength #= require select2 #= require raphael diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee index 7f4a9180ae9..33b4d2e0f69 100644 --- a/app/assets/javascripts/password_strength.js.coffee +++ b/app/assets/javascripts/password_strength.js.coffee @@ -1,3 +1,4 @@ +#= require pwstrength-bootstrap-1.2.2 overwritten_messages = wordSimilarToUsername: "Your password should not contain your username" diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 8e84d312194..425200ff523 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -21,7 +21,7 @@ %div = link_to "Forgot your password?", reset_profile_password_path, method: :put - .form-group#password-strength + .form-group = f.label :password, 'New password', class: 'control-label' .col-sm-10 = f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile' diff --git a/features/profile/profile.feature b/features/profile/profile.feature index d2125e013bc..d7fa370fe2a 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -83,3 +83,22 @@ Feature: Profile Given I visit profile design page When I change my code preview theme Then I should receive feedback that the changes were saved + + @javascript + Scenario: I see the password strength indicator + Given I visit profile password page + When I try to set a weak password + Then I should see the input field yellow + + @javascript + Scenario: I see the password strength indicator error + Given I visit profile password page + When I try to set a short password + Then I should see the input field red + And I should see the password error message + + @javascript + Scenario: I see the password strength indicator with success + Given I visit profile password page + When I try to set a strong password + Then I should see the input field green \ No newline at end of file diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 7d3bea7878e..6d747b65bae 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -64,6 +64,24 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end end + step 'I try to set a weak password' do + within '.update-password' do + fill_in "user_password_profile", with: "22233344" + end + end + + step 'I try to set a short password' do + within '.update-password' do + fill_in "user_password_profile", with: "short" + end + end + + step 'I try to set a strong password' do + within '.update-password' do + fill_in "user_password_profile", with: "Itulvo9z8uud%$" + end + end + step 'I change my password' do within '.update-password' do fill_in "user_current_password", with: "12345678" @@ -86,6 +104,22 @@ class Spinach::Features::Profile < Spinach::FeatureSteps page.should have_content "You must provide a valid current password" end + step 'I should see the input field yellow' do + page.should have_css 'div.has-warning' + end + + step 'I should see the input field green' do + page.should have_css 'div.has-success' + end + + step 'I should see the input field red' do + page.should have_css 'div.has-error' + end + + step 'I should see the password error message' do + page.should have_content 'Your password is too short' + end + step "I should see a password error message" do page.should have_content "Password confirmation doesn't match" end From 37f33393b2e6aa18c416cb7a55d8de80dde0af58 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 24 Oct 2014 19:17:00 -0500 Subject: [PATCH 139/408] Changed colors to match GitLab's red & green and softened the yellow. --- app/assets/stylesheets/sections/profile.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index fce0a703a0a..b9f4e317e9c 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -118,13 +118,13 @@ } .has-success input { - background-color: #C3FF88 !important; + background-color: #D6F1D7 !important; } .has-error input { - background-color: #FFA0A0 !important; + background-color: #F3CECE !important; } .has-warning input { - background-color: #FFEC8B !important; + background-color: #FFE9A4 !important; } From a9fadce361163e97eb1de0ec62e4235ff0fa3daa Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 20 Oct 2014 17:50:53 +0200 Subject: [PATCH 140/408] Create dev fixture projects with fixed visibility --- db/fixtures/development/04_project.rb | 78 +++++++++---------- .../development/07_projects_visibility.rb | 38 +++++++++ .../{07_milestones.rb => 08_milestones.rb} | 0 .../fixtures_development_helper.rb | 8 ++ lib/gitlab/seeder.rb | 6 +- 5 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 db/fixtures/development/07_projects_visibility.rb rename db/fixtures/development/{07_milestones.rb => 08_milestones.rb} (100%) create mode 100644 db/fixtures/development/fixtures_development_helper.rb diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index ae4c0550a4f..a39e7ac028c 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -1,52 +1,48 @@ -require 'sidekiq/testing' +Gitlab::Seeder.quiet do + project_urls = [ + 'https://github.com/documentcloud/underscore.git', + 'https://gitlab.com/gitlab-org/gitlab-ce.git', + 'https://gitlab.com/gitlab-org/gitlab-ci.git', + 'https://gitlab.com/gitlab-org/gitlab-shell.git', + 'https://gitlab.com/gitlab-org/gitlab-test.git', + 'https://github.com/twitter/flight.git', + 'https://github.com/twitter/typeahead.js.git', + 'https://github.com/h5bp/html5-boilerplate.git', + ] -Sidekiq::Testing.inline! do - Gitlab::Seeder.quiet do - project_urls = [ - 'https://github.com/documentcloud/underscore.git', - 'https://gitlab.com/gitlab-org/gitlab-ce.git', - 'https://gitlab.com/gitlab-org/gitlab-ci.git', - 'https://gitlab.com/gitlab-org/gitlab-shell.git', - 'https://gitlab.com/gitlab-org/gitlab-test.git', - 'https://github.com/twitter/flight.git', - 'https://github.com/twitter/typeahead.js.git', - 'https://github.com/h5bp/html5-boilerplate.git', - ] + project_urls.each do |url| + group_path, project_path = url.split('/')[-2..-1] - project_urls.each_with_index do |url, i| - group_path, project_path = url.split('/')[-2..-1] + group = Group.find_by(path: group_path) - group = Group.find_by(path: group_path) + unless group + group = Group.new( + name: group_path.titleize, + path: group_path + ) + group.description = Faker::Lorem.sentence + group.save - unless group - group = Group.new( - name: group_path.titleize, - path: group_path - ) - group.description = Faker::Lorem.sentence - group.save + group.add_owner(User.first) + end - group.add_owner(User.first) - end + project_path.gsub!('.git', '') - project_path.gsub!(".git", "") + params = { + import_url: url, + namespace_id: group.id, + name: project_path.titleize, + description: Faker::Lorem.sentence, + visibility_level: Gitlab::VisibilityLevel.values.sample + } - params = { - import_url: url, - namespace_id: group.id, - name: project_path.titleize, - description: Faker::Lorem.sentence, - visibility_level: Gitlab::VisibilityLevel.values.sample - } + project = Projects::CreateService.new(User.first, params).execute - project = Projects::CreateService.new(User.first, params).execute - - if project.valid? - print '.' - else - puts project.errors.full_messages - print 'F' - end + if project.valid? + print '.' + else + puts project.errors.full_messages + print 'F' end end end diff --git a/db/fixtures/development/07_projects_visibility.rb b/db/fixtures/development/07_projects_visibility.rb new file mode 100644 index 00000000000..c3287584a07 --- /dev/null +++ b/db/fixtures/development/07_projects_visibility.rb @@ -0,0 +1,38 @@ +require Rails.root.join('db', 'fixtures', Rails.env, 'fixtures_development_helper') + +Gitlab::Seeder.quiet do + Gitlab::VisibilityLevel.options.each do |visibility_label, visibility_value| + visibility_label_downcase = visibility_label.downcase + begin + user = User.seed(:username) do |s| + username = "#{visibility_label_downcase}-owner" + s.username = username + s.name = "#{visibility_label} Owner" + s.email = "#{username}@example.com" + s.password = '12345678' + s.confirmed_at = DateTime.now + end[0] + + # import_url does not work for local paths, + # so we just copy the template repository in. + unless Project.find_with_namespace("#{user.namespace.id}/"\ + "#{visibility_label_downcase}") + params = { + name: "#{visibility_label} Project", + description: "#{visibility_label} Project description", + namespace_id: user.namespace.id, + visibility_level: visibility_value, + } + project = Projects::CreateService.new(user, params).execute + new_path = project.repository.path + FileUtils.rm_rf(new_path) + FileUtils.cp_r(FixturesDevelopmentHelper.template_project.repository.path, + new_path) + end + + print '.' + rescue ActiveRecord::RecordNotSaved + print 'F' + end + end +end diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/08_milestones.rb similarity index 100% rename from db/fixtures/development/07_milestones.rb rename to db/fixtures/development/08_milestones.rb diff --git a/db/fixtures/development/fixtures_development_helper.rb b/db/fixtures/development/fixtures_development_helper.rb new file mode 100644 index 00000000000..22a7834bbee --- /dev/null +++ b/db/fixtures/development/fixtures_development_helper.rb @@ -0,0 +1,8 @@ +module FixturesDevelopmentHelper + class << self + def template_project + @template_project ||= Project. + find_with_namespace('gitlab-org/gitlab-test') + end + end +end diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb index 31aa3528c4c..e816eedab9e 100644 --- a/lib/gitlab/seeder.rb +++ b/lib/gitlab/seeder.rb @@ -1,9 +1,13 @@ +require 'sidekiq/testing' + module Gitlab class Seeder def self.quiet mute_mailer SeedFu.quiet = true - yield + Sidekiq::Testing.inline! do + yield + end SeedFu.quiet = false puts "\nOK".green end From d7476123852a7164b63e097b4aac73b04d1eca5c Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 09:20:43 +0100 Subject: [PATCH 141/408] Failing feature for dashboard issues when user has authored issues on projects he is not a member of. --- features/steps/dashboard/issues.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb index 6b5f88e5895..2a5850d091b 100644 --- a/features/steps/dashboard/issues.rb +++ b/features/steps/dashboard/issues.rb @@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps step 'I should see issues authored by me' do should_see(authored_issue) + should_see(authored_issue_on_public_project) should_not_see(assigned_issue) should_not_see(other_issue) end @@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps step 'I have authored issues' do authored_issue + authored_issue_on_public_project end step 'I have assigned issues' do @@ -64,6 +66,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps @other_issue ||= create :issue, project: project end + def authored_issue_on_public_project + @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project + end + def project @project ||= begin project =create :project @@ -71,4 +77,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps project end end + + def public_project + @public_project ||= create :project, :public + end end From 93532432dce129e9075ffa18bc99d77019f63eb2 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 09:30:39 +0100 Subject: [PATCH 142/408] Failing feature for dashboard merge requests when user has authored issues on forked project source. --- features/steps/dashboard/merge_requests.rb | 29 ++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb index 95c378fa201..64ba04079e6 100644 --- a/features/steps/dashboard/merge_requests.rb +++ b/features/steps/dashboard/merge_requests.rb @@ -10,6 +10,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps step 'I should see merge requests authored by me' do should_see(authored_merge_request) + should_see(authored_merge_request_from_fork) should_not_see(assigned_merge_request) should_not_see(other_merge_request) end @@ -22,6 +23,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps step 'I have authored merge requests' do authored_merge_request + authored_merge_request_from_fork end step 'I have assigned merge requests' do @@ -57,11 +59,26 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps end def authored_merge_request - @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project + @authored_merge_request ||= create :merge_request, + source_branch: 'simple_merge_request', + author: current_user, + target_project: project, + source_project: project end def other_merge_request - @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project + @other_merge_request ||= create :merge_request, + source_branch: '2_3_notes_fix', + target_project: project, + source_project: project + end + + def authored_merge_request_from_fork + @authored_merge_request_from_fork ||= create :merge_request, + source_branch: 'basic_page', + author: current_user, + target_project: public_project, + source_project: forked_project end def project @@ -71,4 +88,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps project end end + + def public_project + @public_project ||= create :project, :public + end + + def forked_project + @forked_project ||= Projects::ForkService.new(public_project, current_user).execute + end end From 5e017a4566495b0024ab065e7ceab279ff77e030 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 09:53:35 +0100 Subject: [PATCH 143/408] Assigned merge request should show on dashboard mr overiew feature spec. --- features/steps/dashboard/merge_requests.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb index 64ba04079e6..75e53173d3f 100644 --- a/features/steps/dashboard/merge_requests.rb +++ b/features/steps/dashboard/merge_requests.rb @@ -4,7 +4,9 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps step 'I should see merge requests assigned to me' do should_see(assigned_merge_request) + should_see(assigned_merge_request_from_fork) should_not_see(authored_merge_request) + should_not_see(authored_merge_request_from_fork) should_not_see(other_merge_request) end @@ -12,6 +14,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps should_see(authored_merge_request) should_see(authored_merge_request_from_fork) should_not_see(assigned_merge_request) + should_not_see(assigned_merge_request_from_fork) should_not_see(other_merge_request) end @@ -28,6 +31,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps step 'I have assigned merge requests' do assigned_merge_request + assigned_merge_request_from_fork end step 'I have other merge requests' do @@ -55,7 +59,10 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps end def assigned_merge_request - @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project + @assigned_merge_request ||= create :merge_request, + assignee: current_user, + target_project: project, + source_project: project end def authored_merge_request @@ -81,6 +88,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps source_project: forked_project end + def assigned_merge_request_from_fork + @assigned_merge_request_from_fork ||= create :merge_request, + source_branch: 'basic_page_fix', + assignee: current_user, + target_project: public_project, + source_project: forked_project + end + def project @project ||= begin project =create :project From d3bdd3ba67dda8b8392770a2b6e4a7473ec4d42d Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 10:02:20 +0100 Subject: [PATCH 144/408] Do not filter out issues and merge requests related to user right away. --- app/finders/issuable_finder.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 56c4f22120d..d0574240511 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -48,7 +48,7 @@ class IssuableFinder else [] end - elsif current_user && params[:authorized_only].presence + elsif current_user && params[:authorized_only].presence && !current_user_related? klass.of_projects(current_user.authorized_projects).references(:project) else klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project) @@ -142,4 +142,8 @@ class IssuableFinder def project Project.where(id: params[:project_id]).first if params[:project_id].present? end + + def current_user_related? + params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' + end end From 40c8f159a24b661e9f27dfcde492c4d2a6bbfbe2 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 27 Oct 2014 11:51:31 +0200 Subject: [PATCH 145/408] Fix raw view for public snippets --- app/controllers/snippets_controller.rb | 2 +- features/snippets/public_snippets.feature | 5 +++++ features/steps/snippets/public_snippets.rb | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 987694260c6..bf3312fedc8 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -9,7 +9,7 @@ class SnippetsController < ApplicationController before_filter :set_title - skip_before_filter :authenticate_user!, only: [:index, :user_index, :show] + skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw] respond_to :html diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature index 6964badc413..c2afb63b6d8 100644 --- a/features/snippets/public_snippets.feature +++ b/features/snippets/public_snippets.feature @@ -3,3 +3,8 @@ Feature: Public snippets Given There is public "Personal snippet one" snippet And I visit snippet page "Personal snippet one" Then I should see snippet "Personal snippet one" + + Scenario: Unauthenticated user should see raw public snippets + Given There is public "Personal snippet one" snippet + And I visit snippet raw page "Personal snippet one" + Then I should see raw snippet "Personal snippet one" diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb index 956aa4a3e7e..67669dc0a69 100644 --- a/features/steps/snippets/public_snippets.rb +++ b/features/steps/snippets/public_snippets.rb @@ -7,10 +7,18 @@ class Spinach::Features::PublicSnippets < Spinach::FeatureSteps page.should have_no_xpath("//i[@class='public-snippet']") end + step 'I should see raw snippet "Personal snippet one"' do + page.should have_text(snippet.content) + end + step 'I visit snippet page "Personal snippet one"' do visit snippet_path(snippet) end + step 'I visit snippet raw page "Personal snippet one"' do + visit raw_snippet_path(snippet) + end + def snippet @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end From d504ca8a0c696b31eaf383f97f47e08afac23084 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 13:02:12 +0100 Subject: [PATCH 146/408] Add settings to disable email sending from GitLab. --- config/gitlab.yml.example | 2 ++ config/initializers/1_settings.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 2ca6abac576..bb0ffae0b70 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -39,6 +39,8 @@ production: &base # time_zone: 'UTC' ## Email settings + # Uncomment and set to false if you need to disable email sending from GitLab (default: true) + # email_enabled: true # Email address used in the "From" field in mails sent by GitLab email_from: example@example.com diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4670791ddb0..27bb83784ba 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -95,6 +95,7 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" +Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' From d78e80fa74777e886ca131614f3b4d3f06bf9fff Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 13:05:50 +0100 Subject: [PATCH 147/408] Add email interceptor to prevent mail sending if email sending is disabled. --- config/initializers/disable_email_interceptor.rb | 2 ++ lib/disable_email_interceptor.rb | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 config/initializers/disable_email_interceptor.rb create mode 100644 lib/disable_email_interceptor.rb diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb new file mode 100644 index 00000000000..c76a6b8b19f --- /dev/null +++ b/config/initializers/disable_email_interceptor.rb @@ -0,0 +1,2 @@ +# Interceptor in lib/disable_email_interceptor.rb +ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb new file mode 100644 index 00000000000..1b80be112a4 --- /dev/null +++ b/lib/disable_email_interceptor.rb @@ -0,0 +1,8 @@ +# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails +class DisableEmailInterceptor + + def self.delivering_email(message) + message.perform_deliveries = false + Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}" + end +end From 8101640c33d240fc5b29c3dec33c56b67325a89f Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Mon, 27 Oct 2014 08:30:57 -0500 Subject: [PATCH 148/408] Change update recommendation --- ...x-or-7.x-to-7.4.md => 6.x-or-7.x-to-7.3.md} | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename doc/update/{6.x-or-7.x-to-7.4.md => 6.x-or-7.x-to-7.3.md} (95%) diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.3.md similarity index 95% rename from doc/update/6.x-or-7.x-to-7.4.md rename to doc/update/6.x-or-7.x-to-7.3.md index 2fa6889af73..66853634d38 100644 --- a/doc/update/6.x-or-7.x-to-7.4.md +++ b/doc/update/6.x-or-7.x-to-7.3.md @@ -1,6 +1,6 @@ -# From 6.x or 7.x to 7.4 +# From 6.x or 7.x to 7.3 -This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4. +This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3. ## Global issue numbers @@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut For GitLab Community Edition: ```bash -sudo -u git -H git checkout 7-4-stable +sudo -u git -H git checkout 7-3-stable ``` OR @@ -78,7 +78,7 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout 7-4-stable-ee +sudo -u git -H git checkout 7-3-stable-ee ``` ## 4. Install additional packages @@ -154,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab TIP: to see what changed in `gitlab.yml.example` in this release use next command: ``` -git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example ``` -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings. * Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings. -* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings. * Copy rack attack middleware config ```bash From fd44d0b6c23443cc899316926e4f8a3a9d9ad032 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 27 Oct 2014 16:02:21 +0200 Subject: [PATCH 149/408] update release doc --- doc/release/monthly.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 36fc0b1dea6..b9b4b0082dd 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -11,6 +11,7 @@ NOTE: This is a guide for GitLab developers. A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. ### **3. Create an overall issue** + Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching. Replace the dates with actual dates based on the number of workdays before the release. @@ -179,7 +180,11 @@ So you should use stable branch for future code chages related to release. # **4 workdays before release - Release RC1** -### **1. Update GitLab.com** +### **1. Determine QA person + +Notify person of QA day. + +### **2. Update GitLab.com** Merge the RC1 EE code into GitLab.com. Once the build is green, create a package. @@ -187,18 +192,19 @@ If there are big database migrations consider testing them with the production d Try to deploy in the morning. It is important to do this as soon as possible, so we can catch any errors before we release the full version. -### **2. Prepare the blog post** +### **3. Prepare the blog post** - Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. - Check the changelog of CE and EE for important changes. - Create a WIP MR for the blog post - Ask Dmitriy to add screenshots to the WIP MR. -- Decide with team who will be the MVP user. +- Decide with team who will be the MVP user. +- Create WIP MR for adding MVP to MVP page on website - Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. - Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor) - After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments -### **3. Create a regressions issue** +### **4. Create a regressions issue** On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text: @@ -312,3 +318,14 @@ Proposed tweet for CE "GitLab X.X is released! It brings *** " Update GitLab.com from RC1 to the released package. # **25th - Release GitLab CI** + +- Create the update guid `doc/x.x-to-x.x.md`. +- Update CHANGELOG +- Bump version +- Create annotated tags `git tag -a vx.x.0 -m 'Version x.x.0' xxxxx` +- Create stable branch `x-x-stable` +- Create GitHub release post +- Post to blog about release +- Post to twitter + + From 3b6737f970eec80d82ddc78d098b3b3fccbe4acf Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 15:08:37 +0100 Subject: [PATCH 150/408] Add interceptor test. --- spec/lib/disable_email_interceptor_spec.rb | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 spec/lib/disable_email_interceptor_spec.rb diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb new file mode 100644 index 00000000000..29ec54b13d1 --- /dev/null +++ b/spec/lib/disable_email_interceptor_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe DisableEmailInterceptor do + before do + ActionMailer::Base.register_interceptor(DisableEmailInterceptor) + end + + it 'should not send emails' do + Gitlab.config.gitlab.stub(:email_enabled).and_return(false) + expect { + deliver_mail + }.not_to change(ActionMailer::Base.deliveries, :count) + end + + after do + Mail.class_variable_set(:@@delivery_interceptors, []) + end + + def deliver_mail + key = create :personal_key + Notify.new_ssh_key_email(key.id) + end +end From 28c08775b3d1994d3a8c5057534c704ff9da4bae Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 15:11:03 +0100 Subject: [PATCH 151/408] Add a comment in interceptor spec. --- spec/lib/disable_email_interceptor_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb index 29ec54b13d1..8bf6ee2ed50 100644 --- a/spec/lib/disable_email_interceptor_spec.rb +++ b/spec/lib/disable_email_interceptor_spec.rb @@ -13,6 +13,9 @@ describe DisableEmailInterceptor do end after do + # Removing interceptor from the list because unregister_interceptor is + # implemented in later version of mail gem + # See: https://github.com/mikel/mail/pull/705 Mail.class_variable_set(:@@delivery_interceptors, []) end From d08bb4b3a467d730009a97c79573854af79147d6 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 27 Oct 2014 16:08:11 +0100 Subject: [PATCH 152/408] Add project name to rename repository section --- app/views/projects/edit.html.haml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f48f4bb2953..79be310c2cf 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -13,7 +13,11 @@ = f.label :name, class: 'control-label' do Project name .col-sm-10 - = f.text_field :name, placeholder: "Example Project", class: "form-control" + = f.text_field :name, placeholder: "Example Project", class: "form-control", readonly: true + %p.hint + Rename the project at + %strong Rename repository + section. .form-group @@ -124,6 +128,12 @@ .errors-holder .panel-body = form_for(@project, html: { class: 'form-horizontal' }) do |f| + .form-group.project_name_holder + = f.label :name, class: 'control-label' do + Project name + .col-sm-9 + .form-group + = f.text_field :name, placeholder: "Example Project", class: "form-control" .form-group = f.label :path, class: 'control-label' do %span Path From 951abce5627ca55cb66511cdbd4eda4db577f78b Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 27 Oct 2014 18:06:01 +0100 Subject: [PATCH 153/408] Factor behaviors.scss constants --- app/assets/stylesheets/behaviors.scss | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index be4c4d07f1c..469f4f296ae 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -1,12 +1,22 @@ // Details //-------- -.js-details-container .content { display: none; } -.js-details-container .content.hide { display: block; } -.js-details-container.open .content { display: block; } -.js-details-container.open .content.hide { display: none; } +.js-details-container { + .content { + display: none; + &.hide { display: block; } + } + &.open .content { + display: block; + &.hide { display: none; } + } +} // Toggle between two states. -.js-toggler-container .turn-on { display: block; } -.js-toggler-container .turn-off { display: none; } -.js-toggler-container.on .turn-on { display: none; } -.js-toggler-container.on .turn-off { display: block; } +.js-toggler-container { + .turn-on { display: block; } + .turn-off { display: none; } + &.on { + .turn-on { display: none; } + .turn-off { display: block; } + } +} From 2d187976be893a764aea9699e6b5a1cb15c34d84 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Wed, 22 Oct 2014 00:19:16 +0200 Subject: [PATCH 154/408] Run user select Js only where needed Transform current implementation into regular Coffescript classes so that the same call method can be reused on the dispatcher as for other classes. --- app/assets/javascripts/dispatcher.js.coffee | 6 +++ .../project_users_select.js.coffee | 19 ++++--- app/assets/javascripts/users_select.js.coffee | 53 ++++++++++--------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index fb0560dba49..b070aba2ef4 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -62,6 +62,7 @@ class Dispatcher new TeamMembers() when 'groups:members' new GroupMembers() + new UsersSelect() when 'groups:new', 'groups:edit', 'admin:groups:edit' new GroupAvatar() when 'projects:tree:show' @@ -81,6 +82,8 @@ class Dispatcher when 'admin' new Admin() switch path[1] + when 'groups' + new UsersSelect() when 'projects' new NamespaceSelect() when 'dashboard' @@ -95,6 +98,8 @@ class Dispatcher new ProjectNew() when 'show' new ProjectShow() + when 'issues', 'merge_requests' + new ProjectUsersSelect() when 'wikis' new Wikis() shortcut_handler = new ShortcutsNavigation() @@ -103,6 +108,7 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' shortcut_handler = new ShortcutsNavigation() + new UsersSelect() # If we haven't installed a custom shortcut handler, install the default one diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee index cfbcd5108c8..7fb33926096 100644 --- a/app/assets/javascripts/project_users_select.js.coffee +++ b/app/assets/javascripts/project_users_select.js.coffee @@ -1,6 +1,6 @@ -@projectUsersSelect = - init: -> - $('.ajax-project-users-select').each (i, select) -> +class @ProjectUsersSelect + constructor: -> + $('.ajax-project-users-select').each (i, select) => project_id = $(select).data('project-id') || $('body').data('project-id') $(select).select2 @@ -28,14 +28,16 @@ Api.user(id, callback) - formatResult: projectUsersSelect.projectUserFormatResult - formatSelection: projectUsersSelect.projectUserFormatSelection + formatResult: (args...) => + @formatResult(args...) + formatSelection: (args...) => + @formatSelection(args...) dropdownCssClass: "ajax-project-users-dropdown" dropdownAutoWidth: true escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results m - projectUserFormatResult: (user) -> + formatResult: (user) -> if user.avatar_url avatar = user.avatar_url else @@ -52,8 +54,5 @@
    #{user.username}
    " - projectUserFormatSelection: (user) -> + formatSelection: (user) -> user.name - -$ -> - projectUsersSelect.init() diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 86318bd7d94..9eee7406511 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -1,5 +1,30 @@ -$ -> - userFormatResult = (user) -> +class @UsersSelect + constructor: -> + $('.ajax-users-select').each (i, select) => + $(select).select2 + placeholder: "Search for a user" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.users query.term, (users) -> + data = { results: users } + query.callback(data) + + initSelection: (element, callback) -> + id = $(element).val() + if id isnt "" + Api.user(id, callback) + + + formatResult: (args...) => + @formatResult(args...) + formatSelection: (args...) => + @formatSelection(args...) + dropdownCssClass: "ajax-users-dropdown" + escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results + m + + formatResult: (user) -> if user.avatar_url avatar = user.avatar_url else @@ -11,27 +36,5 @@ $ ->
    #{user.username}
    " - userFormatSelection = (user) -> + formatSelection: (user) -> user.name - - $('.ajax-users-select').each (i, select) -> - $(select).select2 - placeholder: "Search for a user" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.users query.term, (users) -> - data = { results: users } - query.callback(data) - - initSelection: (element, callback) -> - id = $(element).val() - if id isnt "" - Api.user(id, callback) - - - formatResult: userFormatResult - formatSelection: userFormatSelection - dropdownCssClass: "ajax-users-dropdown" - escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results - m From 6093d4fd831957a5ebac4925e4b660f629b9ee60 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Mon, 27 Oct 2014 23:28:02 -0700 Subject: [PATCH 155/408] fix markdown formatting fix markdown formatting issue --- doc/update/6.x-or-7.x-to-7.3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/update/6.x-or-7.x-to-7.3.md b/doc/update/6.x-or-7.x-to-7.3.md index 66853634d38..ae086cc4435 100644 --- a/doc/update/6.x-or-7.x-to-7.3.md +++ b/doc/update/6.x-or-7.x-to-7.3.md @@ -267,6 +267,7 @@ mysql> \q # Set production -> username: git # Set production -> password: the password your replaced $password with earlier sudo -u git -H editor /home/git/gitlab/config/database.yml +``` ## Things went south? Revert to previous version (6.0) From 56ffa0fba47c8e81c2980140988cbdd2c0520b9d Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 12 Oct 2014 05:19:10 -0700 Subject: [PATCH 156/408] improve ssh key emails --- app/views/notify/new_ssh_key_email.html.haml | 2 +- app/views/notify/new_ssh_key_email.text.erb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml index deb0822d8f2..63b0cbbd205 100644 --- a/app/views/notify/new_ssh_key_email.html.haml +++ b/app/views/notify/new_ssh_key_email.html.haml @@ -6,5 +6,5 @@ title: %code= @key.title %p - If this key was added in error, you can remove it here: + If this key was added in error, you can remove it under = link_to "SSH Keys", profile_keys_url diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb index 5f0080c2b76..05b551c89a0 100644 --- a/app/views/notify/new_ssh_key_email.text.erb +++ b/app/views/notify/new_ssh_key_email.text.erb @@ -2,6 +2,6 @@ Hi <%= @user.name %>! A new public key was added to your account: -title.................. <%= @key.title %> +Title: <%= @key.title %> -If this key was added in error, you can remove it here: <%= profile_keys_url %> +If this key was added in error, you can remove it at <%= profile_keys_url %> From 50ee0b81b8ade93a919a78ef1c04dc0afd6f3074 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 28 Oct 2014 09:19:58 +0100 Subject: [PATCH 157/408] Leave the project name field editable, fix the test. --- app/views/projects/edit.html.haml | 6 +----- features/steps/project/project.rb | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 79be310c2cf..b85cf7d8d37 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -13,11 +13,7 @@ = f.label :name, class: 'control-label' do Project name .col-sm-10 - = f.text_field :name, placeholder: "Example Project", class: "form-control", readonly: true - %p.hint - Rename the project at - %strong Rename repository - section. + = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit" .form-group diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index f7fff8e64f9..5e7312d90ff 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -4,7 +4,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps include SharedPaths step 'change project settings' do - fill_in 'project_name', with: 'NewName' + fill_in 'project_name_edit', with: 'NewName' uncheck 'project_issues_enabled' end From 776bca07cd4b3996a39035e90232c99599ed2663 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Thu, 16 Oct 2014 11:34:19 -0500 Subject: [PATCH 158/408] Add Atlassian Bamboo service --- CHANGELOG | 1 + .../projects/services_controller.rb | 3 +- app/models/project.rb | 3 +- app/models/project_services/bamboo_service.rb | 105 ++++++++++++++++++ app/views/projects/services/_form.html.haml | 4 +- doc/README.md | 1 + doc/project_services/bamboo.md | 60 ++++++++++ doc/project_services/project_services.md | 18 +++ features/project/service.feature | 6 + features/steps/project/services.rb | 20 ++++ 10 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 app/models/project_services/bamboo_service.rb create mode 100644 doc/project_services/bamboo.md create mode 100644 doc/project_services/project_services.md diff --git a/CHANGELOG b/CHANGELOG index 01ae3562ded..f01267c460c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ v 7.5.0 - Add time zone configuration on gitlab.yml (Sullivan Senechal) - Fix LDAP authentication for Git HTTP access - Fix LDAP config lookup for provider 'ldap' + - Add Atlassian Bamboo CI service (Drew Blessing) v 7.4.2 - Fix internal snippet exposing for unauthenticated users diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index b50f6286459..a5f30dcfd9d 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -41,7 +41,8 @@ class Projects::ServicesController < Projects::ApplicationController params.require(:service).permit( :title, :token, :type, :active, :api_key, :subdomain, :room, :recipients, :project_url, :webhook, - :user_key, :device, :priority, :sound + :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, + :build_key ) end end diff --git a/app/models/project.rb b/app/models/project.rb index 613f98ba44b..c58c9b551c9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -65,6 +65,7 @@ class Project < ActiveRecord::Base has_one :gemnasium_service, dependent: :destroy has_one :slack_service, dependent: :destroy has_one :buildbox_service, dependent: :destroy + has_one :bamboo_service, dependent: :destroy has_one :pushover_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -313,7 +314,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox bamboo) end def gitlab_ci? diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb new file mode 100644 index 00000000000..b9eec9ab21e --- /dev/null +++ b/app/models/project_services/bamboo_service.rb @@ -0,0 +1,105 @@ +class BambooService < CiService + include HTTParty + + prop_accessor :bamboo_url, :build_key, :username, :password + + validates :bamboo_url, presence: true, + format: { with: URI::regexp }, if: :activated? + validates :build_key, presence: true, if: :activated? + validates :username, presence: true, + if: ->(service) { service.password? }, if: :activated? + validates :password, presence: true, + if: ->(service) { service.username? }, if: :activated? + + attr_accessor :response + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + hook.save + end + + def title + 'Atlassian Bamboo CI' + end + + def description + 'A continuous integration and build server' + end + + def help + 'You must set up automatic revision labeling and a repository trigger in Bamboo.' + end + + def to_param + 'bamboo' + end + + def fields + [ + { type: 'text', name: 'bamboo_url', + placeholder: 'Bamboo root URL like https://bamboo.example.com' }, + { type: 'text', name: 'build_key', + placeholder: 'Bamboo build plan key like KEY' }, + { type: 'text', name: 'username', + placeholder: 'A user with API access, if applicable' }, + { type: 'password', name: 'password' }, + ] + end + + def build_info(sha) + url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}") + + if username.blank? && password.blank? + @response = HTTParty.get(parsed_url.to_s, verify: false) + else + get_url = "#{url}&os_authType=basic" + auth = { + username: username, + password: password, + } + @response = HTTParty.get(get_url, verify: false, basic_auth: auth) + end + end + + def build_page(sha) + build_info(sha) if @response.nil? || !@response.code + + 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}" + 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}" + end + end + + def commit_status(sha) + build_info(sha) if @response.nil? || !@response.code + return :error unless @response.code == 200 || @response.code == 404 + + status = if @response.code == 404 || @response['results']['results']['size'] == '0' + 'Pending' + else + @response['results']['results']['result']['buildState'] + end + + if status.include?('Success') + 'success' + elsif status.include?('Failed') + 'failed' + elsif status.include?('Pending') + 'pending' + else + :error + end + end + + def execute(_data) + # Bamboo requires a GET and does not take any data. + self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", + verify: false) + end +end diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 16d59d1fe9d..1151f22c7e8 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -28,7 +28,7 @@ - @service.fields.each do |field| - name = field[:name] - - value = @service.send(name) + - value = @service.send(name) unless field[:type] == 'password' - type = field[:type] - placeholder = field[:placeholder] - choices = field[:choices] @@ -45,6 +45,8 @@ = f.check_box name - elsif type == 'select' = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + - elsif type == 'password' + = f.password_field name, class: 'form-control' .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/doc/README.md b/doc/README.md index a8e21f75714..7343d5ae273 100644 --- a/doc/README.md +++ b/doc/README.md @@ -5,6 +5,7 @@ - [API](api/README.md) Explore how you can access GitLab via a simple and powerful API. - [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. +- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md new file mode 100644 index 00000000000..51668128c62 --- /dev/null +++ b/doc/project_services/bamboo.md @@ -0,0 +1,60 @@ +# Atlassian Bamboo CI Service + +GitLab provides integration with Atlassian Bamboo for continuous integration. +When configured, pushes to a project will trigger a build in Bamboo automatically. +Merge requests will also display CI status showing whether the build is pending, +failed, or completed successfully. It also provides a link to the Bamboo build +page for more information. + +Bamboo doesn't quite provide the same features as a traditional build system when +it comes to accepting webhooks and commit data. There are a few things that +need to be configured in a Bamboo build plan before GitLab can integrate. + +## Setup + +### Complete these steps in Bamboo: + +1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions' +dropdown. +1. Select the 'Triggers' tab. +1. Click 'Add trigger'. +1. Enter a description such as 'GitLab trigger' +1. Choose 'Repository triggers the build when changes are committed' +1. Check one or more repositories checkboxes +1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a +whitelist of IP addresses that are allowed to trigger Bamboo builds. +1. Save the trigger. +1. In the left pane, select a build stage. If you have multiple build stages +you want to select the last stage that contains the git checkout task. +1. Select the 'Miscellaneous' tab. +1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}' +in the 'Labels' box. +1. Save + +Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo +service in GitLab + +### Complete these steps in GitLab: + +1. Navigate to the project you want to configure to trigger builds. +1. Select 'Settings' in the top navigation. +1. Select 'Services' in the left navigation. +1. Click 'Atlassian Bamboo CI' +1. Select the 'Active' checkbox. +1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com' +1. Enter the build key from your Bamboo build plan. Build keys are a short, +all capital letter, identifier that is unique. It will be something like PR-BLD +1. If necessary, enter username and password for a Bamboo user that has +access to trigger the build plan. Leave these fields blank if you do not require +authentication. +1. Save or optionally click 'Test Settings'. Please note that 'Test Settings' +will actually trigger a build in Bamboo. + +## Troubleshooting + +If builds are not triggered, these are a couple of things to keep in mind. + +1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger +IP addresses'. +1. Remember that GitLab only triggers builds on push events. A commit via the +web interface will not trigger CI currently. diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md new file mode 100644 index 00000000000..20a69a211dd --- /dev/null +++ b/doc/project_services/project_services.md @@ -0,0 +1,18 @@ +# Project Services + +__Project integrations with external services for continuous integration and more.__ + +## Services + +- Assemblia +- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continous integration. +- Build box +- Campfire +- Emails on push +- Flowdock +- Gemnasium +- GitLab CI +- Hipchat +- PivotalTracker +- Pushover +- Slack diff --git a/features/project/service.feature b/features/project/service.feature index af88eaefa8f..88fd038d45f 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -54,3 +54,9 @@ Feature: Project Services And I click email on push service link And I fill email on push settings Then I should see email on push service settings saved + + Scenario: Activate Atlassian Bamboo CI service + When I visit project "Shop" services page + And I click Atlassian Bamboo CI service link + And I fill Atlassian Bamboo CI settings + Then I should see Atlassian Bamboo CI service settings saved diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 5bd60f99c84..17d62210d10 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -14,6 +14,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps page.should have_content 'GitLab CI' page.should have_content 'Assembla' page.should have_content 'Pushover' + page.should have_content 'Atlassian Bamboo' end step 'I click gitlab-ci service link' do @@ -137,4 +138,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps find_field('Priority').find('option[selected]').value.should == '1' find_field('Sound').find('option[selected]').value.should == 'bike' end + + step 'I click Atlassian Bamboo CI service link' do + click_link 'Atlassian Bamboo CI' + end + + step 'I fill Atlassian Bamboo CI settings' do + check 'Active' + fill_in 'Bamboo url', with: 'http://bamboo.example.com' + fill_in 'Build key', with: 'KEY' + fill_in 'Username', with: 'user' + fill_in 'Password', with: 'verySecret' + click_button 'Save' + end + + step 'I should see Atlassian Bamboo CI service settings saved' do + find_field('Bamboo url').value.should == 'http://bamboo.example.com' + find_field('Build key').value.should == 'KEY' + find_field('Username').value.should == 'user' + end end From f3c120d25a7c9ab31609f93a0549c5174c519baf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 28 Oct 2014 16:00:03 +0200 Subject: [PATCH 159/408] Add failing test that should be green after group members api get fixed Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/groups_spec.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 42ccad71aaf..f56caeaf5ad 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -220,13 +220,27 @@ describe API::API, api: true do context "when a member of the group" do it "should return ok and add new member" do - count_before=group_no_members.group_members.count new_user = create(:user) - post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER + + expect { + post api("/groups/#{group_no_members.id}/members", owner), + user_id: new_user.id, access_level: GroupMember::MASTER + }.to change { group_no_members.members.count }.by(1) + response.status.should == 201 json_response['name'].should == new_user.name json_response['access_level'].should == GroupMember::MASTER - group_no_members.group_members.count.should == count_before + 1 + end + + it "should not allow guest to modify group members" do + new_user = create(:user) + + expect { + post api("/groups/#{group_with_members.id}/members", guest), + user_id: new_user.id, access_level: GroupMember::MASTER + }.not_to change { group_with_members.members.count } + + response.status.should == 403 end it "should return error if member already exists" do From 9397d19380aa4f63c9f8b67bf06b9d9ca3db3c1a Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 28 Oct 2014 11:02:36 -0500 Subject: [PATCH 160/408] Added ID to the form-group, to fix alignment of inputs --- app/views/devise/passwords/edit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 182ca5e774b..f6cbf9b82ba 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -6,7 +6,7 @@ .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token - .form-group + .form-group#password-strength = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true From bf07fcf06ad7a5d2fd5f25079ce9e16d003481c7 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 28 Oct 2014 11:04:46 -0500 Subject: [PATCH 161/408] Removed unnecessary role in form. --- app/views/devise/registrations/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 806d206d7b9..123de881f59 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -2,7 +2,7 @@ .login-heading %h3 Sign up .login-body - = form_for(resource, as: resource_name, url: registration_path(resource_name), role: 'form') do |f| + = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| .devise-errors = devise_error_messages! %div From dc4caa26bf438ec9193efd445d55dd0ad79cc906 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Tue, 28 Oct 2014 09:47:57 -0700 Subject: [PATCH 162/408] minor requirements.md cleanup * "Non Unix"->"Non-Unix" * Fix poor wording --- doc/install/requirements.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 85fb260c96e..ed194253148 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -24,7 +24,7 @@ For the installations options please see [the installation page on the GitLab we On the above unsupported distributions is still possible to install GitLab yourself. Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. -### Non Unix operating systems such as Windows +### Non-Unix operating systems such as Windows GitLab is developed for Unix operating systems. GitLab does **not** run on Windows and we have no plans of supporting it in the near future. @@ -53,8 +53,8 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab - 512MB is the absolute minimum but we do not recommend this amount of memory. You will either need to configure 512MB or 1.5GB of swap space. With 512MB of swap space you must configure only one unicorn worker. -With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow. +With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). +If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users @@ -67,7 +67,7 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro ### Storage -The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. +The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. From d9023da90d3d723bcb9ba372b0b89a8747aca6af Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 28 Oct 2014 12:48:20 -0500 Subject: [PATCH 163/408] Refactored password_strength configuration --- .../javascripts/password_strength.js.coffee | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee index 33b4d2e0f69..825f5630266 100644 --- a/app/assets/javascripts/password_strength.js.coffee +++ b/app/assets/javascripts/password_strength.js.coffee @@ -4,29 +4,25 @@ overwritten_messages = overwritten_rules = wordSequences: false + +options = + showProgressBar: false + showVerdicts: false + showPopover: true + showErrors: true + showStatus: true + errorMessages: overwritten_messages $(document).ready -> profileOptions = {} - profileOptions.ui = - showProgressBar: false - showVerdicts: false - showPopover: true - showErrors: true - showStatus: true - errorMessages: overwritten_messages + profileOptions.ui = options profileOptions.rules = activated: overwritten_rules deviseOptions = {} deviseOptions.common = usernameField: "#user_username" - deviseOptions.ui = - showPopover: true - showErrors: true - showVerdicts: false - showProgressBar: false - showStatus: true - errorMessages: overwritten_messages + deviseOptions.ui = options deviseOptions.rules = activated: overwritten_rules From e00e67db42ea3e4b994160dbdc288a8effa14713 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 28 Oct 2014 18:52:21 +0100 Subject: [PATCH 164/408] Drop all Postgres sequences during backup restore --- lib/backup/database.rb | 1 + lib/tasks/gitlab/db/drop_all_postgres_sequences.rake | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 lib/tasks/gitlab/db/drop_all_postgres_sequences.rake diff --git a/lib/backup/database.rb b/lib/backup/database.rb index d12d30a9110..ea659e3b605 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -34,6 +34,7 @@ module Backup # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE # statements like MySQL. Rake::Task["gitlab:db:drop_all_tables"].invoke + Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke pg_env system('psql', config['database'], '-f', db_file_name) end diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake new file mode 100644 index 00000000000..e9cf0a9b5e8 --- /dev/null +++ b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake @@ -0,0 +1,10 @@ +namespace :gitlab do + namespace :db do + task drop_all_postgres_sequences: :environment do + connection = ActiveRecord::Base.connection + connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence| + connection.execute("DROP SEQUENCE #{sequence['relname']}") + end + end + end +end From 1d0f638248edc12c1e36f3276e895eee31584601 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 28 Oct 2014 10:57:30 -0700 Subject: [PATCH 165/408] Don't update GitLab Shell to the latest version but to the corresponding version. --- doc/update/patch_versions.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index c4a77d12800..629c46ad030 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -26,16 +26,14 @@ sudo -u git -H git checkout LATEST_TAG Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`. -### 3. Update gitlab-shell if it is not the latest version +### 3. Update gitlab-shell to the corresponding version ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout LATEST_TAG +sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` ``` -Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`. - ### 4. Install libs, migrations, etc. ```bash From fa39863611a3eab3f386ccd0e8e5003cbd9a7ddb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 28 Oct 2014 20:02:01 +0200 Subject: [PATCH 166/408] Fix tests Signed-off-by: Dmitriy Zaporozhets --- spec/features/projects_spec.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 87bfe102d39..d291621935b 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Projects", feature: true do +describe "Projects", feature: true, js: true do before { login_as :user } describe "DELETE /projects/:id" do @@ -10,21 +10,23 @@ describe "Projects", feature: true do visit edit_project_path(@project) end - it "should be correct path", js: true do - expect { - click_link "Remove project" - fill_in 'confirm_name_input', with: @project.path - click_button 'Confirm' - }.to change {Project.count}.by(-1) + it "should remove project" do + expect { remove_project }.to change {Project.count}.by(-1) end - it 'should delete the project from the database and disk' do + it 'should delete the project from disk' do expect(GitlabShellWorker).to( receive(:perform_async).with(:remove_repository, /#{@project.path_with_namespace}/) ).twice - expect { click_link "Remove project" }.to change {Project.count}.by(-1) + remove_project end end + + def remove_project + click_link "Remove project" + fill_in 'confirm_name_input', with: @project.path + click_button 'Confirm' + end end From 733012cb65e43e41aa3b553c7fd02079cbf9eff4 Mon Sep 17 00:00:00 2001 From: Tomas Srna Date: Wed, 29 Oct 2014 10:52:54 +0100 Subject: [PATCH 167/408] Removed + '' + --- app/uploaders/attachment_uploader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index 24fc294909e..29a55b36ca5 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -27,7 +27,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base end def url - Gitlab.config.gitlab.relative_url_root + '' + super unless super.nil? + Gitlab.config.gitlab.relative_url_root + super unless super.nil? end def file_storage? From f6491508fe54c75c8db8db17b27d6d7912198a7a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 13:31:23 +0200 Subject: [PATCH 168/408] Split group members api Signed-off-by: Dmitriy Zaporozhets --- lib/api/api.rb | 1 + lib/api/group_members.rb | 74 ++++++++++++++ lib/api/groups.rb | 51 ---------- spec/requests/api/group_members_spec.rb | 130 ++++++++++++++++++++++++ spec/requests/api/groups_spec.rb | 124 ---------------------- 5 files changed, 205 insertions(+), 175 deletions(-) create mode 100644 lib/api/group_members.rb create mode 100644 spec/requests/api/group_members_spec.rb diff --git a/lib/api/api.rb b/lib/api/api.rb index 2c7cd9038c3..d26667ba3f7 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -27,6 +27,7 @@ module API helpers APIHelpers mount Groups + mount GroupMembers mount Users mount Projects mount Repositories diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb new file mode 100644 index 00000000000..24c141e9b71 --- /dev/null +++ b/lib/api/group_members.rb @@ -0,0 +1,74 @@ +module API + class GroupMembers < Grape::API + before { authenticate! } + + resource :groups do + helpers do + def find_group(id) + group = Group.find(id) + + if can?(current_user, :read_group, group) + group + else + render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403) + end + end + + def validate_access_level?(level) + Gitlab::Access.options_with_owner.values.include? level.to_i + end + end + + # Get a list of group members viewable by the authenticated user. + # + # Example Request: + # GET /groups/:id/members + get ":id/members" do + group = find_group(params[:id]) + members = group.group_members + users = (paginate members).collect(&:user) + present users, with: Entities::GroupMember, group: group + end + + # Add a user to the list of group members + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # access_level (required) - Project access level + # Example Request: + # POST /groups/:id/members + post ":id/members" do + required_attributes! [:user_id, :access_level] + unless validate_access_level?(params[:access_level]) + render_api_error!("Wrong access level", 422) + end + group = find_group(params[:id]) + if group.group_members.find_by(user_id: params[:user_id]) + render_api_error!("Already exists", 409) + end + group.add_users([params[:user_id]], params[:access_level]) + member = group.group_members.find_by(user_id: params[:user_id]) + present member.user, with: Entities::GroupMember, group: group + end + + # Remove member. + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # + # Example Request: + # DELETE /groups/:id/members/:user_id + delete ":id/members/:user_id" do + group = find_group(params[:id]) + member = group.group_members.find_by(user_id: params[:user_id]) + if member.nil? + render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) + else + member.destroy + end + end + end + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 4841e04689d..f0ab6938b1c 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -97,57 +97,6 @@ module API not_found! end end - - # Get a list of group members viewable by the authenticated user. - # - # Example Request: - # GET /groups/:id/members - get ":id/members" do - group = find_group(params[:id]) - members = group.group_members - users = (paginate members).collect(&:user) - present users, with: Entities::GroupMember, group: group - end - - # Add a user to the list of group members - # - # Parameters: - # id (required) - group id - # user_id (required) - the users id - # access_level (required) - Project access level - # Example Request: - # POST /groups/:id/members - post ":id/members" do - required_attributes! [:user_id, :access_level] - unless validate_access_level?(params[:access_level]) - render_api_error!("Wrong access level", 422) - end - group = find_group(params[:id]) - if group.group_members.find_by(user_id: params[:user_id]) - render_api_error!("Already exists", 409) - end - group.add_users([params[:user_id]], params[:access_level]) - member = group.group_members.find_by(user_id: params[:user_id]) - present member.user, with: Entities::GroupMember, group: group - end - - # Remove member. - # - # Parameters: - # id (required) - group id - # user_id (required) - the users id - # - # Example Request: - # DELETE /groups/:id/members/:user_id - delete ":id/members/:user_id" do - group = find_group(params[:id]) - member = group.group_members.find_by(user_id: params[:user_id]) - if member.nil? - render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) - else - member.destroy - end - end end end end diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb new file mode 100644 index 00000000000..b266f56a9dd --- /dev/null +++ b/spec/requests/api/group_members_spec.rb @@ -0,0 +1,130 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:owner) { create(:user) } + let(:reporter) { create(:user) } + let(:developer) { create(:user) } + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:stranger) { create(:user) } + + let!(:group_with_members) do + group = create(:group) + group.add_users([reporter.id], GroupMember::REPORTER) + group.add_users([developer.id], GroupMember::DEVELOPER) + group.add_users([master.id], GroupMember::MASTER) + group.add_users([guest.id], GroupMember::GUEST) + group + end + + let!(:group_no_members) { create(:group) } + + before do + group_with_members.add_owner owner + group_no_members.add_owner owner + end + + describe "GET /groups/:id/members" do + context "when authenticated as user that is part or the group" do + it "each user: should return an array of members groups of group3" do + [owner, master, developer, reporter, guest].each do |user| + get api("/groups/#{group_with_members.id}/members", user) + response.status.should == 200 + json_response.should be_an Array + json_response.size.should == 5 + json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER + json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER + json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER + json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER + json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST + end + end + + it "users not part of the group should get access error" do + get api("/groups/#{group_with_members.id}/members", stranger) + response.status.should == 403 + end + end + end + + describe "POST /groups/:id/members" do + context "when not a member of the group" do + it "should not add guest as member of group_no_members when adding being done by person outside the group" do + post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER + response.status.should == 403 + end + end + + context "when a member of the group" do + it "should return ok and add new member" do + new_user = create(:user) + + expect { + post api("/groups/#{group_no_members.id}/members", owner), + user_id: new_user.id, access_level: GroupMember::MASTER + }.to change { group_no_members.members.count }.by(1) + + response.status.should == 201 + json_response['name'].should == new_user.name + json_response['access_level'].should == GroupMember::MASTER + end + + it "should not allow guest to modify group members" do + new_user = create(:user) + + expect { + post api("/groups/#{group_with_members.id}/members", guest), + user_id: new_user.id, access_level: GroupMember::MASTER + }.not_to change { group_with_members.members.count } + + response.status.should == 403 + end + + it "should return error if member already exists" do + post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER + response.status.should == 409 + end + + it "should return a 400 error when user id is not given" do + post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER + response.status.should == 400 + end + + it "should return a 400 error when access level is not given" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id + response.status.should == 400 + end + + it "should return a 422 error when access level is not known" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 + response.status.should == 422 + end + end + end + + describe "DELETE /groups/:id/members/:user_id" do + context "when not a member of the group" do + it "should not delete guest's membership of group_with_members" do + random_user = create(:user) + delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) + response.status.should == 403 + end + end + + context "when a member of the group" do + it "should delete guest's membership of group" do + count_before=group_with_members.group_members.count + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + response.status.should == 200 + group_with_members.group_members.count.should == count_before - 1 + end + + it "should return a 404 error when user id is not known" do + delete api("/groups/#{group_with_members.id}/members/1328", owner) + response.status.should == 404 + end + end + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index f56caeaf5ad..8dfd2cd650e 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -165,128 +165,4 @@ describe API::API, api: true do end end end - - describe "members" do - let(:owner) { create(:user) } - let(:reporter) { create(:user) } - let(:developer) { create(:user) } - let(:master) { create(:user) } - let(:guest) { create(:user) } - let!(:group_with_members) do - group = create(:group) - group.add_users([reporter.id], GroupMember::REPORTER) - group.add_users([developer.id], GroupMember::DEVELOPER) - group.add_users([master.id], GroupMember::MASTER) - group.add_users([guest.id], GroupMember::GUEST) - group - end - let!(:group_no_members) { create(:group) } - - before do - group_with_members.add_owner owner - group_no_members.add_owner owner - end - - describe "GET /groups/:id/members" do - context "when authenticated as user that is part or the group" do - it "each user: should return an array of members groups of group3" do - [owner, master, developer, reporter, guest].each do |user| - get api("/groups/#{group_with_members.id}/members", user) - response.status.should == 200 - json_response.should be_an Array - json_response.size.should == 5 - json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER - json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER - json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER - json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER - json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST - end - end - - it "users not part of the group should get access error" do - get api("/groups/#{group_with_members.id}/members", user1) - response.status.should == 403 - end - end - end - - describe "POST /groups/:id/members" do - context "when not a member of the group" do - it "should not add guest as member of group_no_members when adding being done by person outside the group" do - post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should return ok and add new member" do - new_user = create(:user) - - expect { - post api("/groups/#{group_no_members.id}/members", owner), - user_id: new_user.id, access_level: GroupMember::MASTER - }.to change { group_no_members.members.count }.by(1) - - response.status.should == 201 - json_response['name'].should == new_user.name - json_response['access_level'].should == GroupMember::MASTER - end - - it "should not allow guest to modify group members" do - new_user = create(:user) - - expect { - post api("/groups/#{group_with_members.id}/members", guest), - user_id: new_user.id, access_level: GroupMember::MASTER - }.not_to change { group_with_members.members.count } - - response.status.should == 403 - end - - it "should return error if member already exists" do - post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER - response.status.should == 409 - end - - it "should return a 400 error when user id is not given" do - post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER - response.status.should == 400 - end - - it "should return a 400 error when access level is not given" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id - response.status.should == 400 - end - - it "should return a 422 error when access level is not known" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 - response.status.should == 422 - end - end - end - - describe "DELETE /groups/:id/members/:user_id" do - context "when not a member of the group" do - it "should not delete guest's membership of group_with_members" do - random_user = create(:user) - delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should delete guest's membership of group" do - count_before=group_with_members.group_members.count - delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) - response.status.should == 200 - group_with_members.group_members.count.should == count_before - 1 - end - - it "should return a 404 error when user id is not known" do - delete api("/groups/#{group_with_members.id}/members/1328", owner) - response.status.should == 404 - end - end - end - end end From eea6a8a17deb384bfe6b9b83462df6cb5f7f17ad Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 13:38:00 +0200 Subject: [PATCH 169/408] Dont allow guests..developers to manage group members Signed-off-by: Dmitriy Zaporozhets --- lib/api/group_members.rb | 10 ++++++++-- spec/requests/api/group_members_spec.rb | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb index 24c141e9b71..d596517c816 100644 --- a/lib/api/group_members.rb +++ b/lib/api/group_members.rb @@ -39,14 +39,18 @@ module API # Example Request: # POST /groups/:id/members post ":id/members" do + group = find_group(params[:id]) + authorize! :manage_group, group required_attributes! [:user_id, :access_level] + unless validate_access_level?(params[:access_level]) render_api_error!("Wrong access level", 422) end - group = find_group(params[:id]) + if group.group_members.find_by(user_id: params[:user_id]) render_api_error!("Already exists", 409) end + group.add_users([params[:user_id]], params[:access_level]) member = group.group_members.find_by(user_id: params[:user_id]) present member.user, with: Entities::GroupMember, group: group @@ -62,7 +66,9 @@ module API # DELETE /groups/:id/members/:user_id delete ":id/members/:user_id" do group = find_group(params[:id]) - member = group.group_members.find_by(user_id: params[:user_id]) + authorize! :manage_group, group + member = group.group_members.find_by(user_id: params[:user_id]) + if member.nil? render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) else diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index b266f56a9dd..4957186f605 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -115,16 +115,22 @@ describe API::API, api: true do context "when a member of the group" do it "should delete guest's membership of group" do - count_before=group_with_members.group_members.count - delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + expect { + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + }.to change { group_with_members.members.count }.by(-1) + response.status.should == 200 - group_with_members.group_members.count.should == count_before - 1 end it "should return a 404 error when user id is not known" do delete api("/groups/#{group_with_members.id}/members/1328", owner) response.status.should == 404 end + + it "should not allow guest to modify group members" do + delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest) + response.status.should == 403 + end end end end From 57471894819c07796d2aa04e5a21d1a648a7751e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 29 Oct 2014 13:38:43 +0100 Subject: [PATCH 170/408] Add CHANGELOG entry for sequence drop --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 01ae3562ded..43a45e9ae5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ v 7.5.0 - Add time zone configuration on gitlab.yml (Sullivan Senechal) - Fix LDAP authentication for Git HTTP access - Fix LDAP config lookup for provider 'ldap' + - Drop all sequences during Postgres database restore v 7.4.2 - Fix internal snippet exposing for unauthenticated users From 912715c3e452723dd7dda3430f57e69b7f1605af Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 29 Oct 2014 15:59:57 +0200 Subject: [PATCH 171/408] update changelog && bump version --- CHANGELOG | 4 ++++ VERSION | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4428bae4ebd..9884b475818 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +v 7.4.3 + - Fix raw snippets view + - Fix security issue for member api + v 7.4.2 - Fix internal snippet exposing for unauthenticated users diff --git a/VERSION b/VERSION index f8cb1fa110d..0f4a1d6e34e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.2 +7.4.3 From 43fba7f0c518942d060e7a7dd9f7ec3065ac1f69 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 28 Oct 2014 16:00:03 +0200 Subject: [PATCH 172/408] Add failing test that should be green after group members api get fixed Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/groups_spec.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 42ccad71aaf..f56caeaf5ad 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -220,13 +220,27 @@ describe API::API, api: true do context "when a member of the group" do it "should return ok and add new member" do - count_before=group_no_members.group_members.count new_user = create(:user) - post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER + + expect { + post api("/groups/#{group_no_members.id}/members", owner), + user_id: new_user.id, access_level: GroupMember::MASTER + }.to change { group_no_members.members.count }.by(1) + response.status.should == 201 json_response['name'].should == new_user.name json_response['access_level'].should == GroupMember::MASTER - group_no_members.group_members.count.should == count_before + 1 + end + + it "should not allow guest to modify group members" do + new_user = create(:user) + + expect { + post api("/groups/#{group_with_members.id}/members", guest), + user_id: new_user.id, access_level: GroupMember::MASTER + }.not_to change { group_with_members.members.count } + + response.status.should == 403 end it "should return error if member already exists" do From 995d198419b6f196b014ccb47d0298b419b53e6d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 13:31:23 +0200 Subject: [PATCH 173/408] Split group members api Signed-off-by: Dmitriy Zaporozhets --- lib/api/api.rb | 1 + lib/api/group_members.rb | 74 ++++++++++++++ lib/api/groups.rb | 51 ---------- spec/requests/api/group_members_spec.rb | 130 ++++++++++++++++++++++++ spec/requests/api/groups_spec.rb | 124 ---------------------- 5 files changed, 205 insertions(+), 175 deletions(-) create mode 100644 lib/api/group_members.rb create mode 100644 spec/requests/api/group_members_spec.rb diff --git a/lib/api/api.rb b/lib/api/api.rb index 2c7cd9038c3..d26667ba3f7 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -27,6 +27,7 @@ module API helpers APIHelpers mount Groups + mount GroupMembers mount Users mount Projects mount Repositories diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb new file mode 100644 index 00000000000..24c141e9b71 --- /dev/null +++ b/lib/api/group_members.rb @@ -0,0 +1,74 @@ +module API + class GroupMembers < Grape::API + before { authenticate! } + + resource :groups do + helpers do + def find_group(id) + group = Group.find(id) + + if can?(current_user, :read_group, group) + group + else + render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403) + end + end + + def validate_access_level?(level) + Gitlab::Access.options_with_owner.values.include? level.to_i + end + end + + # Get a list of group members viewable by the authenticated user. + # + # Example Request: + # GET /groups/:id/members + get ":id/members" do + group = find_group(params[:id]) + members = group.group_members + users = (paginate members).collect(&:user) + present users, with: Entities::GroupMember, group: group + end + + # Add a user to the list of group members + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # access_level (required) - Project access level + # Example Request: + # POST /groups/:id/members + post ":id/members" do + required_attributes! [:user_id, :access_level] + unless validate_access_level?(params[:access_level]) + render_api_error!("Wrong access level", 422) + end + group = find_group(params[:id]) + if group.group_members.find_by(user_id: params[:user_id]) + render_api_error!("Already exists", 409) + end + group.add_users([params[:user_id]], params[:access_level]) + member = group.group_members.find_by(user_id: params[:user_id]) + present member.user, with: Entities::GroupMember, group: group + end + + # Remove member. + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # + # Example Request: + # DELETE /groups/:id/members/:user_id + delete ":id/members/:user_id" do + group = find_group(params[:id]) + member = group.group_members.find_by(user_id: params[:user_id]) + if member.nil? + render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) + else + member.destroy + end + end + end + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 4841e04689d..f0ab6938b1c 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -97,57 +97,6 @@ module API not_found! end end - - # Get a list of group members viewable by the authenticated user. - # - # Example Request: - # GET /groups/:id/members - get ":id/members" do - group = find_group(params[:id]) - members = group.group_members - users = (paginate members).collect(&:user) - present users, with: Entities::GroupMember, group: group - end - - # Add a user to the list of group members - # - # Parameters: - # id (required) - group id - # user_id (required) - the users id - # access_level (required) - Project access level - # Example Request: - # POST /groups/:id/members - post ":id/members" do - required_attributes! [:user_id, :access_level] - unless validate_access_level?(params[:access_level]) - render_api_error!("Wrong access level", 422) - end - group = find_group(params[:id]) - if group.group_members.find_by(user_id: params[:user_id]) - render_api_error!("Already exists", 409) - end - group.add_users([params[:user_id]], params[:access_level]) - member = group.group_members.find_by(user_id: params[:user_id]) - present member.user, with: Entities::GroupMember, group: group - end - - # Remove member. - # - # Parameters: - # id (required) - group id - # user_id (required) - the users id - # - # Example Request: - # DELETE /groups/:id/members/:user_id - delete ":id/members/:user_id" do - group = find_group(params[:id]) - member = group.group_members.find_by(user_id: params[:user_id]) - if member.nil? - render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) - else - member.destroy - end - end end end end diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb new file mode 100644 index 00000000000..b266f56a9dd --- /dev/null +++ b/spec/requests/api/group_members_spec.rb @@ -0,0 +1,130 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:owner) { create(:user) } + let(:reporter) { create(:user) } + let(:developer) { create(:user) } + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:stranger) { create(:user) } + + let!(:group_with_members) do + group = create(:group) + group.add_users([reporter.id], GroupMember::REPORTER) + group.add_users([developer.id], GroupMember::DEVELOPER) + group.add_users([master.id], GroupMember::MASTER) + group.add_users([guest.id], GroupMember::GUEST) + group + end + + let!(:group_no_members) { create(:group) } + + before do + group_with_members.add_owner owner + group_no_members.add_owner owner + end + + describe "GET /groups/:id/members" do + context "when authenticated as user that is part or the group" do + it "each user: should return an array of members groups of group3" do + [owner, master, developer, reporter, guest].each do |user| + get api("/groups/#{group_with_members.id}/members", user) + response.status.should == 200 + json_response.should be_an Array + json_response.size.should == 5 + json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER + json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER + json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER + json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER + json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST + end + end + + it "users not part of the group should get access error" do + get api("/groups/#{group_with_members.id}/members", stranger) + response.status.should == 403 + end + end + end + + describe "POST /groups/:id/members" do + context "when not a member of the group" do + it "should not add guest as member of group_no_members when adding being done by person outside the group" do + post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER + response.status.should == 403 + end + end + + context "when a member of the group" do + it "should return ok and add new member" do + new_user = create(:user) + + expect { + post api("/groups/#{group_no_members.id}/members", owner), + user_id: new_user.id, access_level: GroupMember::MASTER + }.to change { group_no_members.members.count }.by(1) + + response.status.should == 201 + json_response['name'].should == new_user.name + json_response['access_level'].should == GroupMember::MASTER + end + + it "should not allow guest to modify group members" do + new_user = create(:user) + + expect { + post api("/groups/#{group_with_members.id}/members", guest), + user_id: new_user.id, access_level: GroupMember::MASTER + }.not_to change { group_with_members.members.count } + + response.status.should == 403 + end + + it "should return error if member already exists" do + post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER + response.status.should == 409 + end + + it "should return a 400 error when user id is not given" do + post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER + response.status.should == 400 + end + + it "should return a 400 error when access level is not given" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id + response.status.should == 400 + end + + it "should return a 422 error when access level is not known" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 + response.status.should == 422 + end + end + end + + describe "DELETE /groups/:id/members/:user_id" do + context "when not a member of the group" do + it "should not delete guest's membership of group_with_members" do + random_user = create(:user) + delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) + response.status.should == 403 + end + end + + context "when a member of the group" do + it "should delete guest's membership of group" do + count_before=group_with_members.group_members.count + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + response.status.should == 200 + group_with_members.group_members.count.should == count_before - 1 + end + + it "should return a 404 error when user id is not known" do + delete api("/groups/#{group_with_members.id}/members/1328", owner) + response.status.should == 404 + end + end + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index f56caeaf5ad..8dfd2cd650e 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -165,128 +165,4 @@ describe API::API, api: true do end end end - - describe "members" do - let(:owner) { create(:user) } - let(:reporter) { create(:user) } - let(:developer) { create(:user) } - let(:master) { create(:user) } - let(:guest) { create(:user) } - let!(:group_with_members) do - group = create(:group) - group.add_users([reporter.id], GroupMember::REPORTER) - group.add_users([developer.id], GroupMember::DEVELOPER) - group.add_users([master.id], GroupMember::MASTER) - group.add_users([guest.id], GroupMember::GUEST) - group - end - let!(:group_no_members) { create(:group) } - - before do - group_with_members.add_owner owner - group_no_members.add_owner owner - end - - describe "GET /groups/:id/members" do - context "when authenticated as user that is part or the group" do - it "each user: should return an array of members groups of group3" do - [owner, master, developer, reporter, guest].each do |user| - get api("/groups/#{group_with_members.id}/members", user) - response.status.should == 200 - json_response.should be_an Array - json_response.size.should == 5 - json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER - json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER - json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER - json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER - json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST - end - end - - it "users not part of the group should get access error" do - get api("/groups/#{group_with_members.id}/members", user1) - response.status.should == 403 - end - end - end - - describe "POST /groups/:id/members" do - context "when not a member of the group" do - it "should not add guest as member of group_no_members when adding being done by person outside the group" do - post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should return ok and add new member" do - new_user = create(:user) - - expect { - post api("/groups/#{group_no_members.id}/members", owner), - user_id: new_user.id, access_level: GroupMember::MASTER - }.to change { group_no_members.members.count }.by(1) - - response.status.should == 201 - json_response['name'].should == new_user.name - json_response['access_level'].should == GroupMember::MASTER - end - - it "should not allow guest to modify group members" do - new_user = create(:user) - - expect { - post api("/groups/#{group_with_members.id}/members", guest), - user_id: new_user.id, access_level: GroupMember::MASTER - }.not_to change { group_with_members.members.count } - - response.status.should == 403 - end - - it "should return error if member already exists" do - post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER - response.status.should == 409 - end - - it "should return a 400 error when user id is not given" do - post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER - response.status.should == 400 - end - - it "should return a 400 error when access level is not given" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id - response.status.should == 400 - end - - it "should return a 422 error when access level is not known" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 - response.status.should == 422 - end - end - end - - describe "DELETE /groups/:id/members/:user_id" do - context "when not a member of the group" do - it "should not delete guest's membership of group_with_members" do - random_user = create(:user) - delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should delete guest's membership of group" do - count_before=group_with_members.group_members.count - delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) - response.status.should == 200 - group_with_members.group_members.count.should == count_before - 1 - end - - it "should return a 404 error when user id is not known" do - delete api("/groups/#{group_with_members.id}/members/1328", owner) - response.status.should == 404 - end - end - end - end end From a2dfff418bf2532ebb5aee88414107929b17eefd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 13:38:00 +0200 Subject: [PATCH 174/408] Dont allow guests..developers to manage group members Signed-off-by: Dmitriy Zaporozhets --- lib/api/group_members.rb | 10 ++++++++-- spec/requests/api/group_members_spec.rb | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb index 24c141e9b71..d596517c816 100644 --- a/lib/api/group_members.rb +++ b/lib/api/group_members.rb @@ -39,14 +39,18 @@ module API # Example Request: # POST /groups/:id/members post ":id/members" do + group = find_group(params[:id]) + authorize! :manage_group, group required_attributes! [:user_id, :access_level] + unless validate_access_level?(params[:access_level]) render_api_error!("Wrong access level", 422) end - group = find_group(params[:id]) + if group.group_members.find_by(user_id: params[:user_id]) render_api_error!("Already exists", 409) end + group.add_users([params[:user_id]], params[:access_level]) member = group.group_members.find_by(user_id: params[:user_id]) present member.user, with: Entities::GroupMember, group: group @@ -62,7 +66,9 @@ module API # DELETE /groups/:id/members/:user_id delete ":id/members/:user_id" do group = find_group(params[:id]) - member = group.group_members.find_by(user_id: params[:user_id]) + authorize! :manage_group, group + member = group.group_members.find_by(user_id: params[:user_id]) + if member.nil? render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) else diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index b266f56a9dd..4957186f605 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -115,16 +115,22 @@ describe API::API, api: true do context "when a member of the group" do it "should delete guest's membership of group" do - count_before=group_with_members.group_members.count - delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + expect { + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + }.to change { group_with_members.members.count }.by(-1) + response.status.should == 200 - group_with_members.group_members.count.should == count_before - 1 end it "should return a 404 error when user id is not known" do delete api("/groups/#{group_with_members.id}/members/1328", owner) response.status.should == 404 end + + it "should not allow guest to modify group members" do + delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest) + response.status.should == 403 + end end end end From 1ab1526d92d6084acfd459e82c9ce490e9e29807 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 27 Oct 2014 11:51:31 +0200 Subject: [PATCH 175/408] Fix raw view for public snippets --- app/controllers/snippets_controller.rb | 2 +- features/snippets/public_snippets.feature | 5 +++++ features/steps/snippets/public_snippets.rb | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 987694260c6..bf3312fedc8 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -9,7 +9,7 @@ class SnippetsController < ApplicationController before_filter :set_title - skip_before_filter :authenticate_user!, only: [:index, :user_index, :show] + skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw] respond_to :html diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature index 6964badc413..c2afb63b6d8 100644 --- a/features/snippets/public_snippets.feature +++ b/features/snippets/public_snippets.feature @@ -3,3 +3,8 @@ Feature: Public snippets Given There is public "Personal snippet one" snippet And I visit snippet page "Personal snippet one" Then I should see snippet "Personal snippet one" + + Scenario: Unauthenticated user should see raw public snippets + Given There is public "Personal snippet one" snippet + And I visit snippet raw page "Personal snippet one" + Then I should see raw snippet "Personal snippet one" diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb index 956aa4a3e7e..67669dc0a69 100644 --- a/features/steps/snippets/public_snippets.rb +++ b/features/steps/snippets/public_snippets.rb @@ -7,10 +7,18 @@ class Spinach::Features::PublicSnippets < Spinach::FeatureSteps page.should have_no_xpath("//i[@class='public-snippet']") end + step 'I should see raw snippet "Personal snippet one"' do + page.should have_text(snippet.content) + end + step 'I visit snippet page "Personal snippet one"' do visit snippet_path(snippet) end + step 'I visit snippet raw page "Personal snippet one"' do + visit raw_snippet_path(snippet) + end + def snippet @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end From ec5482a0dc460ec8d6f25ca6fd40377aba2d7714 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 16:34:28 +0200 Subject: [PATCH 176/408] Explicitly require addressable gem feature you need. Fixes Buildbox CI integration Signed-off-by: Dmitriy Zaporozhets --- app/models/project_services/buildbox_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb index b0f8e28c97f..0ab67b79fe4 100644 --- a/app/models/project_services/buildbox_service.rb +++ b/app/models/project_services/buildbox_service.rb @@ -12,6 +12,8 @@ # properties :text # +require "addressable/uri" + class BuildboxService < CiService prop_accessor :project_url, :token From 9de385435d15070ba11eb8c94a24269dc4435841 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 16:34:28 +0200 Subject: [PATCH 177/408] Explicitly require addressable gem feature you need. Fixes Buildbox CI integration Signed-off-by: Dmitriy Zaporozhets --- app/models/project_services/buildbox_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb index b0f8e28c97f..0ab67b79fe4 100644 --- a/app/models/project_services/buildbox_service.rb +++ b/app/models/project_services/buildbox_service.rb @@ -12,6 +12,8 @@ # properties :text # +require "addressable/uri" + class BuildboxService < CiService prop_accessor :project_url, :token From d52a5c76084296ff09bd3677add69a9bd7663ebe Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 29 Oct 2014 16:51:57 +0200 Subject: [PATCH 178/408] update changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 9884b475818..f539d2e272b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ v 7.4.3 - Fix raw snippets view - Fix security issue for member api + - Fix buildbox integration v 7.4.2 - Fix internal snippet exposing for unauthenticated users From 9cadfc9e134f720846bfa3d874d00545ecc78ac4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 17:17:22 +0200 Subject: [PATCH 179/408] Update omniauth-ldap & dependencies Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index f6f3607cbd1..0b6afc34355 100644 --- a/Gemfile +++ b/Gemfile @@ -37,7 +37,7 @@ gem "gitlab_git", '7.0.0.rc10' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' # LDAP Auth -gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap" +gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap" # Git Wiki gem 'gollum-lib', '~> 3.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 314884fa36e..6ca6179ca3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -185,11 +185,11 @@ GEM gitlab-linguist (~> 3.0) rugged (~> 0.21.0) gitlab_meta (7.0) - gitlab_omniauth-ldap (1.1.0) - net-ldap (~> 0.7.0) + gitlab_omniauth-ldap (1.2.0) + net-ldap (~> 0.9) omniauth (~> 1.0) pyu-ruby-sasl (~> 0.0.3.1) - rubyntlm (~> 0.1.1) + rubyntlm (~> 0.3) gollum-lib (3.0.0) github-markup (~> 1.1.0) gitlab-grit (~> 2.6.5) @@ -299,7 +299,7 @@ GEM multi_xml (0.5.5) multipart-post (1.2.0) mysql2 (0.3.16) - net-ldap (0.7.0) + net-ldap (0.9.0) net-scp (1.1.2) net-ssh (>= 2.6.5) net-ssh (2.8.0) @@ -445,7 +445,7 @@ GEM rspec-expectations (~> 2.14.0) rspec-mocks (~> 2.14.0) ruby-progressbar (1.2.0) - rubyntlm (0.1.1) + rubyntlm (0.4.0) rubypants (0.2.0) rugged (0.21.0) safe_yaml (0.9.7) @@ -626,7 +626,7 @@ DEPENDENCIES gitlab_emoji (~> 0.0.1.1) gitlab_git (= 7.0.0.rc10) gitlab_meta (= 7.0) - gitlab_omniauth-ldap (= 1.1.0) + gitlab_omniauth-ldap (= 1.2.0) gollum-lib (~> 3.0.0) gon (~> 5.0.0) grape (~> 0.6.1) From e712583a5e0c3fdb74abcd7c394237354edb3b45 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 29 Oct 2014 17:21:55 -0500 Subject: [PATCH 180/408] Improved rake documentation for importing existing repositories with a rake task. --- doc/raketasks/import.md | 51 +++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 5dba8de6d56..e7dc4138f6f 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -11,18 +11,55 @@ Notes: How to use: -1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path) -1. run the command below +1. Create a new folder inside the git repositories path. + +- For omnibus-gitlab it is located at: `/var/opt/gitlab/git-data/repositories` +- For manual installations it is usually located at: `/home/git/repositories` or you can see where +your repositories are located by looking at `config/gitlab.yml`: ``` -# omnibus-gitlab -sudo gitlab-rake gitlab:import:repos +# 3. Advanced settings +# ========================== + +# GitLab Satellites +# satellites: +# Relative paths are relative to Rails.root (default: tmp/repo_satellites/) +# path: /home/git/gitlab-satellites/ +# timeout: 30 + +satellites: + path: /home/git/gitlab-satellites/ +gitlab_shell: + path: /home/git/gitlab-shell/ + repos_path: /home/git/repositories/ + hooks_path: /home/git/gitlab-shell/hooks/ + upload_pack: true + receive_pack: true -# installation from source or cookbook -bundle exec rake gitlab:import:repos RAILS_ENV=production ``` -Example output: +2. Copy your bare repositories inside this newly created folder, e.g.: + +``` +$ cp /old/git/foo.git /home/git/repositories/new_group/foo.git +``` + +3. Run the command below depending on you type of installation: + +#### Omnibus Installation + +``` +$ sudo gitlab-rake gitlab:import:repos +``` + +#### Manual Installation + +``` +$ cd /home/git/gitlab +$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production +``` + +#### Example output: ``` Processing abcd.git From 332645232235c1b16faf538cc409c5b60d425168 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 30 Oct 2014 01:23:47 +0200 Subject: [PATCH 181/408] Refer to easyfix issues in CONTRIBUTING.md. --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8d3c251080..71435bc600d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls). +If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint. + ### Merge request guidelines If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: From bd0d679b236aa03c5b1f4596d5d669e225288030 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Wed, 29 Oct 2014 16:24:07 -0700 Subject: [PATCH 182/408] Update patch manual to link to blog post. --- doc/release/patch.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/release/patch.md b/doc/release/patch.md index 3ee55028b1f..5d2fa053cac 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -26,6 +26,6 @@ Otherwise include it in the monthly release and note there was a regression fix 1. Apply the patch to GitLab Cloud and the private GitLab development server 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. Cherry-pick the changelog update back into master -1. Create blog post -1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog +1. Create and publish a blog post +1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) From 1f62cb3ee7c4e163472c4268016ac933de26ada5 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Wed, 29 Oct 2014 19:09:24 -0700 Subject: [PATCH 183/408] fix git installation --- doc/install/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/install/installation.md b/doc/install/installation.md index ac6535b0c86..94bd22818e5 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -76,6 +76,7 @@ Is the system packaged Git too old? Remove it and compile from source. cd /tmp curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz cd git-2.1.2/ + ./configure make prefix=/usr/local all # Install into /usr/local/bin From 87fa3f0d2efd454441b838e3fd0b3645a6052c33 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Wed, 29 Oct 2014 21:18:57 -0700 Subject: [PATCH 184/408] ruby -> Ruby --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index ac6535b0c86..76e6835c1d3 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -91,7 +91,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ## 2. Ruby -The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby. +The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. Remove the old Ruby 1.8 if present From 9c6106c4f68fea96a36d77b9b09685dc9fde0161 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Wed, 29 Oct 2014 23:58:34 -0700 Subject: [PATCH 185/408] clarify that 'template1=#' is part of prompt Similar to https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/database_mysql.md clarify that `template1=#` is part of the prompt. --- doc/install/installation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index ac6535b0c86..f81499bf658 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -126,7 +126,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Login to PostgreSQL sudo -u postgres psql -d template1 - # Create a user for GitLab. + # Create a user for GitLab + # Do not type the 'template1=#', this is part of the prompt template1=# CREATE USER git CREATEDB; # Create the GitLab production database & grant all privileges on database From 12e751a892b6872cedf9b9463a8e6fb90456f5f1 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Thu, 30 Oct 2014 00:01:19 -0700 Subject: [PATCH 186/408] Quit the database session at end of database setup --- doc/install/installation.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/install/installation.md b/doc/install/installation.md index ac6535b0c86..0184d8e3369 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -137,6 +137,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Try connecting to the new database with the new user sudo -u git -H psql -d gitlabhq_production + + # Quit the database session + gitlabhq_production> \q ## 5. Redis From c3104abfd4d5c58838a6f4514ffa4b7c04ff6dcd Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Thu, 30 Oct 2014 05:01:21 -0500 Subject: [PATCH 187/408] Fix serialize migration --- db/migrate/20140907220153_serialize_service_properties.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb index b95f5b82e03..6293015fa07 100644 --- a/db/migrate/20140907220153_serialize_service_properties.rb +++ b/db/migrate/20140907220153_serialize_service_properties.rb @@ -23,7 +23,7 @@ class SerializeServiceProperties < ActiveRecord::Migration associations[service.type.to_sym].each do |attribute| service.send("#{attribute}=", service.attributes[attribute.to_s]) end - service.save! + service.save(validate: false) end remove_column :services, :project_url, :string From fd17ba9ffc3350c3054746eac4b6be1a84d690ac Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 29 Oct 2014 15:46:42 +0200 Subject: [PATCH 188/408] Mentioned users are not limited by project scope any more Signed-off-by: Dmitriy Zaporozhets --- app/models/concerns/mentionable.rb | 6 +----- app/services/notification_service.rb | 1 + spec/models/concerns/mentionable_spec.rb | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 spec/models/concerns/mentionable_spec.rb diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 5938d9cb28e..6c1aa99668a 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -52,11 +52,7 @@ module Mentionable if identifier == "all" users += project.team.members.flatten else - if has_project - id = project.team.members.find_by(username: identifier).try(:id) - else - id = User.find_by(username: identifier).try(:id) - end + id = User.find_by(username: identifier).try(:id) users << User.find(id) unless id.blank? end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 36781314278..c9a1574b84e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -124,6 +124,7 @@ class NotificationService opts = { noteable_type: note.noteable_type, project_id: note.project_id } target = note.noteable + if target.respond_to?(:participants) recipients = target.participants else diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb new file mode 100644 index 00000000000..ca6f11b2a4d --- /dev/null +++ b/spec/models/concerns/mentionable_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Issue, "Mentionable" do + describe :mentioned_users do + let!(:user) { create(:user, username: 'stranger') } + let!(:user2) { create(:user, username: 'john') } + let!(:issue) { create(:issue, description: '@stranger mentioned') } + + subject { issue.mentioned_users } + + it { should include(user) } + it { should_not include(user2) } + end +end From dade6650e1fe9ba13462957c15126d506e98673a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 30 Oct 2014 13:25:56 +0200 Subject: [PATCH 189/408] Add CHANGELOG entry Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index f01267c460c..1d1c6d26e11 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 7.5.0 - Fix LDAP authentication for Git HTTP access - Fix LDAP config lookup for provider 'ldap' - Add Atlassian Bamboo CI service (Drew Blessing) + - Mentioned @user will receive email even if he is not participating in issue or commit v 7.4.2 - Fix internal snippet exposing for unauthenticated users From 6cb5d3d2d4c88183a79c2587d7d1bcc4797c406e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 30 Oct 2014 14:44:29 +0200 Subject: [PATCH 190/408] Add addressable explicitly to Gemfile Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 1 + Gemfile.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 0b6afc34355..a2314236e29 100644 --- a/Gemfile +++ b/Gemfile @@ -186,6 +186,7 @@ gem "gon", '~> 5.0.0' gem 'nprogress-rails' gem 'request_store' gem "virtus" +gem 'addressable' group :development do gem "annotate", "~> 2.6.0.beta2" diff --git a/Gemfile.lock b/Gemfile.lock index 6ca6179ca3c..800f33590cb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -592,6 +592,7 @@ DEPENDENCIES RedCloth ace-rails-ap acts-as-taggable-on + addressable annotate (~> 2.6.0.beta2) asciidoctor (= 0.1.4) awesome_print From 353a98757850a37118bc5ff0718d8050f934bf90 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 30 Oct 2014 14:44:29 +0200 Subject: [PATCH 191/408] Add addressable explicitly to Gemfile Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 1 + Gemfile.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index f6f3607cbd1..ab7a1d6ae1e 100644 --- a/Gemfile +++ b/Gemfile @@ -186,6 +186,7 @@ gem "gon", '~> 5.0.0' gem 'nprogress-rails' gem 'request_store' gem "virtus" +gem 'addressable' group :development do gem "annotate", "~> 2.6.0.beta2" diff --git a/Gemfile.lock b/Gemfile.lock index 314884fa36e..f4ecbe6dedc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -592,6 +592,7 @@ DEPENDENCIES RedCloth ace-rails-ap acts-as-taggable-on + addressable annotate (~> 2.6.0.beta2) asciidoctor (= 0.1.4) awesome_print From 822d9aa6ba150ed1983dda3cfddaaa177f6b9f97 Mon Sep 17 00:00:00 2001 From: Sean Edge Date: Wed, 24 Sep 2014 22:30:06 -0400 Subject: [PATCH 192/408] Create RepoTag Grape entity and present it when doing stuff with tags via API. Update API doc for repositories. Add tag message to tag list page in UI. Update Changelog. Update spec to set .gitconfig identity, required for annotated tags. --- CHANGELOG | 1 + app/views/projects/tags/_tag.html.haml | 3 +++ doc/api/repositories.md | 4 +++- lib/api/entities.rb | 19 ++++++++++++++++ lib/api/repositories.rb | 5 +++-- spec/requests/api/repositories_spec.rb | 31 ++++++++++++++------------ 6 files changed, 46 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f01267c460c..aeacd37b1ef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 7.5.0 - Fix LDAP authentication for Git HTTP access - Fix LDAP config lookup for provider 'ldap' - Add Atlassian Bamboo CI service (Drew Blessing) + - Tie up loose ends with annotated tags: API & UI (Sean Edge) v 7.4.2 - Fix internal snippet exposing for unauthenticated users diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index bce105a033b..f93c1b4211f 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -4,6 +4,9 @@ = link_to project_commits_path(@project, tag.name), class: "" do %i.fa.fa-tag = tag.name + - if tag.message.present? +   + = tag.message .pull-right - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' diff --git a/doc/api/repositories.md b/doc/api/repositories.md index a412f60c0d9..8acf85d21c8 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -56,6 +56,7 @@ Parameters: [ { "name": "v1.0.0", + "message": "Release 1.0.0", "commit": { "id": "2695effb5807a22ff3d138d593fd856244e155e7", "parents": [], @@ -67,10 +68,11 @@ Parameters: "committed_date": "2012-05-28T04:42:42-07:00", "committer_email": "jack@example.com" }, - "protected": false } ] ``` +The message will be `nil` when creating a lightweight tag otherwise +it will contain the annotation. It returns 200 if the operation succeed. In case of an error, 405 with an explaining error message is returned. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 80e9470195e..d19caf5b23a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -73,6 +73,25 @@ module API end end + class RepoTag < Grape::Entity + expose :name + expose :message do |repo_obj, _options| + if repo_obj.respond_to?(:message) + repo_obj.message + else + nil + end + end + + expose :commit do |repo_obj, options| + if repo_obj.respond_to?(:commit) + repo_obj.commit + elsif options[:project] + options[:project].repository.commit(repo_obj.target) + end + end + end + class RepoObject < Grape::Entity expose :name diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 626d99c2649..a1a7721b288 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -23,7 +23,8 @@ module API # Example Request: # GET /projects/:id/repository/tags get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project + present user_project.repo.tags.sort_by(&:name).reverse, + with: Entities::RepoTag, project: user_project end # Create tag @@ -43,7 +44,7 @@ module API if result[:status] == :success present result[:tag], - with: Entities::RepoObject, + with: Entities::RepoTag, project: user_project else render_api_error!(result[:message], 400) diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 6e54839b677..dd7a0fc6cc8 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -34,21 +34,24 @@ describe API::API, api: true do end end - # TODO: fix this test for CI - #context 'annotated tag' do - #it 'should create a new annotated tag' do - #post api("/projects/#{project.id}/repository/tags", user), - #tag_name: 'v7.1.0', - #ref: 'master', - #message: 'tag message' + context 'annotated tag' do + it 'should create a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, + project.path_with_namespace + '.git') + system(*%W(git --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(git --git-dir=#{repo_path} config user.email #{user.email})) - #response.status.should == 201 - #json_response['name'].should == 'v7.1.0' - # The message is not part of the JSON response. - # Additional changes to the gitlab_git gem may be required. - # json_response['message'].should == 'tag message' - #end - #end + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.1.0', + ref: 'master', + message: 'Release 7.1.0' + + response.status.should == 201 + json_response['name'].should == 'v7.1.0' + json_response['message'].should == 'Release 7.1.0' + end + end it 'should deny for user without push access' do post api("/projects/#{project.id}/repository/tags", user2), From 961a6bfcc2f0b1063445143ba8841e6c6dcea8bf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 30 Oct 2014 17:28:59 +0200 Subject: [PATCH 193/408] API delete branch: render branch name json instead of true Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/branches_controller.rb | 1 + lib/api/branches.rb | 5 ++++- spec/requests/api/branches_spec.rb | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index dd6df5d196b..9f50660a5ad 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -19,6 +19,7 @@ class Projects::BranchesController < Projects::ApplicationController def create result = CreateBranchService.new(project, current_user). execute(params[:branch_name], params[:ref]) + if result[:status] == :success @branch = result[:branch] redirect_to project_tree_path(@project, @branch.name) diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 14f8b20f6b2..6ec1a753a69 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -82,6 +82,7 @@ module API authorize_push_project result = CreateBranchService.new(user_project, current_user). execute(params[:branch_name], params[:ref]) + if result[:status] == :success present result[:branch], with: Entities::RepoObject, @@ -104,7 +105,9 @@ module API execute(params[:branch]) if result[:status] == :success - true + { + branch_name: params[:branch] + } else render_api_error!(result[:message], result[:return_code]) end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 8834a6cfa83..b45572c39fd 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -146,6 +146,7 @@ describe API::API, api: true do it "should remove branch" do delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) response.status.should == 200 + json_response['branch_name'].should == branch_name end it 'should return 404 if branch not exists' do From 20e04d9f398b1f4221fa9f5de529dea7c2f0f9c1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 30 Oct 2014 17:30:09 +0200 Subject: [PATCH 194/408] Delete branch via API: doc updated Signed-off-by: Dmitriy Zaporozhets --- doc/api/branches.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/api/branches.md b/doc/api/branches.md index 74386615545..319f0b47386 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -211,3 +211,11 @@ Parameters: It return 200 if succeed, 404 if the branch to be deleted does not exist or 400 for other reasons. In case of an error, an explaining message is provided. + +Success response: + +```json +{ + "branch_name": "my-removed-branch" +} +``` From bafd30f92cfb754fe6864c9cd595df10b52b11f2 Mon Sep 17 00:00:00 2001 From: Andrey Krivko Date: Wed, 22 Oct 2014 22:29:26 +0700 Subject: [PATCH 195/408] Session API: Use case-insensitive authentication like in UI --- CHANGELOG | 3 ++- app/models/user.rb | 5 +++++ lib/gitlab/auth.rb | 2 +- spec/lib/gitlab/auth_spec.rb | 10 +++++++++- spec/models/user_spec.rb | 14 ++++++++++++++ spec/requests/api/session_spec.rb | 26 ++++++++++++++++++++++++++ 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1d1c6d26e11..924f9c6204d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 7.5.0 - Fix LDAP config lookup for provider 'ldap' - Add Atlassian Bamboo CI service (Drew Blessing) - Mentioned @user will receive email even if he is not participating in issue or commit + - Session API: Use case-insensitive authentication like in UI (Andrey Krivko) v 7.4.2 - Fix internal snippet exposing for unauthenticated users @@ -49,7 +50,7 @@ v 7.4.0 - Fix ambiguous sha problem with mentioned commit - Fixed bug with apostrophe when at mentioning users - Add active directory ldap option - - Developers can push to wiki repo. Protected branches does not affect wiki repo any more + - Developers can push to wiki repo. Protected branches does not affect wiki repo any more - Faster rev list - Fix branch removal diff --git a/app/models/user.rb b/app/models/user.rb index 154cc0f3e16..52e63cde6f9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -226,6 +226,11 @@ class User < ActiveRecord::Base where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%") end + def by_login(login) + where('lower(username) = :value OR lower(email) = :value', + value: login.to_s.downcase).first + end + def by_username_or_id(name_or_id) where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index ae33c529b93..30509528b8b 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,7 +1,7 @@ module Gitlab class Auth def find(login, password) - user = User.find_by(email: login) || User.find_by(username: login) + user = User.by_login(login) # If no user is found, or it's an LDAP server, try LDAP. # LDAP users are only authenticated via LDAP diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 1f3e1a4a3c1..95fc7e16a11 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -10,13 +10,21 @@ describe Gitlab::Auth do password: password, password_confirmation: password) end - let(:username) { 'john' } + let(:username) { 'John' } # username isn't lowercase, test this let(:password) { 'my-secret' } it "should find user by valid login/password" do expect( gl_auth.find(username, password) ).to eql user end + it 'should find user by valid email/password with case-insensitive email' do + expect(gl_auth.find(user.email.upcase, password)).to eql user + end + + it 'should find user by valid username/password with case-insensitive username' do + expect(gl_auth.find(username.upcase, password)).to eql user + end + it "should not find user with invalid password" do password = 'wrong' expect( gl_auth.find(username, password) ).to_not eql user diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6ad57b06e06..6d865cfc691 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -287,6 +287,20 @@ describe User do end end + describe '.by_login' do + let(:username) { 'John' } + let!(:user) { create(:user, username: username) } + + it 'should get the correct user' do + expect(User.by_login(user.email.upcase)).to eq user + expect(User.by_login(user.email)).to eq user + expect(User.by_login(username.downcase)).to eq user + expect(User.by_login(username)).to eq user + expect(User.by_login(nil)).to be_nil + expect(User.by_login('')).to be_nil + end + end + describe 'all_ssh_keys' do it { should have_many(:keys).dependent(:destroy) } diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index 013f425d6ce..57b2e6cbd6a 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -19,6 +19,32 @@ describe API::API, api: true do end end + context 'when email has case-typo and password is valid' do + it 'should return private token' do + post api('/session'), email: user.email.upcase, password: '12345678' + expect(response.status).to eq 201 + + expect(json_response['email']).to eq user.email + expect(json_response['private_token']).to eq user.private_token + expect(json_response['is_admin']).to eq user.is_admin? + expect(json_response['can_create_project']).to eq user.can_create_project? + expect(json_response['can_create_group']).to eq user.can_create_group? + end + end + + context 'when login has case-typo and password is valid' do + it 'should return private token' do + post api('/session'), login: user.username.upcase, password: '12345678' + expect(response.status).to eq 201 + + expect(json_response['email']).to eq user.email + expect(json_response['private_token']).to eq user.private_token + expect(json_response['is_admin']).to eq user.is_admin? + expect(json_response['can_create_project']).to eq user.can_create_project? + expect(json_response['can_create_group']).to eq user.can_create_group? + end + end + context "when invalid password" do it "should return authentication error" do post api("/session"), email: user.email, password: '123' From 19caccf77390c320b1ff4579bb72a638f5d553cf Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 30 Oct 2014 12:38:09 -0500 Subject: [PATCH 196/408] Added the satellites:create step, that might be necessary after the import. Corrected syntax. --- doc/raketasks/import.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index e7dc4138f6f..e78e1480405 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -11,7 +11,7 @@ Notes: How to use: -1. Create a new folder inside the git repositories path. +1. Create a new folder inside the git repositories path. This will be the name of the new group. - For omnibus-gitlab it is located at: `/var/opt/gitlab/git-data/repositories` - For manual installations it is usually located at: `/home/git/repositories` or you can see where @@ -41,23 +41,31 @@ gitlab_shell: 2. Copy your bare repositories inside this newly created folder, e.g.: ``` -$ cp /old/git/foo.git /home/git/repositories/new_group/foo.git +$ cp -r /old/git/foo.git/ /home/git/repositories/new_group/ ``` -3. Run the command below depending on you type of installation: +3. Run the commands below depending on you type of installation: #### Omnibus Installation ``` $ sudo gitlab-rake gitlab:import:repos ``` +``` +$ sudo gitlab-rake gitlab:satellites:create +``` #### Manual Installation +Before running these commands you need to change the directory to where your GitLab installation is located: + ``` $ cd /home/git/gitlab $ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production ``` +``` +$ sudo -u git -H bundle exec rake gitlab:satellites:create +``` #### Example output: From 493d3e8240d9d49952ab7c90e742ff4117fab711 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 30 Oct 2014 13:08:10 -0500 Subject: [PATCH 197/408] Corrected the wording of the documentation and the layout. --- doc/raketasks/import.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index e78e1480405..45a3bb855ac 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -1,23 +1,21 @@ -# Import - -## Import bare repositories into GitLab project instance +## Import bare repositories into your GitLab instance Notes: -- project owner will be a first admin -- groups will be created as needed -- group owner will be the first admin -- existing projects will be skipped +- The owner of the project will be the first admin +- The groups will be created as needed +- The owner of the group will be the first admin +- Existing projects will be skipped -How to use: +### How to use: -1. Create a new folder inside the git repositories path. This will be the name of the new group. +#### Create a new folder inside the git repositories path. This will be the name of the new group. -- For omnibus-gitlab it is located at: `/var/opt/gitlab/git-data/repositories` -- For manual installations it is usually located at: `/home/git/repositories` or you can see where +- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` +- For manual installations, it is usually located at: `/home/git/repositories` or you can see where your repositories are located by looking at `config/gitlab.yml`: -``` +```yaml # 3. Advanced settings # ========================== @@ -38,7 +36,7 @@ gitlab_shell: ``` -2. Copy your bare repositories inside this newly created folder, e.g.: +#### Copy your bare repositories inside this newly created folder, e.g.: ``` $ cp -r /old/git/foo.git/ /home/git/repositories/new_group/ @@ -46,7 +44,7 @@ $ cp -r /old/git/foo.git/ /home/git/repositories/new_group/ 3. Run the commands below depending on you type of installation: -#### Omnibus Installation +##### Omnibus Installation ``` $ sudo gitlab-rake gitlab:import:repos @@ -55,7 +53,7 @@ $ sudo gitlab-rake gitlab:import:repos $ sudo gitlab-rake gitlab:satellites:create ``` -#### Manual Installation +##### Manual Installation Before running these commands you need to change the directory to where your GitLab installation is located: @@ -67,7 +65,7 @@ $ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production $ sudo -u git -H bundle exec rake gitlab:satellites:create ``` -#### Example output: +##### Example output: ``` Processing abcd.git From 792f7e09e26b7d8872166d27924837f0fd42526b Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 30 Oct 2014 13:10:07 -0500 Subject: [PATCH 198/408] Corrected layout, to be more friendly. --- doc/raketasks/import.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 45a3bb855ac..1f34abddd7f 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -1,15 +1,15 @@ -## Import bare repositories into your GitLab instance +# Import bare repositories into your GitLab instance -Notes: +### Notes: - The owner of the project will be the first admin - The groups will be created as needed - The owner of the group will be the first admin - Existing projects will be skipped -### How to use: +## How to use: -#### Create a new folder inside the git repositories path. This will be the name of the new group. +### Create a new folder inside the git repositories path. This will be the name of the new group. - For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` - For manual installations, it is usually located at: `/home/git/repositories` or you can see where @@ -36,15 +36,15 @@ gitlab_shell: ``` -#### Copy your bare repositories inside this newly created folder, e.g.: +### Copy your bare repositories inside this newly created folder, e.g.: ``` $ cp -r /old/git/foo.git/ /home/git/repositories/new_group/ ``` -3. Run the commands below depending on you type of installation: +### Run the commands below depending on you type of installation: -##### Omnibus Installation +#### Omnibus Installation ``` $ sudo gitlab-rake gitlab:import:repos @@ -53,7 +53,7 @@ $ sudo gitlab-rake gitlab:import:repos $ sudo gitlab-rake gitlab:satellites:create ``` -##### Manual Installation +#### Manual Installation Before running these commands you need to change the directory to where your GitLab installation is located: @@ -65,7 +65,7 @@ $ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production $ sudo -u git -H bundle exec rake gitlab:satellites:create ``` -##### Example output: +#### Example output: ``` Processing abcd.git From 5c645b76b3a9343ac4ff55b8cb14472efc38b746 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 30 Oct 2014 13:14:31 -0500 Subject: [PATCH 199/408] Fixed typo --- doc/raketasks/import.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 1f34abddd7f..a873cd299e3 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -36,13 +36,13 @@ gitlab_shell: ``` -### Copy your bare repositories inside this newly created folder, e.g.: +### Copy your bare repositories inside this newly created folder: ``` $ cp -r /old/git/foo.git/ /home/git/repositories/new_group/ ``` -### Run the commands below depending on you type of installation: +### Run the commands below depending on your type of installation: #### Omnibus Installation From a1dda564b19ed268a8019a9ac814933ac309a92b Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 30 Oct 2014 14:59:05 -0700 Subject: [PATCH 200/408] Revert "Change update recommendation" since it should be fixed with c3104abfd4d5c58838a6f4514ffa4b7c04ff6dcd This reverts commit 8101640c33d240fc5b29c3dec33c56b67325a89f. --- ...x-or-7.x-to-7.3.md => 6.x-or-7.x-to-7.4.md} | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename doc/update/{6.x-or-7.x-to-7.3.md => 6.x-or-7.x-to-7.4.md} (95%) diff --git a/doc/update/6.x-or-7.x-to-7.3.md b/doc/update/6.x-or-7.x-to-7.4.md similarity index 95% rename from doc/update/6.x-or-7.x-to-7.3.md rename to doc/update/6.x-or-7.x-to-7.4.md index ae086cc4435..dd90ae3bf3d 100644 --- a/doc/update/6.x-or-7.x-to-7.3.md +++ b/doc/update/6.x-or-7.x-to-7.4.md @@ -1,6 +1,6 @@ -# From 6.x or 7.x to 7.3 +# From 6.x or 7.x to 7.4 -This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3. +This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4. ## Global issue numbers @@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut For GitLab Community Edition: ```bash -sudo -u git -H git checkout 7-3-stable +sudo -u git -H git checkout 7-4-stable ``` OR @@ -78,7 +78,7 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout 7-3-stable-ee +sudo -u git -H git checkout 7-4-stable-ee ``` ## 4. Install additional packages @@ -154,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab TIP: to see what changed in `gitlab.yml.example` in this release use next command: ``` -git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example ``` -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings. * Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings. -* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings. * Copy rack attack middleware config ```bash From 2022754d933176877e9cff1159512e63e9fff2d8 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 30 Oct 2014 17:36:25 -0500 Subject: [PATCH 201/408] Modified according to suggestions by @sytse --- doc/raketasks/import.md | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index a873cd299e3..153a2e2575b 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -11,30 +11,11 @@ ### Create a new folder inside the git repositories path. This will be the name of the new group. -- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` +- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed +it in the `/etc/gitlab/gitlab.rb` file. - For manual installations, it is usually located at: `/home/git/repositories` or you can see where -your repositories are located by looking at `config/gitlab.yml`: +your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry. -```yaml -# 3. Advanced settings -# ========================== - -# GitLab Satellites -# satellites: -# Relative paths are relative to Rails.root (default: tmp/repo_satellites/) -# path: /home/git/gitlab-satellites/ -# timeout: 30 - -satellites: - path: /home/git/gitlab-satellites/ -gitlab_shell: - path: /home/git/gitlab-shell/ - repos_path: /home/git/repositories/ - hooks_path: /home/git/gitlab-shell/hooks/ - upload_pack: true - receive_pack: true - -``` ### Copy your bare repositories inside this newly created folder: @@ -42,28 +23,22 @@ gitlab_shell: $ cp -r /old/git/foo.git/ /home/git/repositories/new_group/ ``` -### Run the commands below depending on your type of installation: +### Run the command below depending on your type of installation: #### Omnibus Installation ``` $ sudo gitlab-rake gitlab:import:repos ``` -``` -$ sudo gitlab-rake gitlab:satellites:create -``` #### Manual Installation -Before running these commands you need to change the directory to where your GitLab installation is located: +Before running this command you need to change the directory to where your GitLab installation is located: ``` $ cd /home/git/gitlab $ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production ``` -``` -$ sudo -u git -H bundle exec rake gitlab:satellites:create -``` #### Example output: From 3c9bf8a322be062c1a07483927deb1415f517547 Mon Sep 17 00:00:00 2001 From: "Crom (Thibaut CHARLES)" Date: Thu, 30 Oct 2014 23:41:45 +0100 Subject: [PATCH 202/408] Doc: Unicorn minimum worker_processes is 2 A value of 1 cause http push fail (issue #6978 on GitHub) --- config/unicorn.rb.example | 1 + 1 file changed, 1 insertion(+) diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index 6833082d68b..ea22744fd90 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -15,6 +15,7 @@ # Use at least one worker per core if you're on a dedicated server, # more will usually help for _short_ waits on databases/caches. +# The minimum is 2 worker_processes 2 # Since Unicorn is never exposed to outside clients, it does not need to From ff48e1eeb73de6a98ba92c6f1b7bbddda17edcd0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 31 Oct 2014 11:12:41 +0200 Subject: [PATCH 203/408] Save only valid record in service migrations Signed-off-by: Dmitriy Zaporozhets --- db/migrate/20140907220153_serialize_service_properties.rb | 2 +- db/migrate/20141006143943_move_slack_service_to_webhook.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb index 6293015fa07..bd75ab1eacb 100644 --- a/db/migrate/20140907220153_serialize_service_properties.rb +++ b/db/migrate/20140907220153_serialize_service_properties.rb @@ -23,7 +23,7 @@ class SerializeServiceProperties < ActiveRecord::Migration associations[service.type.to_sym].each do |attribute| service.send("#{attribute}=", service.attributes[attribute.to_s]) end - service.save(validate: false) + service.save end remove_column :services, :project_url, :string diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb index 4b62b223cbf..a8e07033a5d 100644 --- a/db/migrate/20141006143943_move_slack_service_to_webhook.rb +++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb @@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration slack_service.properties.delete('subdomain') # Room is configured on the Slack side slack_service.properties.delete('room') - slack_service.save! + slack_service.save end end end From ef9f8677e65b4b791550daad4ebff8ae50c2f0d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 31 Oct 2014 12:08:45 +0200 Subject: [PATCH 204/408] Expose author username in project events API Signed-off-by: Dmitriy Zaporozhets --- doc/api/projects.md | 3 +++ lib/api/entities.rb | 6 ++++++ spec/requests/api/projects_spec.rb | 1 + 3 files changed, 10 insertions(+) diff --git a/doc/api/projects.md b/doc/api/projects.md index dfe3502b6e4..0055e2e476f 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -186,6 +186,7 @@ Parameters: "target_id": 830, "target_type": "Issue", "author_id": 1, + "author_username": "john", "data": null, "target_title": "Public project search field" }, @@ -196,6 +197,7 @@ Parameters: "target_id": null, "target_type": null, "author_id": 1, + "author_username": "john", "data": { "before": "50d4420237a9de7be1304607147aec22e4a14af7", "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", @@ -231,6 +233,7 @@ Parameters: "target_id": 840, "target_type": "Issue", "author_id": 1, + "author_username": "john", "data": null, "target_title": "Finish & merge Code search PR" } diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d19caf5b23a..4e7b1c91c4e 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -183,6 +183,12 @@ module API expose :target_id, :target_type, :author_id expose :data, :target_title expose :created_at + + expose :author_username do |event, options| + if event.author + event.author.username + end + end end class Namespace < Grape::Entity diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index ba7ec7b2be9..cb7a2705573 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -339,6 +339,7 @@ describe API::API, api: true do json_event['action_name'].should == 'joined' json_event['project_id'].to_i.should == project.id + json_event['author_username'].should == user.username end it "should return a 404 error if not found" do From 9b251bd1cc1ed551aad9bc67ca2743b8e2666c6e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 31 Oct 2014 12:10:19 +0200 Subject: [PATCH 205/408] Update changelog Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index cc287b67063..5dab8c864e7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ v 7.5.0 - Add Atlassian Bamboo CI service (Drew Blessing) - Mentioned @user will receive email even if he is not participating in issue or commit - Tie up loose ends with annotated tags: API & UI (Sean Edge) + - Return valid json for deleting branch via API (sponsored by O'Reilly Media) + - Expose username in project events API (sponsored by O'Reilly Media) v 7.4.2 - Fix internal snippet exposing for unauthenticated users From 76e3b8c6599e41c15c63076ebe2bbd27e7293043 Mon Sep 17 00:00:00 2001 From: Alvaro Naveda Date: Fri, 31 Oct 2014 11:55:56 +0100 Subject: [PATCH 206/408] Fixed markdown error in 7.3-to-7.4 update guide Just fixed a minor bug in the markdown notation --- doc/update/7.3-to-7.4.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 69d86fb06ed..3f471500c82 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -9,6 +9,7 @@ ```bash cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` ### 2. Get latest code From 884e916fe2ebb6095af1817c7a962623c12af34e Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Fri, 31 Oct 2014 12:51:25 +0100 Subject: [PATCH 207/408] Fix doc rake import md style [ci-skip] --- doc/raketasks/import.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 153a2e2575b..bb229e8acbb 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -1,13 +1,13 @@ # Import bare repositories into your GitLab instance -### Notes: +## Notes - The owner of the project will be the first admin - The groups will be created as needed - The owner of the group will be the first admin - Existing projects will be skipped -## How to use: +## How to use ### Create a new folder inside the git repositories path. This will be the name of the new group. @@ -16,7 +16,6 @@ it in the `/etc/gitlab/gitlab.rb` file. - For manual installations, it is usually located at: `/home/git/repositories` or you can see where your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry. - ### Copy your bare repositories inside this newly created folder: ``` @@ -40,7 +39,7 @@ $ cd /home/git/gitlab $ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production ``` -#### Example output: +#### Example output ``` Processing abcd.git From d549a2a525e524086c1697138e39b452e5c72c94 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Fri, 31 Oct 2014 13:00:50 +0100 Subject: [PATCH 208/408] Factor lib backend gitlab shell path --- lib/gitlab/backend/shell.rb | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index f95bbde5b39..ddb1ac61bf5 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -16,7 +16,7 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git" + system gitlab_shell_projects_path, 'add-project', "#{name}.git" end # Import repository @@ -27,7 +27,7 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240' + system gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240' end # Move repository @@ -39,7 +39,7 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" + system gitlab_shell_projects_path, 'mv-project', "#{path}.git", "#{new_path}.git" end # Update HEAD for repository @@ -51,7 +51,7 @@ module Gitlab # update_repository_head("gitlab/gitlab-ci", "3-1-stable") # def update_repository_head(path, branch) - system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch + system gitlab_shell_projects_path, 'update-head', "#{path}.git", branch end # Fork repository to new namespace @@ -63,7 +63,7 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace + system gitlab_shell_projects_path, 'fork-project', "#{path}.git", fork_namespace end # Remove repository from file system @@ -74,7 +74,7 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git" + system gitlab_shell_projects_path, 'rm-project', "#{name}.git" end # Add repository branch from passed ref @@ -87,7 +87,7 @@ module Gitlab # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # def add_branch(path, branch_name, ref) - system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref + system gitlab_shell_projects_path, 'create-branch', "#{path}.git", branch_name, ref end # Remove repository branch @@ -99,7 +99,7 @@ module Gitlab # rm_branch("gitlab/gitlab-ci", "4-0-stable") # def rm_branch(path, branch_name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name + system gitlab_shell_projects_path, 'rm-branch', "#{path}.git", branch_name end # Add repository tag from passed ref @@ -129,7 +129,7 @@ module Gitlab # rm_tag("gitlab/gitlab-ci", "v4.0") # def rm_tag(path, tag_name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name + system gitlab_shell_projects_path, 'rm-tag', "#{path}.git", tag_name end # Add new key to gitlab-shell @@ -138,7 +138,7 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content + system gitlab_shell_keys_path, 'add-key', key_id, key_content end # Batch-add keys to authorized_keys @@ -157,7 +157,7 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content + system gitlab_shell_keys_path, 'rm-key', key_id, key_content end # Remove all ssh keys from gitlab shell @@ -166,7 +166,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - system "#{gitlab_shell_path}/bin/gitlab-keys", "clear" + system gitlab_shell_keys_path, 'clear' end # Add empty directory for storing repositories @@ -249,5 +249,13 @@ module Gitlab def exists?(dir_name) File.exists?(full_path(dir_name)) end + + def gitlab_shell_projects_path + File.join(gitlab_shell_path, 'bin', 'gitlab-projects') + end + + def gitlab_shell_keys_path + File.join(gitlab_shell_path, 'bin', 'gitlab-keys') + end end end From 3e55d56c905f119e482dba7a2a5ea0d19f089aeb Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Fri, 31 Oct 2014 17:22:16 -0700 Subject: [PATCH 209/408] remove feature label For automatic label generation remove label `feature` as it is basically means the same thing as label `enhancement`. --- lib/gitlab/issues_labels.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 0d34976736f..1bec6088292 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -15,7 +15,6 @@ module Gitlab { title: "support", color: yellow }, { title: "discussion", color: blue }, { title: "suggestion", color: blue }, - { title: "feature", color: green }, { title: "enhancement", color: green } ] From 54ded5d95b16ea09be50dc7a9347fb6d5c02b1d9 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sat, 1 Nov 2014 22:26:12 +0100 Subject: [PATCH 210/408] Continue strings with backslash instead of append --- spec/models/slack_message_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/models/slack_message_spec.rb b/spec/models/slack_message_spec.rb index 1cd58534702..a07273e99af 100644 --- a/spec/models/slack_message_spec.rb +++ b/spec/models/slack_message_spec.rb @@ -26,11 +26,11 @@ describe SlackMessage do it 'returns a message regarding pushes' do subject.pretext.should == - 'user_name pushed to branch of ' << + 'user_name pushed to branch of '\ ' ()' subject.attachments.should == [ { - text: ": message1 - author1\n" << + text: ": message1 - author1\n"\ ": message2 - author2", color: color, } @@ -45,7 +45,7 @@ describe SlackMessage do it 'returns a message regarding a new branch' do subject.pretext.should == - 'user_name pushed new branch to ' << + 'user_name pushed new branch to '\ '' subject.attachments.should be_empty end From ffa586061dbbb9f10196653673bf18842d2078b3 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sat, 1 Nov 2014 22:30:46 +0100 Subject: [PATCH 211/408] Use require spec_helper instead of relative path More portable if either test or class gets moved, more uniform with the rest of the tests. --- spec/models/slack_message_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/slack_message_spec.rb b/spec/models/slack_message_spec.rb index 1cd58534702..78d743e01bf 100644 --- a/spec/models/slack_message_spec.rb +++ b/spec/models/slack_message_spec.rb @@ -1,4 +1,4 @@ -require_relative '../../app/models/project_services/slack_message' +require 'spec_helper' describe SlackMessage do subject { SlackMessage.new(args) } From bc0cf7458957ad6b2e32ef4176afc1cca2c9c9f7 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Sun, 2 Nov 2014 15:13:30 -0800 Subject: [PATCH 212/408] Make GitLab Shell upgrade a natural part of the upgrade process. --- doc/update/upgrader.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md index cf59b0e461c..44e18a9ed42 100644 --- a/doc/update/upgrader.md +++ b/doc/update/upgrader.md @@ -43,28 +43,31 @@ Check if GitLab and its dependencies are configured correctly: If all items are green, then congratulations upgrade is complete! -## 5. Upgrade GitLab Shell (if needed) +## 5. Upgrade GitLab Shell -If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it. - -Upgrade it by running the commands below after replacing 2.0.1 with the correct version number: +GitLab Shell might be outdated, running the commands below ensures you're using a compatible version: ``` cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.0.1 +sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` ``` ## One line upgrade command You've read through the entire guide and probably already did all the steps one by one. -Here is a one line command with step 1 to 4 for the next time you upgrade: +Here is a one line command with step 1 to 5 for the next time you upgrade: ```bash -cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ +cd /home/git/gitlab; \ + sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ sudo service gitlab stop; \ if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \ + cd /home/git/gitlab-shell; \ + sudo -u git -H git fetch; \ + sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \ + cd /home/git/gitlab; \ sudo service gitlab start; \ sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production ``` From 2edf212a8be3bb14b844b542df587b6029897fe6 Mon Sep 17 00:00:00 2001 From: Liam Monahan Date: Sat, 1 Nov 2014 19:14:42 -0400 Subject: [PATCH 213/408] Expose projects_limit through users API if UserFull. --- doc/api/users.md | 9 ++++++--- lib/api/entities.rb | 3 ++- spec/requests/api/users_spec.rb | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 3fdd3a75e88..20e0d68977e 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -78,7 +78,8 @@ GET /users "is_admin": false, "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ] ``` @@ -140,7 +141,8 @@ Parameters: "color_scheme_id": 2, "is_admin": false, "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ``` @@ -240,7 +242,8 @@ GET /user "color_scheme_id": 2, "is_admin": false, "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d19caf5b23a..db2dead487f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -16,7 +16,8 @@ module API class UserFull < User expose :email - expose :theme_id, :color_scheme_id, :extern_uid, :provider + expose :theme_id, :color_scheme_id, :extern_uid, :provider, \ + :projects_limit expose :can_create_group?, as: :can_create_group expose :can_create_project?, as: :can_create_project end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index bc1598273be..3bb6191ed9f 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -433,6 +433,7 @@ describe API::API, api: true do json_response['is_admin'].should == user.is_admin? json_response['can_create_project'].should == user.can_create_project? json_response['can_create_group'].should == user.can_create_group? + json_response['projects_limit'].should == user.projects_limit end it "should return 401 error if user is unauthenticated" do From e3098b69e7a4bc8b08bd85093204305991d8370d Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Mon, 3 Nov 2014 11:25:31 -0300 Subject: [PATCH 214/408] Don't enable IPv4 *only* on nginx. The current configuration sample files only enable IPv4 by default, making the server inaccesible for many remote hosts (and an increasing amount every day). Enable IPv4 and IPv6 by default. Older servers with no external IPv6 connectivity will not fail since they'll have a local-link IPv6 address to bind to anyway. --- lib/support/nginx/gitlab | 3 ++- lib/support/nginx/gitlab-ssl | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 49a68c62293..6369c1e02ff 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -33,7 +33,8 @@ upstream gitlab { ## Normal HTTP host server { - listen *:80 default_server; + listen 0.0.0.0:80 default_server; + listen [::]:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice root /home/git/gitlab/public; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index cbb198086b5..e992ebaf656 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -39,7 +39,8 @@ upstream gitlab { ## Normal HTTP host server { - listen *:80 default_server; + listen 0.0.0.0:80; + listen [::]:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice @@ -50,7 +51,8 @@ server { ## HTTPS host server { - listen 443 ssl; + listen 0.0.0.0:443 ssl; + listen [::]:443 ssl default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; root /home/git/gitlab/public; From c49cb40f65d75a54c8471cb5207512ec145593cc Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 3 Nov 2014 20:17:02 +0100 Subject: [PATCH 215/408] Remove dead Event#new_branch? method --- app/models/event.rb | 4 ---- spec/models/event_spec.rb | 1 - 2 files changed, 5 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index c0b126713a6..65b4c2edfee 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -186,10 +186,6 @@ class Event < ActiveRecord::Base data[:ref]["refs/heads"] end - def new_branch? - commit_from =~ /^00000/ - end - def new_ref? commit_from =~ /^00000/ end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 1fdd959da9d..10beafc4994 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -60,7 +60,6 @@ describe Event do it { @event.push?.should be_true } it { @event.proper?.should be_true } - it { @event.new_branch?.should be_true } it { @event.tag?.should be_false } it { @event.branch_name.should == "master" } it { @event.author.should == @user } From c3be1517ae3c576f7f4248b82b611a833fe06675 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 3 Nov 2014 20:35:06 +0100 Subject: [PATCH 216/408] Factor '0' * 40 blank ref constants --- app/services/git_push_service.rb | 6 +++--- features/steps/dashboard/event_filters.rb | 2 +- features/steps/shared/project.rb | 2 +- lib/gitlab/git.rb | 5 +++++ spec/models/event_spec.rb | 2 +- spec/services/git_push_service_spec.rb | 2 +- 6 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 lib/gitlab/git.rb diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 8f2b0e347f6..3f5222c93f1 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -160,19 +160,19 @@ class GitPushService ref_parts = ref.split('/') # Return if this is not a push to a branch (e.g. new commits) - ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000" + ref_parts[1] =~ /heads/ && oldrev != Gitlab::Git::BLANK_SHA end def push_to_new_branch?(ref, oldrev) ref_parts = ref.split('/') - ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000" + ref_parts[1] =~ /heads/ && oldrev == Gitlab::Git::BLANK_SHA end def push_remove_branch?(ref, newrev) ref_parts = ref.split('/') - ref_parts[1] =~ /heads/ && newrev == "0000000000000000000000000000000000000000" + ref_parts[1] =~ /heads/ && newrev == Gitlab::Git::BLANK_SHA end def push_to_branch?(ref) diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb index 332bfa95d97..3da3d62d0c0 100644 --- a/features/steps/dashboard/event_filters.rb +++ b/features/steps/dashboard/event_filters.rb @@ -29,7 +29,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps step 'this project has push event' do data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/new_design", user_id: @user.id, diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 4b833850a1c..bd7e6e1d8b3 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -32,7 +32,7 @@ module SharedProject @project = Project.find_by(name: "Shop") data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "6d394385cf567f80a8fd85055db1ab4c5295806f", ref: "refs/heads/fix", user_id: @user.id, diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb new file mode 100644 index 00000000000..67aca5e36e9 --- /dev/null +++ b/lib/gitlab/git.rb @@ -0,0 +1,5 @@ +module Gitlab + module Git + BLANK_SHA = '0' * 40 + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 1fdd959da9d..1f1bc9ac737 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -36,7 +36,7 @@ describe Event do @user = project.owner data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/master", user_id: @user.id, diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 4ef053a767f..19b442573f4 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -8,7 +8,7 @@ describe GitPushService do let (:service) { GitPushService.new } before do - @blankrev = '0000000000000000000000000000000000000000' + @blankrev = Gitlab::Git::BLANK_SHA @oldrev = sample_commit.parent_id @newrev = sample_commit.id @ref = 'refs/heads/master' From 71ed0ab06974d0bc72ad737645c35facf2b01c31 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Mon, 3 Nov 2014 20:02:12 +0100 Subject: [PATCH 217/408] Fix push not allowed to protected branch if commit starts with 7 zeros. --- lib/gitlab/git.rb | 5 +++++ lib/gitlab/git_access.rb | 4 ++-- spec/lib/gitlab/git_access_spec.rb | 7 ++++--- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 lib/gitlab/git.rb diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb new file mode 100644 index 00000000000..67aca5e36e9 --- /dev/null +++ b/lib/gitlab/git.rb @@ -0,0 +1,5 @@ +module Gitlab + module Git + BLANK_SHA = '0' * 40 + end +end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index b768a99a0e8..129881060d5 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -67,7 +67,7 @@ module Gitlab if forced_push?(project, oldrev, newrev) :force_push_code_to_protected_branches # and we dont allow remove of protected branch - elsif newrev =~ /0000000/ + elsif newrev == Gitlab::Git::BLANK_SHA :remove_protected_branches else :push_code_to_protected_branches @@ -85,7 +85,7 @@ module Gitlab def forced_push?(project, oldrev, newrev) return false if project.empty_repo? - if oldrev !~ /00000000/ && newrev !~ /00000000/ + if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read missed_refs.split("\n").size > 0 else diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 570b03827a8..fe0a6bbdabb 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -55,12 +55,13 @@ describe Gitlab::GitAccess do def changes { - push_new_branch: '000000000 570e7b2ab refs/heads/wow', + push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow", push_master: '6f6d7e7ed 570e7b2ab refs/heads/master', push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature', - push_remove_protected_branch: '570e7b2ab 000000000 refs/heads/feature', + push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\ + 'refs/heads/feature', push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0', - push_new_tag: '000000000 570e7b2ab refs/tags/v7.8.9', + push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9", push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] } end From a56d0d47db5b11787472fbed37f23c60bf0e57fe Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 4 Nov 2014 11:16:53 +0100 Subject: [PATCH 218/408] Remove unneeded backslash: "\/" == "/" --- app/helpers/tree_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 9c611a1c147..8e209498323 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -66,7 +66,7 @@ module TreeHelper def tree_breadcrumbs(tree, max_links = 2) if @path.present? part_path = "" - parts = @path.split("\/") + parts = @path.split('/') yield('..', nil) if parts.count > max_links From bc403356bb16da28a8c9cb486decf308036fd8f5 Mon Sep 17 00:00:00 2001 From: Don Luchini Date: Tue, 4 Nov 2014 10:02:38 -0500 Subject: [PATCH 219/408] Do not require immediate password reset if specifying one when seeding database. --- db/fixtures/production/001_admin.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index e0b13db020d..6fe6f63469e 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,8 +1,12 @@ -password = if ENV['GITLAB_ROOT_PASSWORD'].blank? - "5iveL!fe" - else - ENV['GITLAB_ROOT_PASSWORD'] - end +password = nil +expire_time = nil +if ENV['GITLAB_ROOT_PASSWORD'].blank? + password = '5iveL!fe' + expire_time = Time.now +else + password = ENV['GITLAB_ROOT_PASSWORD'] + expire_time = nil +end admin = User.create( email: "admin@example.com", @@ -10,7 +14,7 @@ admin = User.create( username: 'root', password: password, password_confirmation: password, - password_expires_at: Time.now, + password_expires_at: expire_time, theme_id: Gitlab::Theme::MARS ) From 6f34d40436531029228e78d7a55a0e3982dbf89e Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 5 Nov 2014 11:04:08 +0200 Subject: [PATCH 220/408] remove auth duplication --- app/controllers/projects/base_tree_controller.rb | 1 - app/controllers/projects/blame_controller.rb | 1 - app/controllers/projects/blob_controller.rb | 1 - app/controllers/projects/branches_controller.rb | 1 - app/controllers/projects/commit_controller.rb | 1 - app/controllers/projects/commits_controller.rb | 1 - app/controllers/projects/compare_controller.rb | 1 - app/controllers/projects/graphs_controller.rb | 1 - app/controllers/projects/network_controller.rb | 1 - app/controllers/projects/raw_controller.rb | 1 - app/controllers/projects/refs_controller.rb | 1 - app/controllers/projects/repositories_controller.rb | 1 - app/controllers/projects/tags_controller.rb | 2 -- app/controllers/projects_controller.rb | 1 - 14 files changed, 15 deletions(-) diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb index 56c306063c8..a7b1b7b40e8 100644 --- a/app/controllers/projects/base_tree_controller.rb +++ b/app/controllers/projects/base_tree_controller.rb @@ -1,7 +1,6 @@ class Projects::BaseTreeController < Projects::ApplicationController include ExtractsPath - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project end diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index bad06e7aa2d..367d1295f34 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -3,7 +3,6 @@ class Projects::BlameController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 04aa044001e..2412800c493 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -3,7 +3,6 @@ class Projects::BlobController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project before_filter :authorize_push_code!, only: [:destroy] diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 9f50660a5ad..9ebd498e7fa 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -1,6 +1,5 @@ class Projects::BranchesController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :require_non_empty_project before_filter :authorize_download_code! diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index cf05e6ea220..dac858d8e16 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -3,7 +3,6 @@ # Not to be confused with CommitsController, plural. class Projects::CommitController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project before_filter :commit diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 53a0d063d8e..9476b6c0284 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -4,7 +4,6 @@ class Projects::CommitsController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 6d944025598..ffb8c2e4af1 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,6 +1,5 @@ class Projects::CompareController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 21d3970d65a..4a318cb7d56 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -1,6 +1,5 @@ class Projects::GraphsController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 009089ee639..ada1aed0df7 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -3,7 +3,6 @@ class Projects::NetworkController < Projects::ApplicationController include ApplicationHelper # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index f4fdd616c50..fdbc4c5a098 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -3,7 +3,6 @@ class Projects::RawController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 9ac189a78b3..5d9336bdc49 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -2,7 +2,6 @@ class Projects::RefsController < Projects::ApplicationController include ExtractsPath # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 6d8ef0f1ac8..bcd14a1c847 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -1,6 +1,5 @@ class Projects::RepositoriesController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :authorize_download_code! before_filter :require_non_empty_project diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 94794fb5dd0..162ddef0fec 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -1,8 +1,6 @@ class Projects::TagsController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :require_non_empty_project - before_filter :authorize_download_code! before_filter :authorize_push_code!, only: [:create] before_filter :authorize_admin_project!, only: [:destroy] diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f81fc29677b..5a80a2ca465 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -4,7 +4,6 @@ class ProjectsController < ApplicationController before_filter :repository, except: [:new, :create] # Authorize - before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import] layout 'navless', only: [:new, :create, :fork] From 3246ed514fac233ba9aa9ab86e08336225d40150 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Nov 2014 11:44:40 +0200 Subject: [PATCH 221/408] Update GitLab CI service to work with new GitLab CI Signed-off-by: Dmitriy Zaporozhets --- app/models/project_services/gitlab_ci_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index a897c4ab76b..fadebf968bc 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -28,7 +28,7 @@ class GitlabCiService < CiService end def commit_status_path(sha) - project_url + "/builds/#{sha}/status.json?token=#{token}" + project_url + "/commits/#{sha}/status.json?token=#{token}" end def get_ci_build(sha) @@ -55,7 +55,7 @@ class GitlabCiService < CiService end def build_page(sha) - project_url + "/builds/#{sha}" + project_url + "/commits/#{sha}" end def builds_path From 5a8ec1f6712ea044500c015e55f7515007a8285e Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 5 Nov 2014 12:18:26 +0100 Subject: [PATCH 222/408] Create a failing test where commit in mr creates a mr mention note. --- spec/models/note_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 2d839e9611b..6ab7162c15c 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -249,6 +249,12 @@ describe Note do its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" } end + context 'commit contained in a merge request' do + subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) } + + it { should be_nil } + end + context 'commit from issue' do subject { Note.create_cross_reference_note(commit, issue, author, project) } From 0b1084a4538bc46684c8620410988d3b1093e7ab Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 4 Nov 2014 00:50:07 +0100 Subject: [PATCH 223/408] Don't output to stdout from lib non-interactive methods It pollutes the test output too much. --- lib/gitlab/backend/shell.rb | 37 +++++++++++++++++++++------------ lib/gitlab/git_ref_validator.rb | 3 ++- lib/gitlab/utils.rb | 14 +++++++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 lib/gitlab/utils.rb diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index ddb1ac61bf5..cc320da751c 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -16,7 +16,8 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - system gitlab_shell_projects_path, 'add-project', "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'add-project', "#{name}.git"]) end # Import repository @@ -27,7 +28,8 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240' + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project', + "#{name}.git", url, '240']) end # Move repository @@ -39,7 +41,8 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - system gitlab_shell_projects_path, 'mv-project', "#{path}.git", "#{new_path}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', + "#{path}.git", "#{new_path}.git"]) end # Update HEAD for repository @@ -51,7 +54,8 @@ module Gitlab # update_repository_head("gitlab/gitlab-ci", "3-1-stable") # def update_repository_head(path, branch) - system gitlab_shell_projects_path, 'update-head', "#{path}.git", branch + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head', + "#{path}.git", branch]) end # Fork repository to new namespace @@ -63,7 +67,8 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - system gitlab_shell_projects_path, 'fork-project', "#{path}.git", fork_namespace + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project', + "#{path}.git", fork_namespace]) end # Remove repository from file system @@ -74,7 +79,8 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - system gitlab_shell_projects_path, 'rm-project', "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'rm-project', "#{name}.git"]) end # Add repository branch from passed ref @@ -87,7 +93,8 @@ module Gitlab # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # def add_branch(path, branch_name, ref) - system gitlab_shell_projects_path, 'create-branch', "#{path}.git", branch_name, ref + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch', + "#{path}.git", branch_name, ref]) end # Remove repository branch @@ -99,7 +106,8 @@ module Gitlab # rm_branch("gitlab/gitlab-ci", "4-0-stable") # def rm_branch(path, branch_name) - system gitlab_shell_projects_path, 'rm-branch', "#{path}.git", branch_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch', + "#{path}.git", branch_name]) end # Add repository tag from passed ref @@ -117,7 +125,7 @@ module Gitlab cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git #{tag_name} #{ref}) cmd << message unless message.nil? || message.empty? - system *cmd + Gitlab::Utils.system_silent(cmd) end # Remove repository tag @@ -129,7 +137,8 @@ module Gitlab # rm_tag("gitlab/gitlab-ci", "v4.0") # def rm_tag(path, tag_name) - system gitlab_shell_projects_path, 'rm-tag', "#{path}.git", tag_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag', + "#{path}.git", tag_name]) end # Add new key to gitlab-shell @@ -138,7 +147,8 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - system gitlab_shell_keys_path, 'add-key', key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'add-key', key_id, key_content]) end # Batch-add keys to authorized_keys @@ -157,7 +167,8 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - system gitlab_shell_keys_path, 'rm-key', key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'rm-key', key_id, key_content]) end # Remove all ssh keys from gitlab shell @@ -166,7 +177,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - system gitlab_shell_keys_path, 'clear' + Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) end # Add empty directory for storing repositories diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 13cb08948bb..0fdd4dbe577 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -5,7 +5,8 @@ module Gitlab # # Returns true for a valid reference name, false otherwise def validate(ref_name) - system *%W(git check-ref-format refs/#{ref_name}) + Gitlab::Utils.system_silent( + %W(git check-ref-format refs/#{ref_name})) == 0 end end end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb new file mode 100644 index 00000000000..bc30364550a --- /dev/null +++ b/lib/gitlab/utils.rb @@ -0,0 +1,14 @@ +module Gitlab + module Utils + extend self + + # Run system command without outputting to stdout. + # + # @param cmd [Array] + # @return [Integer] exit status + def system_silent(cmd) + IO.popen(cmd).close + $?.exitstatus + end + end +end From 2ee1ec430012e4489ea1d70a13bcb827cafede2e Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 5 Nov 2014 12:53:10 +0100 Subject: [PATCH 224/408] Do not allow cross reference note in a mr if a mr contains mentioned commit. --- app/models/note.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/models/note.rb b/app/models/note.rb index f0ed7580b4c..4252d57ccb1 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -90,7 +90,7 @@ class Note < ActiveRecord::Base note_options.merge!(noteable: noteable) end - create(note_options) + create(note_options) unless cross_reference_disallowed?(noteable, mentioner) end def create_milestone_change_note(noteable, project, author, milestone) @@ -165,6 +165,15 @@ class Note < ActiveRecord::Base [:discussion, type.try(:underscore), id, line_code].join("-").to_sym end + # Determine if cross reference note should be created. + # eg. mentioning a commit in MR comments which exists inside a MR + # should not create "mentioned in" note. + def cross_reference_disallowed?(noteable, mentioner) + if mentioner.kind_of?(MergeRequest) + mentioner.commits.map(&:id).include? noteable.id + end + end + # Determine whether or not a cross-reference note already exists. def cross_reference_exists?(noteable, mentioner) gfm_reference = mentioner_gfm_ref(noteable, mentioner) From d59f8abea56be5c9ffdafc77c4d5755161a903a4 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 5 Nov 2014 13:58:51 +0100 Subject: [PATCH 225/408] Fix tests after change to regex validation message. --- spec/requests/api/projects_spec.rb | 5 ++--- spec/requests/api/users_spec.rb | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cb7a2705573..067935c82a6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -209,9 +209,8 @@ describe API::API, api: true do json_response['message']['path'].should == [ 'can\'t be blank', 'is too short (minimum is 0 characters)', - 'can contain only letters, digits, \'_\', \'-\' and \'.\'. It must '\ - 'start with letter, digit or \'_\', optionally preceeded by \'.\'. '\ - 'It must not end in \'.git\'.' + 'can contain only letters, digits, \'_\', \'-\' and \'.\'. ' \ + 'Cannot start with \'-\' or end in \'.git\'' ] end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 3bb6191ed9f..a1a26d80a14 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -141,8 +141,7 @@ describe API::API, api: true do should == ['must be greater than or equal to 0'] json_response['message']['username']. should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\ - '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.'] + '\'_\', \'-\' and \'.\'. Cannot start with \'-\' or end in \'.git\''] end it "shouldn't available for non admin users" do @@ -285,8 +284,7 @@ describe API::API, api: true do should == ['must be greater than or equal to 0'] json_response['message']['username']. should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\ - '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.'] + '\'_\', \'-\' and \'.\'. Cannot start with \'-\' or end in \'.git\''] end context "with existing user" do From 019c0f9e0f0f8b74803be57cb9f8ef8ab2e057ef Mon Sep 17 00:00:00 2001 From: Don Luchini Date: Wed, 5 Nov 2014 08:57:05 -0500 Subject: [PATCH 226/408] Remove unnecessary lines. --- db/fixtures/production/001_admin.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index 6fe6f63469e..0755ac714e1 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,5 +1,3 @@ -password = nil -expire_time = nil if ENV['GITLAB_ROOT_PASSWORD'].blank? password = '5iveL!fe' expire_time = Time.now From 4a41d4b7d246c4e5f9a9062c7dd417510b0bae0c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Nov 2014 16:21:35 +0200 Subject: [PATCH 227/408] Modify tests to match new gitlab_ci_service logic Signed-off-by: Dmitriy Zaporozhets --- spec/models/gitlab_ci_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb index ebc377047be..83277058fbb 100644 --- a/spec/models/gitlab_ci_service_spec.rb +++ b/spec/models/gitlab_ci_service_spec.rb @@ -34,11 +34,11 @@ describe GitlabCiService do end describe :commit_status_path do - it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c/status.json?token=verySecret"} + it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c/status.json?token=verySecret"} end describe :build_page do - it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c"} + it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c"} end end end From 98db90c4c9f33d16f496ebb5fe589d6312f136c4 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Wed, 5 Nov 2014 15:45:54 +0100 Subject: [PATCH 228/408] Factor regex error messages with spec API tests --- spec/requests/api/projects_spec.rb | 6 ++---- spec/requests/api/users_spec.rb | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 067935c82a6..2c4b68c10b6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -203,14 +203,12 @@ describe API::API, api: true do json_response['message']['name'].should == [ 'can\'t be blank', 'is too short (minimum is 0 characters)', - 'can contain only letters, digits, \'_\', \'-\' and \'.\' and '\ - 'space. It must start with letter, digit or \'_\'.' + Gitlab::Regex.project_regex_message ] json_response['message']['path'].should == [ 'can\'t be blank', 'is too short (minimum is 0 characters)', - 'can contain only letters, digits, \'_\', \'-\' and \'.\'. ' \ - 'Cannot start with \'-\' or end in \'.git\'' + Gitlab::Regex.send(:default_regex_message) ] end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a1a26d80a14..113a39b870e 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -140,8 +140,7 @@ describe API::API, api: true do json_response['message']['projects_limit']. should == ['must be greater than or equal to 0'] json_response['message']['username']. - should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. Cannot start with \'-\' or end in \'.git\''] + should == [Gitlab::Regex.send(:default_regex_message)] end it "shouldn't available for non admin users" do @@ -283,8 +282,7 @@ describe API::API, api: true do json_response['message']['projects_limit']. should == ['must be greater than or equal to 0'] json_response['message']['username']. - should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. Cannot start with \'-\' or end in \'.git\''] + should == [Gitlab::Regex.send(:default_regex_message)] end context "with existing user" do From 6d775ddae4e561b9a932177fdaf5f6a4a16030a2 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 5 Nov 2014 13:58:51 +0100 Subject: [PATCH 229/408] Fix tests after change to regex validation message. --- spec/requests/api/projects_spec.rb | 5 ++--- spec/requests/api/users_spec.rb | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cb7a2705573..067935c82a6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -209,9 +209,8 @@ describe API::API, api: true do json_response['message']['path'].should == [ 'can\'t be blank', 'is too short (minimum is 0 characters)', - 'can contain only letters, digits, \'_\', \'-\' and \'.\'. It must '\ - 'start with letter, digit or \'_\', optionally preceeded by \'.\'. '\ - 'It must not end in \'.git\'.' + 'can contain only letters, digits, \'_\', \'-\' and \'.\'. ' \ + 'Cannot start with \'-\' or end in \'.git\'' ] end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 3bb6191ed9f..a1a26d80a14 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -141,8 +141,7 @@ describe API::API, api: true do should == ['must be greater than or equal to 0'] json_response['message']['username']. should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\ - '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.'] + '\'_\', \'-\' and \'.\'. Cannot start with \'-\' or end in \'.git\''] end it "shouldn't available for non admin users" do @@ -285,8 +284,7 @@ describe API::API, api: true do should == ['must be greater than or equal to 0'] json_response['message']['username']. should == ['can contain only letters, digits, '\ - '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\ - '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.'] + '\'_\', \'-\' and \'.\'. Cannot start with \'-\' or end in \'.git\''] end context "with existing user" do From f36db59d97b375744ee1c05d07792a8d64ae945b Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Wed, 5 Nov 2014 17:14:22 +0100 Subject: [PATCH 230/408] Factor GITLAB_SHELL_VERSION get method --- lib/gitlab/backend/shell.rb | 7 +++++++ lib/tasks/gitlab/check.rake | 10 +++------- lib/tasks/gitlab/shell.rake | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index cc320da751c..aabc7f1e69a 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -8,6 +8,13 @@ module Gitlab end end + class << self + def version_required + @version_required ||= File.read(Rails.root. + join('GITLAB_SHELL_VERSION')).strip + end + end + # Init new repository # # name - project path with namespace diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 56e8ff44988..7ff23a7600a 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -574,20 +574,16 @@ namespace :gitlab do Gitlab::Shell.new.version end - def required_gitlab_shell_version - File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip - end - def gitlab_shell_major_version - required_gitlab_shell_version.split(".")[0].to_i + Gitlab::Shell.version_required.split('.')[0].to_i end def gitlab_shell_minor_version - required_gitlab_shell_version.split(".")[1].to_i + Gitlab::Shell.version_required.split('.')[1].to_i end def gitlab_shell_patch_version - required_gitlab_shell_version.split(".")[2].to_i + Gitlab::Shell.version_required.split('.')[2].to_i end def has_gitlab_shell3? diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 55f338add6a..1e2d64b56c9 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -4,7 +4,7 @@ namespace :gitlab do task :install, [:tag, :repo] => :environment do |t, args| warn_user_is_not_gitlab - default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip + default_version = Gitlab::Shell.version_required args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") user = Gitlab.config.gitlab.user From 383ac10ca5797818f7a61d04fbff0fbf54e87c0e Mon Sep 17 00:00:00 2001 From: Alex Elman Date: Wed, 27 Aug 2014 11:20:28 -0500 Subject: [PATCH 231/408] Issue-280 Send notifications when a note is added to a commit and author is a group member This fixes a bug where commit authors weren't receiving email notifications for notes added to their commits and their membership was in the group but not the project. The fix is to look up membership via the team object which accounts for both project and group members. --- app/models/note.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index f0ed7580b4c..996def0478a 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -251,8 +251,8 @@ class Note < ActiveRecord::Base def commit_author @commit_author ||= - project.users.find_by(email: noteable.author_email) || - project.users.find_by(name: noteable.author_name) + project.team.users.find_by(email: noteable.author_email) || + project.team.users.find_by(name: noteable.author_name) rescue nil end From e4a38e447169069f3d5042d3341ceb4bdc51bf1b Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Wed, 5 Nov 2014 17:51:08 +0100 Subject: [PATCH 232/408] Factor using Repository#path_to_repo --- app/models/project_services/flowdock_service.rb | 3 +-- app/models/project_services/gemnasium_service.rb | 3 +-- lib/backup/repository.rb | 2 +- lib/tasks/gitlab/shell.rake | 2 +- spec/requests/api/repositories_spec.rb | 3 +-- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 0020b4482e5..86705f5dabd 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -37,13 +37,12 @@ class FlowdockService < Service end def execute(push_data) - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") Flowdock::Git.post( push_data[:ref], push_data[:before], push_data[:after], token: token, - repo: repo_path, + repo: project.repository.path_to_repo, repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 6d2fc06a5d0..18fdd204ecd 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -38,14 +38,13 @@ class GemnasiumService < Service end def execute(push_data) - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") Gemnasium::GitlabService.execute( ref: push_data[:ref], before: push_data[:before], after: push_data[:after], token: token, api_key: api_key, - repo: repo_path + repo: project.repository.path_to_repo ) end end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 380beac708d..0bb02f1a357 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -91,7 +91,7 @@ module Backup protected def path_to_repo(project) - File.join(repos_path, project.path_with_namespace + '.git') + project.repository.path_to_repo end def path_to_bundle(project) diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 55f338add6a..6b8f9e377fe 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -76,7 +76,7 @@ namespace :gitlab do desc "GITLAB | Build missing projects" task build_missing_projects: :environment do Project.find_each(batch_size: 1000) do |project| - path_to_repo = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + path_to_repo = project.repository.path_to_repo if File.exists?(path_to_repo) print '-' else diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index dd7a0fc6cc8..beae71c02d9 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -37,8 +37,7 @@ describe API::API, api: true do context 'annotated tag' do it 'should create a new annotated tag' do # Identity must be set in .gitconfig to create annotated tag. - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, - project.path_with_namespace + '.git') + repo_path = project.repository.path_to_repo system(*%W(git --git-dir=#{repo_path} config user.name #{user.name})) system(*%W(git --git-dir=#{repo_path} config user.email #{user.email})) From 57c7dafbfe61950fef716b72126410cf449472e4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Nov 2014 16:35:56 +0200 Subject: [PATCH 233/408] Light gray bg for white code scheme if used in comments and wiki Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/highlight/white.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 815cf367ae8..8d5822937a0 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -186,3 +186,11 @@ } } } + +.readme-holder .wiki, .note-body, .wiki-holder { + .white { + .highlight, pre, .hljs { + background: #F9F9F9; + } + } +} From b33d4bc2f1d26ee3526b9d7f530f468a9d5b5a5e Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 6 Nov 2014 11:58:00 +0200 Subject: [PATCH 234/408] Revert "Don't output to stdout from lib non-interactive methods" This reverts commit 0b1084a4538bc46684c8620410988d3b1093e7ab. --- lib/gitlab/backend/shell.rb | 37 ++++++++++++--------------------- lib/gitlab/git_ref_validator.rb | 3 +-- lib/gitlab/utils.rb | 14 ------------- 3 files changed, 14 insertions(+), 40 deletions(-) delete mode 100644 lib/gitlab/utils.rb diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index cc320da751c..ddb1ac61bf5 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -16,8 +16,7 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, - 'add-project', "#{name}.git"]) + system gitlab_shell_projects_path, 'add-project', "#{name}.git" end # Import repository @@ -28,8 +27,7 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project', - "#{name}.git", url, '240']) + system gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240' end # Move repository @@ -41,8 +39,7 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', - "#{path}.git", "#{new_path}.git"]) + system gitlab_shell_projects_path, 'mv-project', "#{path}.git", "#{new_path}.git" end # Update HEAD for repository @@ -54,8 +51,7 @@ module Gitlab # 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]) + system gitlab_shell_projects_path, 'update-head', "#{path}.git", branch end # Fork repository to new namespace @@ -67,8 +63,7 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project', - "#{path}.git", fork_namespace]) + system gitlab_shell_projects_path, 'fork-project', "#{path}.git", fork_namespace end # Remove repository from file system @@ -79,8 +74,7 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, - 'rm-project', "#{name}.git"]) + system gitlab_shell_projects_path, 'rm-project', "#{name}.git" end # Add repository branch from passed ref @@ -93,8 +87,7 @@ module Gitlab # 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]) + system gitlab_shell_projects_path, 'create-branch', "#{path}.git", branch_name, ref end # Remove repository branch @@ -106,8 +99,7 @@ module Gitlab # 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]) + system gitlab_shell_projects_path, 'rm-branch', "#{path}.git", branch_name end # Add repository tag from passed ref @@ -125,7 +117,7 @@ module Gitlab cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git #{tag_name} #{ref}) cmd << message unless message.nil? || message.empty? - Gitlab::Utils.system_silent(cmd) + system *cmd end # Remove repository tag @@ -137,8 +129,7 @@ module Gitlab # 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]) + system gitlab_shell_projects_path, 'rm-tag', "#{path}.git", tag_name end # Add new key to gitlab-shell @@ -147,8 +138,7 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - Gitlab::Utils.system_silent([gitlab_shell_keys_path, - 'add-key', key_id, key_content]) + system gitlab_shell_keys_path, 'add-key', key_id, key_content end # Batch-add keys to authorized_keys @@ -167,8 +157,7 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - Gitlab::Utils.system_silent([gitlab_shell_keys_path, - 'rm-key', key_id, key_content]) + system gitlab_shell_keys_path, 'rm-key', key_id, key_content end # Remove all ssh keys from gitlab shell @@ -177,7 +166,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) + system gitlab_shell_keys_path, 'clear' end # Add empty directory for storing repositories diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 0fdd4dbe577..13cb08948bb 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -5,8 +5,7 @@ module Gitlab # # Returns true for a valid reference name, false otherwise def validate(ref_name) - Gitlab::Utils.system_silent( - %W(git check-ref-format refs/#{ref_name})) == 0 + system *%W(git check-ref-format refs/#{ref_name}) end end end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb deleted file mode 100644 index bc30364550a..00000000000 --- a/lib/gitlab/utils.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Gitlab - module Utils - extend self - - # Run system command without outputting to stdout. - # - # @param cmd [Array] - # @return [Integer] exit status - def system_silent(cmd) - IO.popen(cmd).close - $?.exitstatus - end - end -end From d1b489e048e2bd9304ae335d9105e6efde99012b Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 6 Nov 2014 13:07:16 +0200 Subject: [PATCH 235/408] Revert "Revert "Don't output to stdout from lib non-interactive methods"" This reverts commit b33d4bc2f1d26ee3526b9d7f530f468a9d5b5a5e. --- lib/gitlab/backend/shell.rb | 37 +++++++++++++++++++++------------ lib/gitlab/git_ref_validator.rb | 3 ++- lib/gitlab/utils.rb | 14 +++++++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 lib/gitlab/utils.rb diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index ddb1ac61bf5..cc320da751c 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -16,7 +16,8 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - system gitlab_shell_projects_path, 'add-project', "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'add-project', "#{name}.git"]) end # Import repository @@ -27,7 +28,8 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240' + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project', + "#{name}.git", url, '240']) end # Move repository @@ -39,7 +41,8 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - system gitlab_shell_projects_path, 'mv-project', "#{path}.git", "#{new_path}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', + "#{path}.git", "#{new_path}.git"]) end # Update HEAD for repository @@ -51,7 +54,8 @@ module Gitlab # update_repository_head("gitlab/gitlab-ci", "3-1-stable") # def update_repository_head(path, branch) - system gitlab_shell_projects_path, 'update-head', "#{path}.git", branch + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head', + "#{path}.git", branch]) end # Fork repository to new namespace @@ -63,7 +67,8 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - system gitlab_shell_projects_path, 'fork-project', "#{path}.git", fork_namespace + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project', + "#{path}.git", fork_namespace]) end # Remove repository from file system @@ -74,7 +79,8 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - system gitlab_shell_projects_path, 'rm-project', "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'rm-project', "#{name}.git"]) end # Add repository branch from passed ref @@ -87,7 +93,8 @@ module Gitlab # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # def add_branch(path, branch_name, ref) - system gitlab_shell_projects_path, 'create-branch', "#{path}.git", branch_name, ref + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch', + "#{path}.git", branch_name, ref]) end # Remove repository branch @@ -99,7 +106,8 @@ module Gitlab # rm_branch("gitlab/gitlab-ci", "4-0-stable") # def rm_branch(path, branch_name) - system gitlab_shell_projects_path, 'rm-branch', "#{path}.git", branch_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch', + "#{path}.git", branch_name]) end # Add repository tag from passed ref @@ -117,7 +125,7 @@ module Gitlab cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git #{tag_name} #{ref}) cmd << message unless message.nil? || message.empty? - system *cmd + Gitlab::Utils.system_silent(cmd) end # Remove repository tag @@ -129,7 +137,8 @@ module Gitlab # rm_tag("gitlab/gitlab-ci", "v4.0") # def rm_tag(path, tag_name) - system gitlab_shell_projects_path, 'rm-tag', "#{path}.git", tag_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag', + "#{path}.git", tag_name]) end # Add new key to gitlab-shell @@ -138,7 +147,8 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - system gitlab_shell_keys_path, 'add-key', key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'add-key', key_id, key_content]) end # Batch-add keys to authorized_keys @@ -157,7 +167,8 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - system gitlab_shell_keys_path, 'rm-key', key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'rm-key', key_id, key_content]) end # Remove all ssh keys from gitlab shell @@ -166,7 +177,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - system gitlab_shell_keys_path, 'clear' + Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) end # Add empty directory for storing repositories diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 13cb08948bb..0fdd4dbe577 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -5,7 +5,8 @@ module Gitlab # # Returns true for a valid reference name, false otherwise def validate(ref_name) - system *%W(git check-ref-format refs/#{ref_name}) + Gitlab::Utils.system_silent( + %W(git check-ref-format refs/#{ref_name})) == 0 end end end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb new file mode 100644 index 00000000000..bc30364550a --- /dev/null +++ b/lib/gitlab/utils.rb @@ -0,0 +1,14 @@ +module Gitlab + module Utils + extend self + + # Run system command without outputting to stdout. + # + # @param cmd [Array] + # @return [Integer] exit status + def system_silent(cmd) + IO.popen(cmd).close + $?.exitstatus + end + end +end From bf8b87411701667a8d9e608b2e7b3171c4c3e551 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 6 Nov 2014 11:47:38 +0200 Subject: [PATCH 236/408] fix system silent call --- lib/gitlab/git_ref_validator.rb | 2 +- lib/gitlab/utils.rb | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 0fdd4dbe577..39d17def930 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -6,7 +6,7 @@ module Gitlab # Returns true for a valid reference name, false otherwise def validate(ref_name) Gitlab::Utils.system_silent( - %W(git check-ref-format refs/#{ref_name})) == 0 + %W(git check-ref-format refs/#{ref_name})) end end end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index bc30364550a..bd184c27187 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -5,10 +5,9 @@ module Gitlab # Run system command without outputting to stdout. # # @param cmd [Array] - # @return [Integer] exit status + # @return [Boolean] def system_silent(cmd) - IO.popen(cmd).close - $?.exitstatus + Popen::popen(cmd).last.zero? end end end From 9353db59a084a1524c19efba2ef185a15967f233 Mon Sep 17 00:00:00 2001 From: skv Date: Thu, 6 Nov 2014 22:34:41 +0300 Subject: [PATCH 237/408] remove unused js --- app/assets/javascripts/dispatcher.js.coffee | 2 -- app/assets/javascripts/team_members.js.coffee | 4 ---- app/views/projects/team_members/_team_member.html.haml | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 app/assets/javascripts/team_members.js.coffee diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index ec4b7ea42cf..fb1adbc4b3d 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -58,8 +58,6 @@ class Dispatcher when 'groups:show', 'projects:show' new Activities() shortcut_handler = new ShortcutsNavigation() - when 'projects:teams:members:index' - new TeamMembers() when 'groups:members' new GroupMembers() new UsersSelect() diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee deleted file mode 100644 index 32486f7da54..00000000000 --- a/app/assets/javascripts/team_members.js.coffee +++ /dev/null @@ -1,4 +0,0 @@ -class @TeamMembers - constructor: -> - $('.team-members .project-access-select').on "change", -> - $(this.form).submit() diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml index 5f29b58de32..7a9c0939ba0 100644 --- a/app/views/projects/team_members/_team_member.html.haml +++ b/app/views/projects/team_members/_team_member.html.haml @@ -5,7 +5,7 @@ - unless @project.personal? && user == current_user .pull-left = form_for(member, as: :project_member, url: project_team_member_path(@project, member.user)) do |f| - = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "medium project-access-select span2 trigger-submit" + = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "trigger-submit"   = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do %i.fa.fa-minus.fa-inverse From 6cac4e6271fcbfdd51feb3d32dc6c29905adb966 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Nov 2014 12:51:18 +0200 Subject: [PATCH 238/408] Fix attachment misaligned in comment Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/notes/_note.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 814bf19970c..a25c5e207fb 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -59,7 +59,7 @@ - if note.attachment.image? = link_to note.attachment.secure_url, target: '_blank' do = image_tag note.attachment.secure_url, class: 'note-image-attach' - .attachment.pull-right + .attachment = link_to note.attachment.secure_url, target: "_blank" do %i.fa.fa-paperclip = note.attachment_identifier From 2148e1997ace8bb5efab214c07492ed5a372dd31 Mon Sep 17 00:00:00 2001 From: Nikita Verkhovin Date: Sat, 8 Nov 2014 16:54:08 +0600 Subject: [PATCH 239/408] Add issue edited timestamp --- app/helpers/issues_helper.rb | 13 +++++++++++++ app/views/projects/issues/show.html.haml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 7671033b539..d513e0ba58e 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -62,6 +62,19 @@ module IssuesHelper '' end + def issue_timestamp(issue) + # Shows the created at time and the updated at time if different + ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}" + if issue.updated_at != issue.created_at + ts << capture_haml do + haml_tag :small do + haml_concat " (Edited #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')})" + end + end + end + ts.html_safe + end + # Checks if issues_tracker setting exists in gitlab.yml def external_issues_tracker_enabled? Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any? diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 71eb0d5c866..aad58e48f6c 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -39,7 +39,7 @@ Open .creator - Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)} + Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)} %h4.title = gfm escape_once(@issue.title) From 271a3520794d0d977ba3907963871da59cee554f Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 8 Nov 2014 23:33:27 -0800 Subject: [PATCH 240/408] minor updates & formatting changes minor updates @ formatting changes to match other versions of file. Unify formatting of https://github.com/gitlabhq/gitlabhq/blob/master/lib/support/nginx/gitlab, https://github.com/gitlabhq/gitlabhq/blob/master/lib/support/nginx/gitlab-ssl, & https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb --- lib/support/nginx/gitlab | 7 +++++-- lib/support/nginx/gitlab-ssl | 24 ++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 49a68c62293..eeba62c6b10 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -1,5 +1,5 @@ ## GitLab -## Maintainer: @randx +## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller ## ## Lines starting with two hashes (##) are comments with information. ## Lines starting with one hash (#) are configuration parameters that can be uncommented. @@ -15,7 +15,7 @@ ## - installing an old version of Nginx with the chunkin module [2] compiled in, or ## - using a newer version of Nginx. ## -## At the time of writing we do not know if either of these theoretical solutions works. +## At the time of writing we do not know if either of these theoretical solutions works. ## As a workaround users can use Git over SSH to push large files. ## ## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 @@ -26,6 +26,7 @@ ## configuration ## ################################### ## +## See installation.md#using-https for additional HTTPS configuration details. upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; @@ -42,6 +43,8 @@ server { ## Or if you want to accept large git objects over http client_max_body_size 20m; + ## See app/controllers/application_controller.rb for headers set + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index cbb198086b5..979e032a1c5 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -1,5 +1,5 @@ ## GitLab -## Contributors: randx, yin8086, sashkab, orkoden, axilleas +## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller ## ## Modified from nginx http version ## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/ @@ -26,9 +26,8 @@ ## [1] https://github.com/agentzh/chunkin-nginx-module#status ## [2] https://github.com/agentzh/chunkin-nginx-module ## -## ################################### -## SSL configuration ## +## configuration ## ################################### ## ## See installation.md#using-https for additional HTTPS configuration details. @@ -37,22 +36,22 @@ upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } -## Normal HTTP host +## Redirects all HTTP traffic to the HTTPS host server { listen *:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice - - ## Redirects all traffic to the HTTPS host - root /nowhere; ## root doesn't have to be a valid path since we are redirecting - rewrite ^ https://$server_name$request_uri? permanent; + return 301 https://$server_name$request_uri; + access_log /var/log/nginx/gitlab_access.log; + error_log /var/log/nginx/gitlab_error.log; } + ## HTTPS host server { listen 443 ssl; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com - server_tokens off; + server_tokens off; ## Don't show the nginx version number, a security best practice root /home/git/gitlab/public; ## Increase this if you want to upload large attachments @@ -70,12 +69,9 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; - ## [WARNING] The following header states that the browser should only communicate - ## with your server over a secure connection for the next 24 months. - add_header Strict-Transport-Security max-age=63072000; - add_header X-Frame-Options SAMEORIGIN; - add_header X-Content-Type-Options nosniff; + ## See app/controllers/application_controller.rb for headers set ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. ## Replace with your ssl_trusted_certificate. For more info see: From 6ace931c3548aaa6c229c7f38191d128e6dc1362 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 9 Nov 2014 03:37:56 -0800 Subject: [PATCH 241/408] make repo name link to repo homepage --- CHANGELOG | 1 + app/helpers/projects_helper.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ff41575bcc6..dcf4c5cd369 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ v 7.5.0 - Add time zone configuration on gitlab.yml (Sullivan Senechal) - Fix LDAP authentication for Git HTTP access - Fix LDAP config lookup for provider 'ldap' + - Project title links to project homepage (Ben Bodenmiller) - Add Atlassian Bamboo CI service (Drew Blessing) - Mentioned @user will receive email even if he is not participating in issue or commit - Session API: Use case-insensitive authentication like in UI (Andrey Krivko) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 883c1f63af6..fb5470d98e5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -42,12 +42,12 @@ module ProjectsHelper def project_title(project) if project.group content_tag :span do - link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name + link_to(simple_sanitize(project.group.name), group_path(project.group)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project)) end else owner = project.namespace.owner content_tag :span do - link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name + link_to(simple_sanitize(owner.name), user_path(owner)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project)) end end end From 280822cd379a7b7d3790f1546d677e14b700d2d1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Nov 2014 11:38:25 +0200 Subject: [PATCH 242/408] Version 7.6.0.rc1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 027a8b7b332..12ef9184d79 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.5.0.pre +7.6.0.rc1 \ No newline at end of file From 5136b6aec0b783189f46b4fad77c0b2beca9c3f1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Nov 2014 16:09:41 +0200 Subject: [PATCH 243/408] Revert "Version 7.6.0.rc1" This reverts commit 280822cd379a7b7d3790f1546d677e14b700d2d1. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 12ef9184d79..027a8b7b332 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.6.0.rc1 \ No newline at end of file +7.5.0.pre From f56541de9a488dec68f4e98f738f90c51d898fc9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Nov 2014 16:17:04 +0200 Subject: [PATCH 244/408] Revert "Create dev fixture projects with fixed visibility" This reverts commit a9fadce361163e97eb1de0ec62e4235ff0fa3daa. --- db/fixtures/development/04_project.rb | 78 ++++++++++--------- .../{08_milestones.rb => 07_milestones.rb} | 0 .../development/07_projects_visibility.rb | 38 --------- .../fixtures_development_helper.rb | 8 -- lib/gitlab/seeder.rb | 6 +- 5 files changed, 42 insertions(+), 88 deletions(-) rename db/fixtures/development/{08_milestones.rb => 07_milestones.rb} (100%) delete mode 100644 db/fixtures/development/07_projects_visibility.rb delete mode 100644 db/fixtures/development/fixtures_development_helper.rb diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index a39e7ac028c..ae4c0550a4f 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -1,48 +1,52 @@ -Gitlab::Seeder.quiet do - project_urls = [ - 'https://github.com/documentcloud/underscore.git', - 'https://gitlab.com/gitlab-org/gitlab-ce.git', - 'https://gitlab.com/gitlab-org/gitlab-ci.git', - 'https://gitlab.com/gitlab-org/gitlab-shell.git', - 'https://gitlab.com/gitlab-org/gitlab-test.git', - 'https://github.com/twitter/flight.git', - 'https://github.com/twitter/typeahead.js.git', - 'https://github.com/h5bp/html5-boilerplate.git', - ] +require 'sidekiq/testing' - project_urls.each do |url| - group_path, project_path = url.split('/')[-2..-1] +Sidekiq::Testing.inline! do + Gitlab::Seeder.quiet do + project_urls = [ + 'https://github.com/documentcloud/underscore.git', + 'https://gitlab.com/gitlab-org/gitlab-ce.git', + 'https://gitlab.com/gitlab-org/gitlab-ci.git', + 'https://gitlab.com/gitlab-org/gitlab-shell.git', + 'https://gitlab.com/gitlab-org/gitlab-test.git', + 'https://github.com/twitter/flight.git', + 'https://github.com/twitter/typeahead.js.git', + 'https://github.com/h5bp/html5-boilerplate.git', + ] - group = Group.find_by(path: group_path) + project_urls.each_with_index do |url, i| + group_path, project_path = url.split('/')[-2..-1] - unless group - group = Group.new( - name: group_path.titleize, - path: group_path - ) - group.description = Faker::Lorem.sentence - group.save + group = Group.find_by(path: group_path) - group.add_owner(User.first) - end + unless group + group = Group.new( + name: group_path.titleize, + path: group_path + ) + group.description = Faker::Lorem.sentence + group.save - project_path.gsub!('.git', '') + group.add_owner(User.first) + end - params = { - import_url: url, - namespace_id: group.id, - name: project_path.titleize, - description: Faker::Lorem.sentence, - visibility_level: Gitlab::VisibilityLevel.values.sample - } + project_path.gsub!(".git", "") - project = Projects::CreateService.new(User.first, params).execute + params = { + import_url: url, + namespace_id: group.id, + name: project_path.titleize, + description: Faker::Lorem.sentence, + visibility_level: Gitlab::VisibilityLevel.values.sample + } - if project.valid? - print '.' - else - puts project.errors.full_messages - print 'F' + project = Projects::CreateService.new(User.first, params).execute + + if project.valid? + print '.' + else + puts project.errors.full_messages + print 'F' + end end end end diff --git a/db/fixtures/development/08_milestones.rb b/db/fixtures/development/07_milestones.rb similarity index 100% rename from db/fixtures/development/08_milestones.rb rename to db/fixtures/development/07_milestones.rb diff --git a/db/fixtures/development/07_projects_visibility.rb b/db/fixtures/development/07_projects_visibility.rb deleted file mode 100644 index c3287584a07..00000000000 --- a/db/fixtures/development/07_projects_visibility.rb +++ /dev/null @@ -1,38 +0,0 @@ -require Rails.root.join('db', 'fixtures', Rails.env, 'fixtures_development_helper') - -Gitlab::Seeder.quiet do - Gitlab::VisibilityLevel.options.each do |visibility_label, visibility_value| - visibility_label_downcase = visibility_label.downcase - begin - user = User.seed(:username) do |s| - username = "#{visibility_label_downcase}-owner" - s.username = username - s.name = "#{visibility_label} Owner" - s.email = "#{username}@example.com" - s.password = '12345678' - s.confirmed_at = DateTime.now - end[0] - - # import_url does not work for local paths, - # so we just copy the template repository in. - unless Project.find_with_namespace("#{user.namespace.id}/"\ - "#{visibility_label_downcase}") - params = { - name: "#{visibility_label} Project", - description: "#{visibility_label} Project description", - namespace_id: user.namespace.id, - visibility_level: visibility_value, - } - project = Projects::CreateService.new(user, params).execute - new_path = project.repository.path - FileUtils.rm_rf(new_path) - FileUtils.cp_r(FixturesDevelopmentHelper.template_project.repository.path, - new_path) - end - - print '.' - rescue ActiveRecord::RecordNotSaved - print 'F' - end - end -end diff --git a/db/fixtures/development/fixtures_development_helper.rb b/db/fixtures/development/fixtures_development_helper.rb deleted file mode 100644 index 22a7834bbee..00000000000 --- a/db/fixtures/development/fixtures_development_helper.rb +++ /dev/null @@ -1,8 +0,0 @@ -module FixturesDevelopmentHelper - class << self - def template_project - @template_project ||= Project. - find_with_namespace('gitlab-org/gitlab-test') - end - end -end diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb index e816eedab9e..31aa3528c4c 100644 --- a/lib/gitlab/seeder.rb +++ b/lib/gitlab/seeder.rb @@ -1,13 +1,9 @@ -require 'sidekiq/testing' - module Gitlab class Seeder def self.quiet mute_mailer SeedFu.quiet = true - Sidekiq::Testing.inline! do - yield - end + yield SeedFu.quiet = false puts "\nOK".green end From 667c0a909bde1cf71f21d8ec9768e98b1c489030 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 7 Nov 2014 12:18:00 -0600 Subject: [PATCH 245/408] Custom git hook documentation --- doc/README.md | 1 + doc/hooks/custom_hooks.md | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 doc/hooks/custom_hooks.md diff --git a/doc/README.md b/doc/README.md index 7343d5ae273..b9aa12f7675 100644 --- a/doc/README.md +++ b/doc/README.md @@ -16,6 +16,7 @@ - [Install](install/README.md) Requirements, directory structures and manual installation. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier. +- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. - [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Update](update/README.md) Update guides to upgrade your installation. diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md new file mode 100644 index 00000000000..00867ead80d --- /dev/null +++ b/doc/hooks/custom_hooks.md @@ -0,0 +1,41 @@ +# Custom Git Hooks + +**Note: Custom git hooks must be configured on the filesystem of the GitLab +server. Only GitLab server administrators will be able to complete these tasks. +Please explore webhooks as an option if you do not have filesystem access.** + +Git natively supports hooks that are executed on different actions. +Examples of server-side git hooks include pre-receive, post-receive, and update. +See +[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) +for more information about each hook type. + +As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab +administrators can add custom git hooks to any GitLab project. + +## Setup + +Normally, git hooks are placed in the repository or project's `hooks` directory. +GitLab creates a symlink from each project's `hooks` directory to the +gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell +upgrades. As such, custom hooks are implemented a little differently. Behavior +is exactly the same once the hook is created, though. Follow these steps to +set up a custom hook. + +1. Pick a project that needs a custom git hook. +1. On the GitLab server, navigate to the project's repository directory. +For a manual install the path is usually +`/home/git/repositories//.git`. For Omnibus installs the path is +usually `/var/opt/gitlab/git-data/repositories//.git`. +1. Create a new directory in this location called `custom_hooks`. +1. Inside the new `custom_hooks` directory, create a file with a name matching +the hook type. For a pre-receive hook the file name should be `pre-receive` with +no extension. +1. Make the hook file executable and make sure it's owned by git. +1. Write the code to make the git hook function as expected. Hooks can be +in any language. Ensure the 'shebang' at the top properly reflects the language +type. For example, if the script is in Ruby the shebang will probably be +`#!/usr/bin/env ruby`. + +That's it! Assuming the hook code is properly implemented the hook will fire +as appropriate. From 07cca8cc0c5bd6913e55c99a0493dc2f92dcdbb5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 11 Nov 2014 11:47:44 +0200 Subject: [PATCH 246/408] Use release tool for monthly releases Signed-off-by: Dmitriy Zaporozhets --- doc/release/monthly.md | 86 +++++++++++------------------------------- 1 file changed, 23 insertions(+), 63 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 386c19c0fe0..7354efc3e2e 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -143,35 +143,19 @@ Make sure the code quality indicators are green / good. - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) -### **4. Set VERSION** +### **4. Run release tool** -Change version in VERSION to `x.x.0.rc1`. - -### **5. Tag** - -Create an annotated tag that points to the version change commit: +Get release tools ``` -git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1' +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools ``` -Tags should be created for both GitLab CE and GitLab EE. Don't forget to push tags to all remotes. +Create release candidate and stable branch: ``` -git push remote_name vx.x.0.rc1 -``` - -### **6. Create stable branches** - -For GitLab EE, append `-ee` to the branch. - -`x-x-stable-ee` - -``` -git checkout master -git pull -git checkout -b x-x-stable -git push x-x-stable +bundle exec rake release["x.x.0.rc1"] ``` Now developers can use master for merging new features. @@ -245,69 +229,45 @@ create an issue about it in order to discuss the next steps after the release. # **22nd - Release CE and EE** -For GitLab EE, append `-ee` to the branches and tags. +**Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`** -`x-x-stable-ee` -`v.x.x.0-ee` +### **1. Release code** -Note: Merge CE into EE if needed. +Get release tools -### **1. Set VERSION to x.x.x and push** +``` +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools +``` + +Bump version, create release tag and push to remotes: + +``` +bundle exec rake release["x.x.0"] +``` -- Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed. -- Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed. -- Change the VERSION file in `master` branch of the CE repository and commit and push to origin. -- Change the VERSION file in `master` branch of the EE repository and commit and push to origin. ### **2. Update installation.md** Update [installation.md](/doc/install/installation.md) to the newest version in master. -### **3. Push latest changes from x-x-stable branch to dev.gitlab.org** -``` -git checkout -b x-x-stable -git push origin x-x-stable -``` - -### **4. Build the Omnibus packages** +### **3. Build the Omnibus packages** Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md). This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase. -### **5. Create annotated tag vx.x.x** -In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit, - -``` -git tag -a vx.x.0 -m 'Version x.x.0' xxxxx -``` - -where `xxxxx` is SHA-1. - -### **6. Push the tag and x-x-stable branch to the remotes** - -For GitLab CE, push to dev, GitLab.com and GitHub. - -For GitLab EE, push to the subscribers repo. - -Make sure the branch is marked 'protected' on each of the remotes you pushed to. - -``` -git push x-x-stable(-ee) -git push vx.x.0 -``` - -### **7. Publish packages for new release** +### **4. Publish packages for new release** Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository. -### **8. Publish blog for new release** +### **5. Publish blog for new release** Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository. -### **9. Tweet to blog** +### **6. Tweet to blog** Send out a tweet to share the good news with the world. List the most important features and link to the blog post. From 1d1b21164258c2e2a6e1d782e517871c33ea961d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 11 Nov 2014 12:01:47 +0200 Subject: [PATCH 247/408] Use release tools in patch release Signed-off-by: Dmitriy Zaporozhets --- doc/release/patch.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/doc/release/patch.md b/doc/release/patch.md index 5d2fa053cac..6ed56427e9a 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -10,6 +10,8 @@ Otherwise include it in the monthly release and note there was a regression fix ## Release Procedure +### Preparation + 1. Verify that the issue can be reproduced 1. Note in the 'GitLab X.X regressions' that you will create a patch 1. Create an issue on private GitLab development server @@ -17,12 +19,33 @@ Otherwise include it in the monthly release and note there was a regression fix 1. Fix the issue on a feature branch, do this on the private GitLab development server 1. Consider creating and testing workarounds 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch +1. Make sure that the build has passed and all tests are passing 1. In a separate commit in the stable branch update the CHANGELOG 1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" -1. In a separate commit in the stable branch update the VERSION -1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'` -1. Make sure that the build has passed and all tests are passing -1. Push the code and the tags to all the CE and EE repositories + +### Bump version + +Get release tools + +``` +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools +``` + +Bump version in stable branch, create release tag and push to remotes: + +``` +bundle exec rake release["x.x.x"] +``` + +Or if you need to release only EE: + +``` +CE=false be rake release['x.x.x'] +``` + +### Release + 1. Apply the patch to GitLab Cloud and the private GitLab development server 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) 1. Cherry-pick the changelog update back into master From a15dc7b8141e4aa981409bffd2bb6ad311902dfa Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 11 Nov 2014 10:22:05 +0000 Subject: [PATCH 248/408] Add tip about EE and CE master synced before release rc1 --- doc/release/monthly.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 7354efc3e2e..fa1a883f4b2 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -145,6 +145,8 @@ Make sure the code quality indicators are green / good. ### **4. Run release tool** +**Make sure EE `master` has latest changes from CE `master`** + Get release tools ``` From ccd842c3a7742d1d99a13a32ea725032810f2690 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 11 Nov 2014 14:52:30 +0200 Subject: [PATCH 249/408] Prevent post-receive error when push to project with dead forks If project has open merge request from fork and this fork was removed before merge request was closed it cause exception during push Signed-off-by: Dmitriy Zaporozhets --- app/models/project.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index c58c9b551c9..1383bf3c46e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -411,7 +411,7 @@ class Project < ActiveRecord::Base mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - mrs.uniq.each do |merge_request| + mrs.uniq.select(&:source_project).each do |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) end @@ -420,7 +420,7 @@ class Project < ActiveRecord::Base # Update code for merge requests between project and project fork mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a - mrs.uniq.each do |merge_request| + mrs.uniq.select(&:source_project).each do |merge_request| merge_request.reload_code merge_request.mark_as_unchecked end @@ -435,7 +435,7 @@ class Project < ActiveRecord::Base mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a - mrs.uniq.each do |merge_request| + mrs.uniq.select(&:source_project).each do |merge_request| Note.create_new_commits_note(merge_request, merge_request.project, user, commits) end From 2139e3519b1f1023478bec087cf94f2ec237c0c7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 11 Nov 2014 16:49:26 +0200 Subject: [PATCH 250/408] Refactor merge request refresh logic on push Signed-off-by: Dmitriy Zaporozhets --- app/models/project.rb | 39 +------- .../merge_requests/refresh_service.rb | 67 +++++++++++++ spec/models/project_spec.rb | 57 ----------- .../merge_requests/refresh_service_spec.rb | 98 +++++++++++++++++++ 4 files changed, 167 insertions(+), 94 deletions(-) create mode 100644 app/services/merge_requests/refresh_service.rb create mode 100644 spec/services/merge_requests/refresh_service_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index 1383bf3c46e..d2576bb85d0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -402,43 +402,8 @@ class Project < ActiveRecord::Base end def update_merge_requests(oldrev, newrev, ref, user) - return true unless ref =~ /heads/ - branch_name = ref.gsub("refs/heads/", "") - commits = self.repository.commits_between(oldrev, newrev) - c_ids = commits.map(&:id) - - # Close merge requests - mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a - mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - - mrs.uniq.select(&:source_project).each do |merge_request| - MergeRequests::MergeService.new.execute(merge_request, user, nil) - end - - # Update code for merge requests into project between project branches - mrs = self.merge_requests.opened.by_branch(branch_name).to_a - # Update code for merge requests between project and project fork - mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a - - mrs.uniq.select(&:source_project).each do |merge_request| - merge_request.reload_code - merge_request.mark_as_unchecked - end - - # Add comment about pushing new commits to merge requests - comment_mr_with_commits(branch_name, commits, user) - - true - end - - def comment_mr_with_commits(branch_name, commits, user) - mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a - mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a - - mrs.uniq.select(&:source_project).each do |merge_request| - Note.create_new_commits_note(merge_request, merge_request.project, - user, commits) - end + MergeRequests::RefreshService.new(self, user). + execute(oldrev, newrev, ref) end def valid_repo? diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb new file mode 100644 index 00000000000..74448998ddd --- /dev/null +++ b/app/services/merge_requests/refresh_service.rb @@ -0,0 +1,67 @@ +module MergeRequests + class RefreshService < MergeRequests::BaseService + def execute(oldrev, newrev, ref) + return true unless ref =~ /heads/ + + @branch_name = ref.gsub("refs/heads/", "") + @fork_merge_requests = @project.fork_merge_requests.opened + @commits = @project.repository.commits_between(oldrev, newrev) + + close_merge_requests + reload_merge_requests + comment_mr_with_commits + + true + end + + private + + # Collect open merge requests that target same branch we push into + # and close if push to master include last commit from merge request + # We need this to close(as merged) merge requests that were merged into + # target branch manually + def close_merge_requests + commit_ids = @commits.map(&:id) + merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a + merge_requests = merge_requests.select(&:last_commit) + + merge_requests = merge_requests.select do |merge_request| + commit_ids.include?(merge_request.last_commit.id) + end + + + merge_requests.uniq.select(&:source_project).each do |merge_request| + MergeRequests::MergeService.new.execute(merge_request, @current_user, nil) + end + end + + # Refresh merge request diff if we push to source or target branch of merge request + # Note: we should update merge requests from forks too + def reload_merge_requests + merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a + merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| + merge_request.reload_code + merge_request.mark_as_unchecked + end + end + + # Add comment about pushing new commits to merge requests + def comment_mr_with_commits + merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a + merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| + Note.create_new_commits_note(merge_request, merge_request.project, + @current_user, @commits) + end + end + + def filter_merge_requests(merge_requests) + merge_requests.uniq.select(&:source_project) + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 48b58400a1e..70a15cac1a8 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -145,63 +145,6 @@ describe Project do end end - describe 'comment merge requests with commits' do - before do - @user = create(:user) - group = create(:group) - group.add_owner(@user) - - @project = create(:project, namespace: group) - @fork_project = Projects::ForkService.new(@project, @user).execute - @merge_request = create(:merge_request, source_project: @project, - source_branch: 'master', - target_branch: 'feature', - target_project: @project) - @fork_merge_request = create(:merge_request, source_project: @fork_project, - source_branch: 'master', - target_branch: 'feature', - target_project: @project) - - @commits = @merge_request.commits - end - - context 'push to origin repo source branch' do - before do - @project.comment_mr_with_commits('master', @commits, @user) - end - - it { @merge_request.notes.should_not be_empty } - it { @fork_merge_request.notes.should be_empty } - end - - context 'push to origin repo target branch' do - before do - @project.comment_mr_with_commits('feature', @commits, @user) - end - - it { @merge_request.notes.should be_empty } - it { @fork_merge_request.notes.should be_empty } - end - - context 'push to fork repo source branch' do - before do - @fork_project.comment_mr_with_commits('master', @commits, @user) - end - - it { @merge_request.notes.should be_empty } - it { @fork_merge_request.notes.should_not be_empty } - end - - context 'push to fork repo target branch' do - before do - @fork_project.comment_mr_with_commits('feature', @commits, @user) - end - - it { @merge_request.notes.should be_empty } - it { @fork_merge_request.notes.should be_empty } - end - end - describe :find_with_namespace do context 'with namespace' do before do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb new file mode 100644 index 00000000000..9f294152053 --- /dev/null +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe MergeRequests::RefreshService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { MergeRequests::RefreshService } + + describe :execute do + before do + @user = create(:user) + group = create(:group) + group.add_owner(@user) + + @project = create(:project, namespace: group) + @fork_project = Projects::ForkService.new(@project, @user).execute + @merge_request = create(:merge_request, source_project: @project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project) + + @fork_merge_request = create(:merge_request, source_project: @fork_project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project) + + @commits = @merge_request.commits + + @oldrev = @commits.last.id + @newrev = @commits.first.id + end + + context 'push to origin repo source branch' do + before do + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it { @merge_request.notes.should_not be_empty } + it { @merge_request.should be_open } + it { @fork_merge_request.should be_open } + it { @fork_merge_request.notes.should be_empty } + end + + context 'push to origin repo target branch' do + before do + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_merged } + it { @fork_merge_request.should be_merged } + it { @fork_merge_request.notes.should be_empty } + end + + context 'push to fork repo source branch' do + before do + service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_open } + it { @fork_merge_request.notes.should_not be_empty } + it { @fork_merge_request.should be_open } + end + + context 'push to fork repo target branch' do + before do + service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_open } + it { @fork_merge_request.notes.should be_empty } + it { @fork_merge_request.should be_open } + end + + context 'push to origin repo target branch after fork project was removed' do + before do + @fork_project.destroy + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { @merge_request.notes.should be_empty } + it { @merge_request.should be_merged } + it { @fork_merge_request.should be_open } + it { @fork_merge_request.notes.should be_empty } + end + + def reload_mrs + @merge_request.reload + @fork_merge_request.reload + end + end +end From af154478675f9e3d970dbd9339e0ed23c23a7eec Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 11 Nov 2014 16:09:58 +0100 Subject: [PATCH 251/408] Create emails helper for actions links. --- app/helpers/emails_helper.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/helpers/emails_helper.rb diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb new file mode 100644 index 00000000000..2ef28922ec1 --- /dev/null +++ b/app/helpers/emails_helper.rb @@ -0,0 +1,20 @@ +module EmailsHelper + + # Google Actions + # https://developers.google.com/gmail/markup/reference/go-to-action + def email_action(options) + data = { + "@context" => "http://schema.org", + "@type" => "EmailMessage", + "action" => { + "@type" => "ViewAction", + "name" => options[:name], + "url" => options[:url], + } + } + + content_tag :script, type: 'application/ld+json' do + data.to_json.html_safe + end + end +end From 3dcb5f8501e6eedb41c5d8a83eff1e3b80822f1d Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 11 Nov 2014 16:10:24 +0100 Subject: [PATCH 252/408] Include the helper for mailer, add links to emails. --- app/mailers/notify.rb | 1 + app/views/layouts/notify.html.haml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index bd438bab89a..0ee19836627 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -11,6 +11,7 @@ class Notify < ActionMailer::Base add_template_helper ApplicationHelper add_template_helper GitlabMarkdownHelper add_template_helper MergeRequestsHelper + add_template_helper EmailsHelper default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:protocol] = Gitlab.config.gitlab.protocol diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index ab421d63f1a..1236cf00f01 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -28,3 +28,4 @@ You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team. - if @target_url #{link_to "View it on GitLab", @target_url} + = email_action name: "View #{@note.noteable_type.underscore.humanize}", url: @target_url From d65f2bad6c7f834b708886f1ee8160a67281638d Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 11 Nov 2014 16:30:14 +0100 Subject: [PATCH 253/408] Add documentation about buttons in gmail. --- doc/integration/gitlab_actions.png | Bin 0 -> 17321 bytes doc/integration/gitlab_buttons_in_gmail.md | 11 +++++++++++ 2 files changed, 11 insertions(+) create mode 100644 doc/integration/gitlab_actions.png create mode 100644 doc/integration/gitlab_buttons_in_gmail.md diff --git a/doc/integration/gitlab_actions.png b/doc/integration/gitlab_actions.png new file mode 100644 index 0000000000000000000000000000000000000000..b08f54d137bd9ab1bde8471211f95016a9ee6aa0 GIT binary patch literal 17321 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 Date: Tue, 11 Nov 2014 18:48:39 +0200 Subject: [PATCH 254/408] Sync master branch before releasing rc Signed-off-by: Dmitriy Zaporozhets --- doc/release/monthly.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index fa1a883f4b2..affe634587d 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -154,6 +154,13 @@ git clone git@dev.gitlab.org:gitlab/release-tools.git cd release-tools ``` +Release candidate creates stable branch from master. +So we need to sync master branch between all CE remotes. Also do same for EE. + +``` +bundle exec rake sync +``` + Create release candidate and stable branch: ``` From 9d457fa4771456290881630f726054d0c47ee842 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 11 Nov 2014 19:08:39 +0200 Subject: [PATCH 255/408] Bump gitlab-shell version Signed-off-by: Dmitriy Zaporozhets --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 7ec1d6db408..ccbccc3dc62 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.1.0 +2.2.0 From 857852ce048607a0898a9b40e04fdb0658b6a83d Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 12 Nov 2014 11:59:57 +0100 Subject: [PATCH 256/408] Set action on issue/mr creation mail. --- app/helpers/emails_helper.rb | 34 ++++++++++++++++++++---------- app/views/layouts/notify.html.haml | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 2ef28922ec1..24d67c21d6b 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -2,19 +2,31 @@ module EmailsHelper # Google Actions # https://developers.google.com/gmail/markup/reference/go-to-action - def email_action(options) - data = { - "@context" => "http://schema.org", - "@type" => "EmailMessage", - "action" => { - "@type" => "ViewAction", - "name" => options[:name], - "url" => options[:url], + def email_action(url) + name = action_title(url) + if name + data = { + "@context" => "http://schema.org", + "@type" => "EmailMessage", + "action" => { + "@type" => "ViewAction", + "name" => name, + "url" => url, + } } - } - content_tag :script, type: 'application/ld+json' do - data.to_json.html_safe + content_tag :script, type: 'application/ld+json' do + data.to_json.html_safe + end + end + end + + def action_title(url) + return unless url + ["merge_requests", "issues", "commit"].each do |action| + if url.split("/").include?(action) + return "View #{action.humanize.singularize}" + end end end end diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 1236cf00f01..da451961327 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -28,4 +28,4 @@ You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team. - if @target_url #{link_to "View it on GitLab", @target_url} - = email_action name: "View #{@note.noteable_type.underscore.humanize}", url: @target_url + = email_action @target_url From 1f902c2464a4f5c68f1b42be597d4e3e25a32130 Mon Sep 17 00:00:00 2001 From: Marvin Frick Date: Wed, 12 Nov 2014 12:06:24 +0100 Subject: [PATCH 257/408] fixes the `block_removed_ldap_users` rake task In e23a26a (and later 1bc9936) the API for Gitlab::LDAP::Adapter was changed. I assume this rake task was an oversight in the refactoring of the changed class. While being on it, I noticed that already blocked users cannot be blocked again. --- lib/tasks/gitlab/cleanup.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index 63dcdc52370..189ad6090a4 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -92,11 +92,11 @@ namespace :gitlab do User.ldap.each do |ldap_user| print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..." - if Gitlab::LDAP::Access.open { |access| access.allowed?(ldap_user) } + if Gitlab::LDAP::Access.allowed?(ldap_user) puts " [OK]".green else if block_flag - ldap_user.block! + ldap_user.block! unless ldap_user.blocked? puts " [BLOCKED]".red else puts " [NOT IN LDAP]".yellow From 5cf6d5949d6c776e24d3bd5c0b417000f1efc57a Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 12 Nov 2014 12:54:43 +0100 Subject: [PATCH 258/408] Remove the lowest memory requirement of 512MB. --- doc/install/requirements.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index ed194253148..fd59ac8a073 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -50,11 +50,6 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ### Memory -- 512MB is the absolute minimum but we do not recommend this amount of memory. -You will either need to configure 512MB or 1.5GB of swap space. -With 512MB of swap space you must configure only one unicorn worker. -With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users @@ -90,7 +85,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o ## Supported web browsers - Chrome (Latest stable version) -- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) +- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Safari 7+ (known problem: required fields in html5 do not work) - Opera (Latest released version) - IE 10+ From 722d80739b6b4eb6e4803fc55f750300d66b94be Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Nov 2014 13:59:25 +0200 Subject: [PATCH 259/408] Prevent big amount of sql queries for push service Signed-off-by: Dmitriy Zaporozhets --- app/services/git_push_service.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 3f5222c93f1..529af1970f6 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -83,9 +83,14 @@ class GitPushService # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to # a different branch. issues_to_close = commit.closes_issues(project) - author = commit_user(commit) - if !issues_to_close.empty? && is_default_branch + # Load commit author only if needed. + # For push with 1k commits it prevents 900+ requests in database + author = nil + + if issues_to_close.present? && is_default_branch + author ||= commit_user(commit) + issues_to_close.each do |issue| Issues::CloseService.new(project, author, {}).execute(issue, commit) end @@ -96,8 +101,13 @@ class GitPushService # being pushed to a different branch). refs = commit.references(project) - issues_to_close refs.reject! { |r| commit.has_mentioned?(r) } - refs.each do |r| - Note.create_cross_reference_note(r, commit, author, project) + + if refs.present? + author ||= commit_user(commit) + + refs.each do |r| + Note.create_cross_reference_note(r, commit, author, project) + end end end end From 4a5044e30269f8b3c6c075093cd4646a478231c7 Mon Sep 17 00:00:00 2001 From: Dimitry Andric Date: Thu, 13 Nov 2014 13:09:47 +0100 Subject: [PATCH 260/408] Correctly restore empty repositories. If a project is being restored, but there is no bundle file, the project was empty when it was backed up. In this case, just use git init --base to create a new bare repository. --- lib/backup/repository.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 0bb02f1a357..faa1b3b4099 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -59,7 +59,13 @@ module Backup project.namespace.ensure_dir_exist if project.namespace - if system(*%W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}), silent) + if File.exists?(path_to_bundle(project)) + cmd = %W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}) + else + cmd = %W(git init --bare #{path_to_repo(project)}) + end + + if system(*cmd, silent) puts "[DONE]".green else puts "[FAILED]".red From 9eb571f0ea49d182353d576739d412b914a46b62 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Thu, 13 Nov 2014 16:19:07 +0100 Subject: [PATCH 261/408] Add branch controller test. --- spec/controllers/branches_controller_spec.rb | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 spec/controllers/branches_controller_spec.rb diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb new file mode 100644 index 00000000000..610d7a84e31 --- /dev/null +++ b/spec/controllers/branches_controller_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Projects::BranchesController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + + project.team << [user, :master] + + project.stub(:branches).and_return(['master', 'foo/bar/baz']) + project.stub(:tags).and_return(['v1.0.0', 'v2.0.0']) + controller.instance_variable_set(:@project, project) + end + + describe "POST create" do + render_views + + before { + post :create, + project_id: project.to_param, + branch_name: branch, + ref: ref + } + + context "valid branch name, valid source" do + let(:branch) { "merge_branch" } + let(:ref) { "master" } + it { should redirect_to("/#{project.path_with_namespace}/tree/merge_branch") } + end + + context "invalid branch name, valid ref" do + let(:branch) { "" } + let(:ref) { "master" } + it { should redirect_to("/#{project.path_with_namespace}/tree/alert('merge');") } + end + + context "valid branch name, invalid ref" do + let(:branch) { "merge_branch" } + let(:ref) { "" } + it { should render_template("new") } + end + + context "invalid branch name, invalid ref" do + let(:branch) { "" } + let(:ref) { "" } + it { should render_template("new") } + end + end +end From 334fe86574227433bd2909577c5955c40721d509 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Thu, 13 Nov 2014 16:20:43 +0100 Subject: [PATCH 262/408] Sanitize branch name and ref name --- app/controllers/projects/branches_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 9ebd498e7fa..cff1a907dc2 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -1,4 +1,5 @@ class Projects::BranchesController < Projects::ApplicationController + include ActionView::Helpers::SanitizeHelper # Authorize before_filter :require_non_empty_project @@ -16,8 +17,10 @@ class Projects::BranchesController < Projects::ApplicationController end def create + branch_name = sanitize(strip_tags(params[:branch_name])) + ref = sanitize(strip_tags(params[:ref])) result = CreateBranchService.new(project, current_user). - execute(params[:branch_name], params[:ref]) + execute(branch_name, ref) if result[:status] == :success @branch = result[:branch] From 5e9f6562bb861a4e7e062760cf094a5dba283d80 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Nov 2014 19:20:35 +0200 Subject: [PATCH 263/408] Update CHANGELOG for 7.5 Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 80ead77783f..d27ec2bbe29 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,17 @@ v 7.5.0 - Return valid json for deleting branch via API (sponsored by O'Reilly Media) - Expose username in project events API (sponsored by O'Reilly Media) - Adds comments to commits in the API + - Performance improvements + - Fix post-receive issue for projects with deleted forks + - New gitlab-shell version with custom hooks support + - Improve code + - GitLab CI 5.2+ support (does not support older versions) + - Fixed bug when you can not push commits starting with 000000 to protected branches + - Added a password strength indicator + - Change project name and path in one form + - Display renamed files in diff views (Vinnie Okada) + - Add timezone configuration to gitlab.yml + - Fix raw view for public snippets v 7.4.3 - Fix raw snippets view From 18c8226566edb1c7fa43ccc1bf7a1db33f91489f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Nov 2014 19:40:47 +0200 Subject: [PATCH 264/408] Refactor project fork service Signed-off-by: Dmitriy Zaporozhets --- app/services/projects/fork_service.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index c4f2d08efe9..4930660055a 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -13,11 +13,14 @@ module Projects project = Project.new(project_params) project.name = @from_project.name project.path = @from_project.path - project.namespace = @current_user.namespace + project.creator = @current_user + if namespace = @params[:namespace] project.namespace = namespace + else + project.namespace = @current_user.namespace end - project.creator = @current_user + unless @current_user.can?(:create_projects, project.namespace) project.errors.add(:namespace, 'insufficient access rights') return project @@ -47,8 +50,8 @@ module Projects else project.errors.add(:base, "Invalid fork destination") end - project + project end end end From e08e405ac4c448d8b720ed2ef6181c15e3f3dfc1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Nov 2014 22:06:19 +0200 Subject: [PATCH 265/408] Select namespace where to fork project Now you can fork project into group or personal namespace. Also I moved fork logic from ProjectsController to own fork resource Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/forks_controller.rb | 22 ++++++++++++++++++++ app/controllers/projects_controller.rb | 16 -------------- app/models/user.rb | 10 +++++++++ app/views/projects/_home_panel.html.haml | 2 +- app/views/projects/fork.html.haml | 19 ----------------- app/views/projects/forks/error.html.haml | 20 ++++++++++++++++++ app/views/projects/forks/new.html.haml | 19 +++++++++++++++++ config/routes.rb | 11 +++++----- 8 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 app/controllers/projects/forks_controller.rb delete mode 100644 app/views/projects/fork.html.haml create mode 100644 app/views/projects/forks/error.html.haml create mode 100644 app/views/projects/forks/new.html.haml diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb new file mode 100644 index 00000000000..a0481d11582 --- /dev/null +++ b/app/controllers/projects/forks_controller.rb @@ -0,0 +1,22 @@ +class Projects::ForksController < Projects::ApplicationController + # Authorize + before_filter :authorize_download_code! + before_filter :require_non_empty_project + + def new + @namespaces = current_user.manageable_namespaces + @namespaces.delete(@project.namespace) + end + + def create + namespace = Namespace.find(params[:namespace_id]) + @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute + + if @forked_project.saved? && @forked_project.forked? + redirect_to(@forked_project, notice: 'Project was successfully forked.') + else + @title = 'Fork project' + render :error + end + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b5910c902e4..b3181fa310e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -111,22 +111,6 @@ class ProjectsController < ApplicationController end end - def fork - @forked_project = ::Projects::ForkService.new(project, current_user).execute - - respond_to do |format| - format.html do - if @forked_project.saved? && @forked_project.forked? - redirect_to(@forked_project, notice: 'Project was successfully forked.') - else - @title = 'Fork project' - render "fork" - end - end - format.js - end - end - def autocomplete_sources note_type = params['type'] note_id = params['type_id'] diff --git a/app/models/user.rb b/app/models/user.rb index d400edc0df5..fc191a78f53 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -551,4 +551,14 @@ class User < ActiveRecord::Base UsersStarProject.create!(project: project, user: self) end end + + def manageable_namespaces + @manageable_namespaces ||= + begin + namespaces = [] + namespaces << namespace + namespaces += owned_groups + namespaces += masters_groups + end + end end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 672a91e0eef..c2fa1c4bccc 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -20,7 +20,7 @@ = link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do = link_to_toggle_fork - else - = link_to fork_project_path(@project), title: "Fork project", method: "POST" do + = link_to new_project_fork_path(@project), title: "Fork project" do = link_to_toggle_fork .star-buttons diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml deleted file mode 100644 index d8f5c7b98d6..00000000000 --- a/app/views/projects/fork.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -.alert.alert-danger.alert-block - %h4 - %i.fa.fa-code-fork - Fork Error! - %p - You tried to fork - = link_to_project @project - but it failed for the following reason: - - - - if @forked_project && @forked_project.errors.any? - %p - – - = @forked_project.errors.full_messages.first - - %p - = link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do - %i.fa.fa-code-fork - Try to Fork again diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml new file mode 100644 index 00000000000..76d3aa5bf00 --- /dev/null +++ b/app/views/projects/forks/error.html.haml @@ -0,0 +1,20 @@ +- if @forked_project && !@forked_project.saved? + .alert.alert-danger.alert-block + %h4 + %i.fa.fa-code-fork + Fork Error! + %p + You tried to fork + = link_to_project @project + but it failed for the following reason: + + + - if @forked_project && @forked_project.errors.any? + %p + – + = @forked_project.errors.full_messages.first + + %p + = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do + %i.fa.fa-code-fork + Try to Fork again diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml new file mode 100644 index 00000000000..db7486b00e8 --- /dev/null +++ b/app/views/projects/forks/new.html.haml @@ -0,0 +1,19 @@ +%h3.page-title Fork project +%p.lead Select namespace where to fork this project +%hr + +- @namespaces.in_groups_of(6, false) do |group| + .row + - group.each do |namespace| + .col-md-2.col-sm-3 + .thumbnail + = link_to project_fork_path(@project, namespace_id: namespace.id), title: "Fork here", method: "POST" do + - if namespace.kind_of?(Group) + = image_tag group_icon(namespace.path) + - else + = image_tag avatar_icon(namespace.owner.email, 200) + .caption + %h4=namespace.human_name + %p + = namespace.path + diff --git a/config/routes.rb b/config/routes.rb index 2534153758b..470fe7f4dc1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -181,7 +181,6 @@ Gitlab::Application.routes.draw do resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do member do put :transfer - post :fork post :archive post :unarchive post :upload_image @@ -214,11 +213,11 @@ Gitlab::Application.routes.draw do match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/} - resources :snippets, constraints: {id: /\d+/} do - member do - get "raw" - end + resources :snippets, constraints: {id: /\d+/} do + member do + get "raw" end + end resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do collection do @@ -232,6 +231,8 @@ Gitlab::Application.routes.draw do end end + resource :fork, only: [:new, :create] + resource :repository, only: [:show] do member do get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex } From ced2438312c3fd48f9487465533bb493d83ee998 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 14 Nov 2014 11:08:58 +0100 Subject: [PATCH 266/408] Clean the string with commit author and email. --- app/helpers/commits_helper.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 0e0532b65b2..36adeadd8a5 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -87,8 +87,8 @@ module CommitsHelper # avatar: true will prepend the avatar image # size: size of the avatar image in px def commit_person_link(commit, options = {}) - source_name = commit.send "#{options[:source]}_name".to_sym - source_email = commit.send "#{options[:source]}_email".to_sym + source_name = clean(commit.send "#{options[:source]}_name".to_sym) + source_email = clean(commit.send "#{options[:source]}_email".to_sym) user = User.find_for_commit(source_email, source_name) person_name = user.nil? ? source_name : user.name @@ -124,4 +124,8 @@ module CommitsHelper def truncate_sha(sha) Commit.truncate_sha(sha) end + + def clean(string) + Sanitize.clean(string, remove_contents: true) + end end From e0467e8f58d168900e7282160e1674a2125265cc Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Fri, 14 Nov 2014 02:41:58 -0800 Subject: [PATCH 267/408] fix backup rake task --- doc/raketasks/backup_restore.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index b4581e2a07a..d2f0d6e7bc1 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -14,7 +14,7 @@ You can only restore a backup to exactly the same version of GitLab that you cre sudo gitlab-rake gitlab:backup:create # if you've installed GitLab from source or using the cookbook -bundle exec rake gitlab:backup:create RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` Example output: From de3bef058ee16aae0f29802e39c3af24c5b79790 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 14 Nov 2014 11:47:08 +0100 Subject: [PATCH 268/408] Gitlab.com uses special packages, build them first --- doc/release/monthly.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 386c19c0fe0..e0c98fbce76 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -316,7 +316,8 @@ Proposed tweet for CE "GitLab X.X is released! It brings *** " # **1 workday after release - Update GitLab.com** -Update GitLab.com from RC1 to the released package. +- Build a package for gitlab.com based on the official release instead of RC1 +- Deploy the package # **25th - Release GitLab CI** From d2c3c98e3cf88dd59a2a1a0d94e711e31c11b2cd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 14 Nov 2014 12:55:13 +0200 Subject: [PATCH 269/408] Routing specs for fork projects Signed-off-by: Dmitriy Zaporozhets --- spec/routing/project_routing_spec.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 4b2eb42c709..ea584c9802d 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -55,7 +55,6 @@ end # projects POST /projects(.:format) projects#create # new_project GET /projects/new(.:format) projects#new -# fork_project POST /:id/fork(.:format) projects#fork # files_project GET /:id/files(.:format) projects#files # edit_project GET /:id/edit(.:format) projects#edit # project GET /:id(.:format) projects#show @@ -70,10 +69,6 @@ describe ProjectsController, "routing" do get("/projects/new").should route_to('projects#new') end - it "to #fork" do - post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq') - end - it "to #edit" do get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq') end @@ -462,3 +457,13 @@ describe Projects::GraphsController, "routing" do get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master') end end + +describe Projects::ForksController, "routing" do + it "to #new" do + get("/gitlab/gitlabhq/fork/new").should route_to("projects/forks#new", project_id: 'gitlab/gitlabhq') + end + + it "to #create" do + post("/gitlab/gitlabhq/fork").should route_to("projects/forks#create", project_id: 'gitlab/gitlabhq') + end +end From a9dc2c202938863896ca2de55dc301c655285b93 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 14 Nov 2014 12:14:17 +0100 Subject: [PATCH 270/408] Update gitlab_git to 7.0.0.rc11 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index dc84b26bc4c..bb8aef65d2f 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,7 @@ gem 'omniauth-shibboleth' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '7.0.0.rc10' +gem "gitlab_git", '7.0.0.rc11' # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' diff --git a/Gemfile.lock b/Gemfile.lock index c283d4384fc..a3645f7bbec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -179,7 +179,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.0.1.1) emoji (~> 1.0.1) - gitlab_git (7.0.0.rc10) + gitlab_git (7.0.0.rc11) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -625,7 +625,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.0.pre) gitlab-linguist (~> 3.0.0) gitlab_emoji (~> 0.0.1.1) - gitlab_git (= 7.0.0.rc10) + gitlab_git (= 7.0.0.rc11) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.0) gollum-lib (~> 3.0.0) From c10f61802be9d9059b64386cca6bfc3b07beb0b1 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 11 Nov 2014 16:59:50 +0100 Subject: [PATCH 271/408] Run 'GC.start' after every EmailsOnPushWorker job --- CHANGELOG | 1 + app/workers/emails_on_push_worker.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ff41575bcc6..32703569a69 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ v 7.5.0 - API: Add support for Hipchat (Kevin Houdebert) - Add time zone configuration on gitlab.yml (Sullivan Senechal) - Fix LDAP authentication for Git HTTP access + - Run 'GC.start' after every EmailsOnPushWorker job - Fix LDAP config lookup for provider 'ldap' - Add Atlassian Bamboo CI service (Drew Blessing) - Mentioned @user will receive email even if he is not participating in issue or commit diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 2947c8e3ecd..e3f6f3a6aef 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -21,5 +21,8 @@ class EmailsOnPushWorker recipients.split(" ").each do |recipient| Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver end + ensure + compare = nil + GC.start end end From 2388fdd7c6274dad8c10f5bc517f0a8b1aa28aa3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 14 Nov 2014 16:06:39 +0200 Subject: [PATCH 272/408] Improve fork to namespaces feature * Show namespace thumbnail differently if project was already forked * Show loading spinner when click on fork * Fork link navigates to personal namespace only if no manageable groups exists Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/dispatcher.js.coffee | 2 + app/assets/javascripts/project_fork.js.coffee | 5 ++ app/assets/stylesheets/sections/projects.scss | 25 ++++++++++ app/helpers/namespaces_helper.rb | 8 ++++ app/models/namespace.rb | 4 ++ app/views/projects/_home_panel.html.haml | 2 +- app/views/projects/forks/new.html.haml | 47 +++++++++++++------ features/project/fork.feature | 2 + features/steps/project/fork.rb | 6 +++ 9 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 app/assets/javascripts/project_fork.js.coffee diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index fb1adbc4b3d..e8b71a71945 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -75,6 +75,8 @@ class Dispatcher # Ensure we don't create a particular shortcut handler here. This is # already created, where the network graph is created. shortcut_handler = true + when 'projects:forks:new' + new ProjectFork() when 'users:show' new User() diff --git a/app/assets/javascripts/project_fork.js.coffee b/app/assets/javascripts/project_fork.js.coffee new file mode 100644 index 00000000000..e15a1c4ef76 --- /dev/null +++ b/app/assets/javascripts/project_fork.js.coffee @@ -0,0 +1,5 @@ +class @ProjectFork + constructor: -> + $('.fork-thumbnail a').on 'click', -> + $('.fork-namespaces').hide() + $('.save-project-loader').show() diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index b4ee5ccc8d7..76a7507d699 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -270,3 +270,28 @@ ul.nav.nav-projects-tabs { color: #999; } } + +.fork-namespaces { + .thumbnail { + + &.fork-exists-thumbnail { + border-color: #EEE; + + .caption { + color: #999; + } + } + + &.fork-thumbnail { + border-color: #AAA; + + &:hover { + background-color: $hover; + } + } + + a { + text-decoration: none; + } + } +} diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index bf25dce2301..2bcfde62830 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -25,4 +25,12 @@ module NamespacesHelper hidden_field_tag(id, value, class: css_class) end + + def namespace_icon(namespace, size = 40) + if namespace.kind_of?(Group) + group_icon(namespace.path) + else + avatar_icon(namespace.owner.email, size) + end + end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index c0c6de0ee7d..ea4b48fdd7f 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -90,4 +90,8 @@ class Namespace < ActiveRecord::Base def kind type == 'Group' ? 'group' : 'user' end + + def find_fork_of(project) + projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first + end end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index c2fa1c4bccc..8b9260d661c 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -16,7 +16,7 @@ - unless @project.empty_repo? .fork-buttons - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - - if current_user.already_forked?(@project) + - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 = link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do = link_to_toggle_fork - else diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index db7486b00e8..54f2cef023b 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -2,18 +2,37 @@ %p.lead Select namespace where to fork this project %hr -- @namespaces.in_groups_of(6, false) do |group| - .row - - group.each do |namespace| - .col-md-2.col-sm-3 - .thumbnail - = link_to project_fork_path(@project, namespace_id: namespace.id), title: "Fork here", method: "POST" do - - if namespace.kind_of?(Group) - = image_tag group_icon(namespace.path) - - else - = image_tag avatar_icon(namespace.owner.email, 200) - .caption - %h4=namespace.human_name - %p - = namespace.path +.fork-namespaces + - @namespaces.in_groups_of(6, false) do |group| + .row + - group.each do |namespace| + .col-md-2.col-sm-3 + - if fork = namespace.find_fork_of(@project) + .thumbnail.fork-exists-thumbnail + = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do + = image_tag namespace_icon(namespace, 200) + .caption + %h4=namespace.human_name + %p + = namespace.path + - else + .thumbnail.fork-thumbnail + = link_to project_fork_path(@project, namespace_id: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do + = image_tag namespace_icon(namespace, 200) + .caption + %h4=namespace.human_name + %p + = namespace.path + + %p.light + Fork is a copy of a project repository. + %br + Forking a repository allows you to do changes without affecting the original project. + +.save-project-loader.hide + .center + %h2 + %i.fa.fa-spinner.fa-spin + Forking repository + %p Please wait a moment, this page will automatically refresh when ready. diff --git a/features/project/fork.feature b/features/project/fork.feature index d3d1180db04..22f68e5b340 100644 --- a/features/project/fork.feature +++ b/features/project/fork.feature @@ -6,9 +6,11 @@ Feature: Project Fork Scenario: User fork a project Given I click link "Fork" + When I fork to my namespace Then I should see the forked project page Scenario: User already has forked the project Given I already have a project named "Shop" in my namespace And I click link "Fork" + When I fork to my namespace Then I should see a "Name has already been taken" warning diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index da50ba9ced0..8e58597db20 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -25,4 +25,10 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps step 'I should see a "Name has already been taken" warning' do page.should have_content "Name has already been taken" end + + step 'I fork to my namespace' do + within '.fork-namespaces' do + click_link current_user.name + end + end end From 987007b8dbfdfb6ebd2831f135f3b4b6641f63d0 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 15 Nov 2014 02:17:01 -0800 Subject: [PATCH 273/408] remove duplicate time zone entry in CHANGELOG --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dca9fd74728..d47cbb2c236 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ v 7.5.0 - API: Add support for Hipchat (Kevin Houdebert) - - Add time zone configuration on gitlab.yml (Sullivan Senechal) + - Add time zone configuration in gitlab.yml (Sullivan Senechal) - Fix LDAP authentication for Git HTTP access - Run 'GC.start' after every EmailsOnPushWorker job - Fix LDAP config lookup for provider 'ldap' @@ -22,7 +22,6 @@ v 7.5.0 - Added a password strength indicator - Change project name and path in one form - Display renamed files in diff views (Vinnie Okada) - - Add timezone configuration to gitlab.yml - Fix raw view for public snippets v 7.4.3 From 5b5446bd761e1d6b07171ba5c6c9b994f797b6a8 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sat, 15 Nov 2014 06:25:08 -0800 Subject: [PATCH 274/408] remove extra cd command --- doc/update/7.3-to-7.4.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 3f471500c82..f8a405c195b 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -35,8 +35,6 @@ sudo -u git -H git checkout 7-4-stable-ee ### 3. Install libs, migrations, etc. ```bash -cd /home/git/gitlab - # MySQL installations (note: the line below states '--without ... postgres') sudo -u git -H bundle install --without development test postgres --deployment From 214f6985a825b6b6f4119abc7b2b7147e7f97d20 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 17 Nov 2014 11:03:03 +0000 Subject: [PATCH 275/408] Update changelog --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index dca9fd74728..3cd32a75dde 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,7 +23,8 @@ v 7.5.0 - Change project name and path in one form - Display renamed files in diff views (Vinnie Okada) - Add timezone configuration to gitlab.yml - - Fix raw view for public snippets + - Fix raw view for public snippets + - Use secret token with GitLab internal API. v 7.4.3 - Fix raw snippets view From da35baed656a90eb5531955d591152c7142cd0cb Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 17 Nov 2014 16:56:51 -0500 Subject: [PATCH 276/408] Added update guide for updating to 7.5, and pointed installation and updates guides to new version. --- doc/install/installation.md | 10 +- ...-or-7.x-to-7.4.md => 6.x-or-7.x-to-7.5.md} | 28 +-- doc/update/7.4-to-7.5.md | 187 ++++++++++++++++++ 3 files changed, 206 insertions(+), 19 deletions(-) rename doc/update/{6.x-or-7.x-to-7.4.md => 6.x-or-7.x-to-7.5.md} (93%) create mode 100644 doc/update/7.4-to-7.5.md diff --git a/doc/install/installation.md b/doc/install/installation.md index 459a21ae821..5dd9388eece 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -101,8 +101,8 @@ Remove the old Ruby 1.8 if present Download Ruby and compile it: mkdir /tmp/ruby && cd /tmp/ruby - curl -L --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz - cd ruby-2.1.2 + curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz | tar xz + cd ruby-2.1.5 ./configure --disable-install-rdoc make sudo make install @@ -181,9 +181,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-4-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-5-stable gitlab -**Note:** You can change `7-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `7-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It @@ -278,7 +278,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da GitLab Shell is an SSH access and repository management software developed specially for GitLab. # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): - sudo -u git -H bundle exec rake gitlab:shell:install[v2.0.1] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:shell:install[v2.2.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production # By default, the gitlab-shell config is generated from your main GitLab config. # You can review (and modify) the gitlab-shell config as follows: diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.5.md similarity index 93% rename from doc/update/6.x-or-7.x-to-7.4.md rename to doc/update/6.x-or-7.x-to-7.5.md index dd90ae3bf3d..c9b95c62611 100644 --- a/doc/update/6.x-or-7.x-to-7.4.md +++ b/doc/update/6.x-or-7.x-to-7.5.md @@ -1,6 +1,6 @@ -# From 6.x or 7.x to 7.4 +# From 6.x or 7.x to 7.5 -This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4. +This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.5. ## Global issue numbers @@ -34,7 +34,7 @@ You can check which version you are running with `ruby -v`. If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons. -If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1. +If you are running Ruby 2.1.1 consider upgrading to 2.1.5, because of the high memory usage of Ruby 2.1.1. Install, update dependencies: @@ -46,8 +46,8 @@ Download and compile Ruby: ```bash mkdir /tmp/ruby && cd /tmp/ruby -curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz -cd ruby-2.1.2 +curl --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz | tar xz +cd ruby-2.1.5 ./configure --disable-install-rdoc make sudo make install @@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut For GitLab Community Edition: ```bash -sudo -u git -H git checkout 7-4-stable +sudo -u git -H git checkout 7-5-stable ``` OR @@ -78,7 +78,7 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout 7-4-stable-ee +sudo -u git -H git checkout 7-5-stable-ee ``` ## 4. Install additional packages @@ -119,7 +119,7 @@ sudo apt-get install pkg-config cmake ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.0.1 +sudo -u git -H git checkout v2.2.0 ``` ## 7. Install libs, migrations, etc. @@ -154,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab TIP: to see what changed in `gitlab.yml.example` in this release use next command: ``` -git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 7-5-stable:config/gitlab.yml.example ``` -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings. -* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings. -* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.2.0/config.yml.example but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl but with your settings. * Copy rack attack middleware config ```bash diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md new file mode 100644 index 00000000000..737aeb9c1ab --- /dev/null +++ b/doc/update/7.4-to-7.5.md @@ -0,0 +1,187 @@ +# From 7.4 to 7.5 + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 7-5-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-5-stable-ee +``` + +### 3. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 4. Update config files + +#### New configuration options for gitlab.yml + +There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml. + +``` +git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gitlab.yml.example +``` + +#### Change timeout for unicorn + +``` +# set timeout to 60 +sudo -u git -H editor config/unicorn.rb +``` + +#### Change nginx https settings + +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl but with your setting + +#### MySQL Databases: Update database.yml config file + +* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) + + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + + +### 7. Optional optimizations for GitLab setups with MySQL databases + +Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. + +``` +# Stop GitLab +sudo service gitlab stop + +# Secure your MySQL installation (added in GitLab 6.2) +sudo mysql_secure_installation + +# Login to MySQL +mysql -u root -p + +# do not type the 'mysql>', this is part of the prompt + +# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8) +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all outputed SQL statements + +# Convert all tables to correct character set +SET foreign_key_checks = 0; +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all outputed SQL statements + +# turn foreign key checks back on +SET foreign_key_checks = 1; + +# Find MySQL users +mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%'; + +# If git user exists and gitlab user does not exist +# you are done with the database cleanup tasks +mysql> \q + +# If both users exist skip to Delete gitlab user + +# Create new user for GitLab (changed in GitLab 6.4) +# change $password in the command below to a real password you pick +mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; + +# Grant the git user necessary permissions on the database +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Delete the old gitlab user +mysql> DELETE FROM mysql.user WHERE user='gitlab'; + +# Quit the database session +mysql> \q + +# Try connecting to the new database with the new user +sudo -u git -H mysql -u git -p -D gitlabhq_production + +# Type the password you replaced $password with earlier + +# You should now see a 'mysql>' prompt + +# Quit the database session +mysql> \q + +# Update database configuration details +# See config/database.yml.mysql for latest recommended configuration details +# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8) +# Set production -> pool: 10 (updated in GitLab 5.3) +# Set production -> username: git +# Set production -> password: the password your replaced $password with earlier +sudo -u git -H editor /home/git/gitlab/config/database.yml + +# Run thorough check +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` + + +## Things went south? Revert to previous version (7.4) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.3 to 7.4](7.3-to-7.4.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above. From 533f4cdf30b38c587f7a91f0dfd898b907ecd944 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Wed, 22 Oct 2014 10:54:59 +0200 Subject: [PATCH 277/408] gitlab shell works if multiple rubies installed Before this it would fail because git hooks automatically prepend things to the path, which can lead the wrong Ruby version to be called in which dependencies are not installed. To make sure that this is correct, the forked_merge_requests commented out test that depends on this change was uncommented. For that test to pass, it is also necessary to setup the mock server on port 3001 under test_env.rb. --- GITLAB_SHELL_VERSION | 2 +- config/application.rb | 2 + config/gitlab.yml.example | 2 +- .../initializers/gitlab_shell_secret_token.rb | 20 +------ .../project/forked_merge_requests.feature | 26 +++++---- lib/gitlab/backend/shell.rb | 21 ++++++++ lib/tasks/gitlab/shell.rake | 12 +++-- spec/support/test_env.rb | 53 ++++++++++++++++++- 8 files changed, 98 insertions(+), 40 deletions(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index ccbccc3dc62..276cbf9e285 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.2.0 +2.3.0 diff --git a/config/application.rb b/config/application.rb index 44a5d68d126..8300cf57a61 100644 --- a/config/application.rb +++ b/config/application.rb @@ -92,5 +92,7 @@ module Gitlab redis_config_hash[:namespace] = 'cache:gitlab' config.cache_store = :redis_store, redis_config_hash + + ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH'] end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index bb0ffae0b70..14b5e134ce2 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -307,7 +307,7 @@ test: enabled: true gitlab: host: localhost - port: 80 + port: 3001 # When you run tests we clone and setup gitlab-shell # In order to setup it correctly you need to specify diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb index 8d2b771e535..250b86caaf0 100644 --- a/config/initializers/gitlab_shell_secret_token.rb +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -1,19 +1 @@ -# Be sure to restart your server when you modify this file. - -require 'securerandom' - -# Your secret key for verifying the gitlab_shell. - - -secret_file = Rails.root.join('.gitlab_shell_secret') -gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') - -unless File.exist? secret_file - # Generate a new token of 16 random hexadecimal characters and store it in secret_file. - token = SecureRandom.hex(16) - File.write(secret_file, token) -end - -if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink) - FileUtils.symlink(secret_file, gitlab_shell_symlink) -end \ No newline at end of file +Gitlab::Shell.setup_secret_token diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index d9fbb875c28..7442145d87e 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -11,20 +11,18 @@ Feature: Project Forked Merge Requests And I submit the merge request Then I should see merge request "Merge Request On Forked Project" - # TODO: Improve it so it does not fail randomly - # - #@javascript - #Scenario: I can edit a forked merge request - #Given I visit project "Forked Shop" merge requests page - #And I click link "New Merge Request" - #And I fill out a "Merge Request On Forked Project" merge request - #And I submit the merge request - #And I should see merge request "Merge Request On Forked Project" - #And I click link edit "Merge Request On Forked Project" - #Then I see the edit page prefilled for "Merge Request On Forked Project" - #And I update the merge request title - #And I save the merge request - #Then I should see the edited merge request + @javascript + Scenario: I can edit a forked merge request + Given I visit project "Forked Shop" merge requests page + And I click link "New Merge Request" + And I fill out a "Merge Request On Forked Project" merge request + And I submit the merge request + And I should see merge request "Merge Request On Forked Project" + And I click link edit "Merge Request On Forked Project" + Then I see the edit page prefilled for "Merge Request On Forked Project" + And I update the merge request title + And I save the merge request + Then I should see the edited merge request @javascript Scenario: I cannot submit an invalid merge request diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index aabc7f1e69a..7b10ab539eb 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -1,3 +1,5 @@ +require 'securerandom' + module Gitlab class Shell class AccessDenied < StandardError; end @@ -13,6 +15,25 @@ module Gitlab @version_required ||= File.read(Rails.root. join('GITLAB_SHELL_VERSION')).strip end + + # Be sure to restart your server when you modify this method. + def setup_secret_token + secret_file = Rails.root.join('.gitlab_shell_secret') + gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, + '.gitlab_shell_secret') + + unless File.exist? secret_file + # Generate a new token of 16 random hexadecimal characters + # and store it in secret_file. + token = SecureRandom.hex(16) + File.write(secret_file, token) + end + + if File.exist?(Gitlab.config.gitlab_shell.path) && + !File.exist?(gitlab_shell_symlink) + FileUtils.symlink(secret_file, gitlab_shell_symlink) + end + end end # Init new repository diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 202e55c89ad..d3cc7135c54 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -22,10 +22,14 @@ namespace :gitlab do # Make sure we're on the right tag Dir.chdir(target_dir) do + # Allows to change the origin URL to the fork + # when developing gitlab-shell. + sh(*%W(git remote set-url origin #{args.repo})) + # First try to checkout without fetching # to avoid stalling tests if the Internet is down. - reset = "git reset --hard $(git describe #{args.tag} || git describe origin/#{args.tag})" - sh "#{reset} || git fetch origin && #{reset}" + reset = "(rev=\"$(git describe #{args.tag} || git describe \"origin/#{args.tag}\")\" && git reset --hard \"$rev\")" + sh "#{reset} || (git fetch --tags origin && #{reset})" config = { user: user, @@ -37,7 +41,7 @@ namespace :gitlab do bin: %x{which redis-cli}.chomp, namespace: "resque:gitlab" }.stringify_keys, - log_level: "INFO", + log_level: Rails.env.test? ? 'DEBUG' : 'INFO', audit_usernames: false }.stringify_keys @@ -66,6 +70,8 @@ namespace :gitlab do File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f| f.puts "PATH=#{ENV['PATH']}" end + + Gitlab::Shell.setup_secret_token end desc "GITLAB | Setup gitlab-shell" diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index e6db410fb1c..eb665b8b618 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -1,4 +1,5 @@ require 'rspec/mocks' +require 'webrick' module TestEnv extend self @@ -24,8 +25,6 @@ module TestEnv disable_mailer if opts[:mailer] == false # Clean /tmp/tests - tmp_test_path = Rails.root.join('tmp', 'tests') - if File.directory?(tmp_test_path) Dir.entries(tmp_test_path).each do |entry| unless ['.', '..', 'gitlab-shell', factory_repo_name].include?(entry) @@ -39,6 +38,8 @@ module TestEnv # Setup GitLab shell for test instance setup_gitlab_shell + setup_internal_api_mock + # Create repository for FactoryGirl.create(:project) setup_factory_repo end @@ -108,4 +109,52 @@ module TestEnv def factory_repo_name 'gitlab-test' end + + def tmp_test_path + Rails.root.join('tmp', 'tests') + end + + def internal_api_mock_pid_path + File.join(tmp_test_path, 'internal_api_mock.pid') + end + + # This mock server exists because during testing GitLab is not served + # on any port, but gitlab-shell needs to ask the GitLab internal API + # if it is OK to push to repositories. This can happen during blob web + # edit tests. The server always replies yes: this should not modify affect + # web interface tests. + def setup_internal_api_mock + begin + server = WEBrick::HTTPServer.new( + BindAddress: '0.0.0.0', + Port: Gitlab.config.gitlab.port, + AccessLog: [], + Logger: WEBrick::Log.new('/dev/null') + ) + rescue => ex + ex.message.prepend('could not start mock server on configured port. ') + raise ex + end + fork do + trap(:INT) { server.shutdown } + server.mount_proc('/') do |_req, res| + res.status = 200 + res.body = 'true' + end + WEBrick::Daemon.start do + File.write(internal_api_mock_pid_path, Process.pid) + end + server.start + end + # Ideally this should be called from `config.after(:suite)`, + # but on Spinach when user hits Ctrl+C the server does not get killed + # if the hook is set up with `Spinach.hooks.after_run`. + at_exit do + # The file should exist on normal operation, + # but certain errors can lead to it not existing. + if File.exists?(internal_api_mock_pid_path) + Process.kill(:INT, File.read(internal_api_mock_pid_path).to_i) + end + end + end end From 53bf52f191612df92d993cbcd3c4d6c89ab9c95a Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 14 Nov 2014 18:23:55 +0200 Subject: [PATCH 278/408] Better message for failed pushes because of git hooks Conflicts: lib/gitlab/git_access.rb spec/lib/gitlab/git_access_spec.rb --- GITLAB_SHELL_VERSION | 2 +- lib/api/internal.rb | 2 +- lib/gitlab/backend/grack_auth.rb | 2 +- lib/gitlab/git_access.rb | 51 +++++++++++++++---------- lib/gitlab/git_access_status.rb | 15 ++++++++ lib/gitlab/git_access_wiki.rb | 8 +++- spec/lib/gitlab/git_access_spec.rb | 24 ++++++------ spec/lib/gitlab/git_access_wiki_spec.rb | 4 +- spec/requests/api/internal_spec.rb | 16 ++++---- 9 files changed, 76 insertions(+), 48 deletions(-) create mode 100644 lib/gitlab/git_access_status.rb diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index ccbccc3dc62..276cbf9e285 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.2.0 +2.3.0 diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ebf2296097d..1648834f034 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -43,7 +43,7 @@ module API return false unless actor - access.allowed?( + access.check( actor, params[:action], project, diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index df1461a45c9..762639414e0 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -80,7 +80,7 @@ module Grack case git_cmd when *Gitlab::GitAccess::DOWNLOAD_COMMANDS if user - Gitlab::GitAccess.new.download_allowed?(user, project) + Gitlab::GitAccess.new.download_access_check(user, project).allowed? elsif project.public? # Allow clone/fetch for public projects true diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 129881060d5..3452240dad8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,61 +5,60 @@ module Gitlab attr_reader :params, :project, :git_cmd, :user - def allowed?(actor, cmd, project, changes = nil) + def check(actor, cmd, project, changes = nil) case cmd when *DOWNLOAD_COMMANDS if actor.is_a? User - download_allowed?(actor, project) + download_access_check(actor, project) elsif actor.is_a? DeployKey actor.projects.include?(project) elsif actor.is_a? Key - download_allowed?(actor.user, project) + download_access_check(actor.user, project) else raise 'Wrong actor' end when *PUSH_COMMANDS if actor.is_a? User - push_allowed?(actor, project, changes) + push_access_check(actor, project, changes) elsif actor.is_a? DeployKey - # Deploy key not allowed to push - return false + return build_status_object(false, "Deploy key not allowed to push") elsif actor.is_a? Key - push_allowed?(actor.user, project, changes) + push_access_check(actor.user, project, changes) else raise 'Wrong actor' end else - false + return build_status_object(false, "Wrong command") end end - def download_allowed?(user, project) - if user && user_allowed?(user) - user.can?(:download_code, project) + def download_access_check(user, project) + if user && user_allowed?(user) && user.can?(:download_code, project) + build_status_object(true) else - false + build_status_object(false, "You don't have access") end end - def push_allowed?(user, project, changes) - return false unless user && user_allowed?(user) - return true if changes.blank? + def push_access_check(user, project, changes) + return build_status_object(false, "You don't have access") unless user && user_allowed?(user) + return build_status_object(true) if changes.blank? changes = changes.lines if changes.kind_of?(String) # Iterate over all changes to find if user allowed all of them to be applied changes.each do |change| - unless change_allowed?(user, project, change) + status = change_access_check(user, project, change) + unless status.allowed? # If user does not have access to make at least one change - cancel all push - return false + return status end end - # If user has access to make all changes - true + return build_status_object(true) end - def change_allowed?(user, project, change) + def change_access_check(user, project, change) oldrev, newrev, ref = change.split(' ') action = if project.protected_branch?(branch_name(ref)) @@ -79,7 +78,11 @@ module Gitlab :push_code end - user.can?(action, project) + if user.can?(action, project) + build_status_object(true) + else + build_status_object(false, "You don't have permission") + end end def forced_push?(project, oldrev, newrev) @@ -116,5 +119,11 @@ module Gitlab nil end end + + protected + + def build_status_object(status, message = '') + GitAccessStatus.new(status, message) + end end end diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb new file mode 100644 index 00000000000..3d451ecebee --- /dev/null +++ b/lib/gitlab/git_access_status.rb @@ -0,0 +1,15 @@ +module Gitlab + class GitAccessStatus + attr_accessor :status, :message + alias_method :allowed?, :status + + def initialize(status, message = '') + @status = status + @message = message + end + + def to_json + {status: @status, message: @message}.to_json + end + end +end \ No newline at end of file diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index 9f0eb3be20f..f7d1428deb2 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -1,7 +1,11 @@ module Gitlab class GitAccessWiki < GitAccess - def change_allowed?(user, project, change) - user.can?(:write_wiki, project) + def change_allowed_check(user, project, change) + if user.can?(:write_wiki, project) + build_status_object(true) + else + build_status_object(false, "You don't have access") + end end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index fe0a6bbdabb..1addba55787 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -5,14 +5,14 @@ describe Gitlab::GitAccess do let(:project) { create(:project) } let(:user) { create(:user) } - describe 'download_allowed?' do + describe 'download_access_check' do describe 'master permissions' do before { project.team << [user, :master] } context 'pull code' do - subject { access.download_allowed?(user, project) } + subject { access.download_access_check(user, project) } - it { should be_true } + it { subject.allowed?.should be_true } end end @@ -20,9 +20,9 @@ describe Gitlab::GitAccess do before { project.team << [user, :guest] } context 'pull code' do - subject { access.download_allowed?(user, project) } + subject { access.download_access_check(user, project) } - it { should be_false } + it { subject.allowed?.should be_false } end end @@ -33,22 +33,22 @@ describe Gitlab::GitAccess do end context 'pull code' do - subject { access.download_allowed?(user, project) } + subject { access.download_access_check(user, project) } - it { should be_false } + it { subject.allowed?.should be_false } end end describe 'without acccess to project' do context 'pull code' do - subject { access.download_allowed?(user, project) } + subject { access.download_access_check(user, project) } - it { should be_false } + it { subject.allowed?.should be_false } end end end - describe 'push_allowed?' do + describe 'push_access_check' do def protect_feature_branch create(:protected_branch, name: 'feature', project: project) end @@ -117,9 +117,9 @@ describe Gitlab::GitAccess do permissions_matrix[role].each do |action, allowed| context action do - subject { access.push_allowed?(user, project, changes[action]) } + subject { access.push_access_check(user, project, changes[action]) } - it { should allowed ? be_true : be_false } + it { subject.allowed?.should allowed ? be_true : be_false } end end end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index ed5785b31e6..d8d19fd50f0 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -11,9 +11,9 @@ describe Gitlab::GitAccessWiki do project.team << [user, :developer] end - subject { access.push_allowed?(user, project, changes) } + subject { access.push_access_check(user, project, changes) } - it { should be_true } + it { subject.should be_true } end def changes diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 677b1494041..53b7808d4c3 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -37,7 +37,7 @@ describe API::API, api: true do pull(key, project) response.status.should == 200 - response.body.should == 'true' + JSON.parse(response.body)["status"].should be_true end end @@ -46,7 +46,7 @@ describe API::API, api: true do push(key, project) response.status.should == 200 - response.body.should == 'true' + JSON.parse(response.body)["status"].should be_true end end end @@ -61,7 +61,7 @@ describe API::API, api: true do pull(key, project) response.status.should == 200 - response.body.should == 'false' + JSON.parse(response.body)["status"].should be_false end end @@ -70,7 +70,7 @@ describe API::API, api: true do push(key, project) response.status.should == 200 - response.body.should == 'false' + JSON.parse(response.body)["status"].should be_false end end end @@ -87,7 +87,7 @@ describe API::API, api: true do pull(key, personal_project) response.status.should == 200 - response.body.should == 'false' + JSON.parse(response.body)["status"].should be_false end end @@ -96,7 +96,7 @@ describe API::API, api: true do push(key, personal_project) response.status.should == 200 - response.body.should == 'false' + JSON.parse(response.body)["status"].should be_false end end end @@ -114,7 +114,7 @@ describe API::API, api: true do pull(key, project) response.status.should == 200 - response.body.should == 'true' + JSON.parse(response.body)["status"].should be_true end end @@ -123,7 +123,7 @@ describe API::API, api: true do push(key, project) response.status.should == 200 - response.body.should == 'false' + JSON.parse(response.body)["status"].should be_false end end end From b8fcaa7f4126ce2b5fe0436197b2aacc1be84e96 Mon Sep 17 00:00:00 2001 From: Zertrin Date: Tue, 21 Oct 2014 09:59:03 +0200 Subject: [PATCH 279/408] revert using the extension of the blob to determine the syntax highlighting language nohighlight functionality for a hardcoded set of filenames is kept --- app/helpers/blob_helper.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 11fbf1baae7..420ac3f77c7 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,14 +1,9 @@ module BlobHelper def highlightjs_class(blob_name) - if blob_name.include?('.') - ext = blob_name.split('.').last - return 'language-' + ext + if no_highlight_files.include?(blob_name.downcase) + 'no-highlight' else - if no_highlight_files.include?(blob_name.downcase) - 'no-highlight' - else - blob_name.downcase - end + blob_name.downcase end end From 1016acc6096849e239d65ae386005b7563f110c6 Mon Sep 17 00:00:00 2001 From: Daniel Serodio Date: Tue, 18 Nov 2014 10:59:04 -0200 Subject: [PATCH 280/408] Small improvement to /api/user/keys doc The keys resource includes a create_at attribute --- doc/api/users.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 20e0d68977e..b30a31deccc 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -260,12 +260,14 @@ GET /user/keys { "id": 1, "title": "Public key", - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2014-08-01T14:47:39.080Z" }, { "id": 3, "title": "Another Public key", - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2014-08-01T14:47:39.080Z" } ] ``` @@ -302,7 +304,8 @@ Parameters: { "id": 1, "title": "Public key", - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2014-08-01T14:47:39.080Z" } ``` From f7bf892cca6bb8106194c14bef1ed9ddfc26ec91 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 18 Nov 2014 17:14:36 +0200 Subject: [PATCH 281/408] Revert "gitlab shell works if multiple rubies installed" This reverts commit 533f4cdf30b38c587f7a91f0dfd898b907ecd944. --- GITLAB_SHELL_VERSION | 2 +- config/application.rb | 2 - config/gitlab.yml.example | 2 +- .../initializers/gitlab_shell_secret_token.rb | 20 ++++++- .../project/forked_merge_requests.feature | 26 ++++----- lib/gitlab/backend/shell.rb | 21 -------- lib/tasks/gitlab/shell.rake | 12 ++--- spec/support/test_env.rb | 53 +------------------ 8 files changed, 40 insertions(+), 98 deletions(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 276cbf9e285..ccbccc3dc62 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.3.0 +2.2.0 diff --git a/config/application.rb b/config/application.rb index 8300cf57a61..44a5d68d126 100644 --- a/config/application.rb +++ b/config/application.rb @@ -92,7 +92,5 @@ module Gitlab redis_config_hash[:namespace] = 'cache:gitlab' config.cache_store = :redis_store, redis_config_hash - - ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH'] end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 14b5e134ce2..bb0ffae0b70 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -307,7 +307,7 @@ test: enabled: true gitlab: host: localhost - port: 3001 + port: 80 # When you run tests we clone and setup gitlab-shell # In order to setup it correctly you need to specify diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb index 250b86caaf0..8d2b771e535 100644 --- a/config/initializers/gitlab_shell_secret_token.rb +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -1 +1,19 @@ -Gitlab::Shell.setup_secret_token +# Be sure to restart your server when you modify this file. + +require 'securerandom' + +# Your secret key for verifying the gitlab_shell. + + +secret_file = Rails.root.join('.gitlab_shell_secret') +gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') + +unless File.exist? secret_file + # Generate a new token of 16 random hexadecimal characters and store it in secret_file. + token = SecureRandom.hex(16) + File.write(secret_file, token) +end + +if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink) + FileUtils.symlink(secret_file, gitlab_shell_symlink) +end \ No newline at end of file diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index 7442145d87e..d9fbb875c28 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -11,18 +11,20 @@ Feature: Project Forked Merge Requests And I submit the merge request Then I should see merge request "Merge Request On Forked Project" - @javascript - Scenario: I can edit a forked merge request - Given I visit project "Forked Shop" merge requests page - And I click link "New Merge Request" - And I fill out a "Merge Request On Forked Project" merge request - And I submit the merge request - And I should see merge request "Merge Request On Forked Project" - And I click link edit "Merge Request On Forked Project" - Then I see the edit page prefilled for "Merge Request On Forked Project" - And I update the merge request title - And I save the merge request - Then I should see the edited merge request + # TODO: Improve it so it does not fail randomly + # + #@javascript + #Scenario: I can edit a forked merge request + #Given I visit project "Forked Shop" merge requests page + #And I click link "New Merge Request" + #And I fill out a "Merge Request On Forked Project" merge request + #And I submit the merge request + #And I should see merge request "Merge Request On Forked Project" + #And I click link edit "Merge Request On Forked Project" + #Then I see the edit page prefilled for "Merge Request On Forked Project" + #And I update the merge request title + #And I save the merge request + #Then I should see the edited merge request @javascript Scenario: I cannot submit an invalid merge request diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 7b10ab539eb..aabc7f1e69a 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -1,5 +1,3 @@ -require 'securerandom' - module Gitlab class Shell class AccessDenied < StandardError; end @@ -15,25 +13,6 @@ module Gitlab @version_required ||= File.read(Rails.root. join('GITLAB_SHELL_VERSION')).strip end - - # Be sure to restart your server when you modify this method. - def setup_secret_token - secret_file = Rails.root.join('.gitlab_shell_secret') - gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, - '.gitlab_shell_secret') - - unless File.exist? secret_file - # Generate a new token of 16 random hexadecimal characters - # and store it in secret_file. - token = SecureRandom.hex(16) - File.write(secret_file, token) - end - - if File.exist?(Gitlab.config.gitlab_shell.path) && - !File.exist?(gitlab_shell_symlink) - FileUtils.symlink(secret_file, gitlab_shell_symlink) - end - end end # Init new repository diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index d3cc7135c54..202e55c89ad 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -22,14 +22,10 @@ namespace :gitlab do # Make sure we're on the right tag Dir.chdir(target_dir) do - # Allows to change the origin URL to the fork - # when developing gitlab-shell. - sh(*%W(git remote set-url origin #{args.repo})) - # First try to checkout without fetching # to avoid stalling tests if the Internet is down. - reset = "(rev=\"$(git describe #{args.tag} || git describe \"origin/#{args.tag}\")\" && git reset --hard \"$rev\")" - sh "#{reset} || (git fetch --tags origin && #{reset})" + reset = "git reset --hard $(git describe #{args.tag} || git describe origin/#{args.tag})" + sh "#{reset} || git fetch origin && #{reset}" config = { user: user, @@ -41,7 +37,7 @@ namespace :gitlab do bin: %x{which redis-cli}.chomp, namespace: "resque:gitlab" }.stringify_keys, - log_level: Rails.env.test? ? 'DEBUG' : 'INFO', + log_level: "INFO", audit_usernames: false }.stringify_keys @@ -70,8 +66,6 @@ namespace :gitlab do File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f| f.puts "PATH=#{ENV['PATH']}" end - - Gitlab::Shell.setup_secret_token end desc "GITLAB | Setup gitlab-shell" diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index eb665b8b618..e6db410fb1c 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -1,5 +1,4 @@ require 'rspec/mocks' -require 'webrick' module TestEnv extend self @@ -25,6 +24,8 @@ module TestEnv disable_mailer if opts[:mailer] == false # Clean /tmp/tests + tmp_test_path = Rails.root.join('tmp', 'tests') + if File.directory?(tmp_test_path) Dir.entries(tmp_test_path).each do |entry| unless ['.', '..', 'gitlab-shell', factory_repo_name].include?(entry) @@ -38,8 +39,6 @@ module TestEnv # Setup GitLab shell for test instance setup_gitlab_shell - setup_internal_api_mock - # Create repository for FactoryGirl.create(:project) setup_factory_repo end @@ -109,52 +108,4 @@ module TestEnv def factory_repo_name 'gitlab-test' end - - def tmp_test_path - Rails.root.join('tmp', 'tests') - end - - def internal_api_mock_pid_path - File.join(tmp_test_path, 'internal_api_mock.pid') - end - - # This mock server exists because during testing GitLab is not served - # on any port, but gitlab-shell needs to ask the GitLab internal API - # if it is OK to push to repositories. This can happen during blob web - # edit tests. The server always replies yes: this should not modify affect - # web interface tests. - def setup_internal_api_mock - begin - server = WEBrick::HTTPServer.new( - BindAddress: '0.0.0.0', - Port: Gitlab.config.gitlab.port, - AccessLog: [], - Logger: WEBrick::Log.new('/dev/null') - ) - rescue => ex - ex.message.prepend('could not start mock server on configured port. ') - raise ex - end - fork do - trap(:INT) { server.shutdown } - server.mount_proc('/') do |_req, res| - res.status = 200 - res.body = 'true' - end - WEBrick::Daemon.start do - File.write(internal_api_mock_pid_path, Process.pid) - end - server.start - end - # Ideally this should be called from `config.after(:suite)`, - # but on Spinach when user hits Ctrl+C the server does not get killed - # if the hook is set up with `Spinach.hooks.after_run`. - at_exit do - # The file should exist on normal operation, - # but certain errors can lead to it not existing. - if File.exists?(internal_api_mock_pid_path) - Process.kill(:INT, File.read(internal_api_mock_pid_path).to_i) - end - end - end end From 8e7fa0c2a1add0c583409c2617bc06e949cd3a72 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 18 Nov 2014 17:15:51 +0200 Subject: [PATCH 282/408] Use new gitlab-shell v2.3.0 Signed-off-by: Dmitriy Zaporozhets --- GITLAB_SHELL_VERSION | 2 +- config/application.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index ccbccc3dc62..276cbf9e285 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.2.0 +2.3.0 diff --git a/config/application.rb b/config/application.rb index 44a5d68d126..8a280de6fac 100644 --- a/config/application.rb +++ b/config/application.rb @@ -92,5 +92,8 @@ module Gitlab redis_config_hash[:namespace] = 'cache:gitlab' config.cache_store = :redis_store, redis_config_hash + + # This is needed for gitlab-shell + ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH'] end end From 7fb3b908ed825282a2b540e514a92a6cd8e267c5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 18 Nov 2014 16:43:24 +0200 Subject: [PATCH 283/408] Bump gitlab_git with new rugged Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index bb8aef65d2f..2c4274dcf3d 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,7 @@ gem 'omniauth-shibboleth' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '7.0.0.rc11' +gem "gitlab_git", '7.0.0.rc12' # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' diff --git a/Gemfile.lock b/Gemfile.lock index a3645f7bbec..938ce560620 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -179,11 +179,11 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.0.1.1) emoji (~> 1.0.1) - gitlab_git (7.0.0.rc11) + gitlab_git (7.0.0.rc12) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) - rugged (~> 0.21.0) + rugged (~> 0.21.2) gitlab_meta (7.0) gitlab_omniauth-ldap (1.2.0) net-ldap (~> 0.9) @@ -447,7 +447,7 @@ GEM ruby-progressbar (1.2.0) rubyntlm (0.4.0) rubypants (0.2.0) - rugged (0.21.0) + rugged (0.21.2) safe_yaml (0.9.7) sanitize (2.1.0) nokogiri (>= 1.4.4) @@ -625,7 +625,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.0.pre) gitlab-linguist (~> 3.0.0) gitlab_emoji (~> 0.0.1.1) - gitlab_git (= 7.0.0.rc11) + gitlab_git (= 7.0.0.rc12) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.0) gollum-lib (~> 3.0.0) From f9aead9f6e7e477236a51fa4eab3a6cba5dd2331 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 18 Nov 2014 18:00:38 +0200 Subject: [PATCH 284/408] Hide gpg signature on tags page from tag message Signed-off-by: Dmitriy Zaporozhets --- app/helpers/git_helper.rb | 5 +++++ app/views/projects/tags/_tag.html.haml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 app/helpers/git_helper.rb diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb new file mode 100644 index 00000000000..09684955233 --- /dev/null +++ b/app/helpers/git_helper.rb @@ -0,0 +1,5 @@ +module GitHelper + def strip_gpg_signature(text) + text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "") + end +end diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index f93c1b4211f..4ab102ba96c 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -6,7 +6,7 @@ = tag.name - if tag.message.present?   - = tag.message + = strip_gpg_signature(tag.message) .pull-right - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' From fe46a5c0847007b2e6e36addc3e1f09a3e837390 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 18 Nov 2014 18:44:12 +0200 Subject: [PATCH 285/408] Start 7.6.0 Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 17 +++++++++++++++++ VERSION | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b19722581c7..cc94e1af486 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,20 @@ +v 7.6.0 + - Fork repository to groups + - New rugged version + - + - + - + - + - + - + - + - + - + - + - + - + - + v 7.5.0 - API: Add support for Hipchat (Kevin Houdebert) - Add time zone configuration in gitlab.yml (Sullivan Senechal) diff --git a/VERSION b/VERSION index 027a8b7b332..a28398aef42 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.5.0.pre +7.6.0.pre From 23ef17835734bf81589543dfb390ccf4729731cd Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 19 Nov 2014 11:11:27 +0200 Subject: [PATCH 286/408] bump gitlab_shell --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 276cbf9e285..2bf1c1ccf36 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.3.0 +2.3.1 From b34f1be47f667412fc4355bafd17163f2a9f8466 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Nov 2014 15:48:28 +0200 Subject: [PATCH 287/408] Increase md typography font size Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/files.scss | 3 --- app/assets/stylesheets/main/mixins.scss | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss index e2b0ef0c5ea..1ed41272ac5 100644 --- a/app/assets/stylesheets/generic/files.scss +++ b/app/assets/stylesheets/generic/files.scss @@ -42,7 +42,6 @@ } .file-content { background: #fff; - font-size: 11px; &.image_file { background: #eee; @@ -54,8 +53,6 @@ } &.wiki { - font-size: 14px; - line-height: 1.6; padding: 25px; .highlight { diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss index 7f607fc4e8b..5f83913b73b 100644 --- a/app/assets/stylesheets/main/mixins.scss +++ b/app/assets/stylesheets/main/mixins.scss @@ -58,8 +58,8 @@ } @mixin md-typography { - font-size: 14px; - line-height: 1.6; + font-size: 15px; + line-height: 1.5; img { max-width: 100%; @@ -93,7 +93,7 @@ blockquote p { color: #888; - font-size: 14px; + font-size: 15px; line-height: 1.5; } From e65866d4ff337f66e3526a3eb7ac5ea9ae0c3d8f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Nov 2014 17:45:41 +0200 Subject: [PATCH 288/408] Improve dashboard page for mobile Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/sections/events.scss | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 656aa5b18a6..485a9c46610 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -186,7 +186,24 @@ } @media (max-width: $screen-xs-max) { - .event-item .event-title { - @include str-truncated(65%); + .event-item { + .event-title { + white-space: normal; + overflow: visible; + max-width: 100%; + } + .avatar { + display: none; + } + + .event-body { + margin: 0; + border-left: 2px solid #DDD; + padding-left: 10px; + } + + .event-item-timestamp { + display: none; + } } } From 580cedd76cb1b2a9101fcb722dfec455ad00c8c6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Nov 2014 18:11:16 +0200 Subject: [PATCH 289/408] Fix header and project home ui for mobile Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/sections/header.scss | 1 + app/assets/stylesheets/sections/nav.scss | 2 +- app/assets/stylesheets/sections/projects.scss | 13 +++++++++++++ app/views/projects/_home_panel.html.haml | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index e0e0d60c387..9ad1a1db2cd 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -59,6 +59,7 @@ header { } .navbar-collapse { + margin-top: 47px; padding-right: 0; padding-left: 0; } diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 31c0a0835db..ccd672c5f67 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -63,7 +63,6 @@ @media (max-width: $screen-xs-max) { font-size: 18px; margin: 0; - max-height: none; &, .container { @@ -86,6 +85,7 @@ color: #fff; font-weight: normal; text-shadow: none; + border: none; &:after { display: none; } } diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 76a7507d699..7b894cf00bb 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -295,3 +295,16 @@ ul.nav.nav-projects-tabs { } } } + +@media (max-width: $screen-xs-max) { + .project-home-panel { + .star-fork-buttons { + padding-top: 10px; + padding-right: 15px; + } + } + + .project-home-links { + display: none; + } +} diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 8b9260d661c..30d063c7a36 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -31,7 +31,7 @@ - else = link_to_toggle_star('You must sign in to star a project.', false, false) - .project-home-row + .project-home-row.hidden-xs - if current_user && !empty_repo .project-home-dropdown = render "dropdown" From 2f3df4cb567a6f14b5a0e161084c2f4cf6fbf764 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Sat, 8 Nov 2014 23:12:14 -0600 Subject: [PATCH 290/408] HipChat service: correct service name & use v2 API HipChat refers to their own product camel cased so we should do the same. HipChat no longer recommends people use the deprecated v1 API so switch to using the v2 API by default. hipchat-rb does not yet default to v2 in any version so it must be specified. --- Gemfile | 2 +- Gemfile.lock | 5 ++--- app/models/project_services/hipchat_service.rb | 5 +++-- features/steps/project/services.rb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 2c4274dcf3d..613ef11cf4d 100644 --- a/Gemfile +++ b/Gemfile @@ -134,7 +134,7 @@ gem "redis-rails" gem 'tinder', '~> 1.9.2' # HipChat integration -gem "hipchat", "~> 0.14.0" +gem "hipchat", "~> 1.4.0" # Flowdock integration gem "gitlab-flowdock-git-hook", "~> 0.4.2" diff --git a/Gemfile.lock b/Gemfile.lock index 938ce560620..b6c1dcfa331 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -235,8 +235,7 @@ GEM railties (>= 4.0.1) hashie (2.1.2) hike (1.2.3) - hipchat (0.14.0) - httparty + hipchat (1.4.0) httparty html-pipeline (1.11.0) activesupport (>= 2) @@ -636,7 +635,7 @@ DEPENDENCIES guard-rspec guard-spinach haml-rails - hipchat (~> 0.14.0) + hipchat (~> 1.4.0) html-pipeline-gitlab (~> 0.1.0) httparty jasmine (= 2.0.2) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 4078938cdbb..2b804687853 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -19,7 +19,7 @@ class HipchatService < Service validates :token, presence: true, if: :activated? def title - 'Hipchat' + 'HipChat' end def description @@ -44,7 +44,8 @@ class HipchatService < Service private def gate - @gate ||= HipChat::Client.new(token) + options = { api_version: 'v2' } + @gate ||= HipChat::Client.new(token, options) end def create_message(push) diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index d5d58070d86..ffc231cb575 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -10,7 +10,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps step 'I should see list of available services' do page.should have_content 'Project services' page.should have_content 'Campfire' - page.should have_content 'Hipchat' + page.should have_content 'HipChat' page.should have_content 'GitLab CI' page.should have_content 'Assembla' page.should have_content 'Pushover' @@ -33,7 +33,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps end step 'I click hipchat service link' do - click_link 'Hipchat' + click_link 'HipChat' end step 'I fill hipchat settings' do From 1353f9aa643f86a3f38f3d2dfa8666d3d942293e Mon Sep 17 00:00:00 2001 From: Daniel Aquino Date: Sat, 8 Nov 2014 23:04:31 -0600 Subject: [PATCH 291/408] HipChat service: support custom servers HipChat allows users to run their own private servers and to be able to support those we must connect to the correct URL when using one of these custom servers. --- app/controllers/projects/services_controller.rb | 2 +- app/models/project_services/hipchat_service.rb | 7 +++++-- features/project/service.feature | 6 ++++++ features/steps/project/services.rb | 11 +++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index a5f30dcfd9d..c50a1f1e75b 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController :title, :token, :type, :active, :api_key, :subdomain, :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, - :build_key + :build_key, :server ) end end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 2b804687853..a848d74044c 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -15,7 +15,7 @@ class HipchatService < Service MAX_COMMITS = 3 - prop_accessor :token, :room + prop_accessor :token, :room, :server validates :token, presence: true, if: :activated? def title @@ -33,7 +33,9 @@ class HipchatService < Service def fields [ { type: 'text', name: 'token', placeholder: '' }, - { type: 'text', name: 'room', placeholder: '' } + { type: 'text', name: 'room', placeholder: '' }, + { type: 'text', name: 'server', + placeholder: 'Leave blank for default. https://chat.hipchat.com' } ] end @@ -45,6 +47,7 @@ class HipchatService < Service def gate options = { api_version: 'v2' } + options[:server_url] = server unless server.nil? @gate ||= HipChat::Client.new(token, options) end diff --git a/features/project/service.feature b/features/project/service.feature index 88fd038d45f..ed9e03b428d 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -19,6 +19,12 @@ Feature: Project Services And I fill hipchat settings Then I should see hipchat service settings saved + Scenario: Activate hipchat service with custom server + When I visit project "Shop" services page + And I click hipchat service link + And I fill hipchat settings with custom server + Then I should see hipchat service settings with custom server saved + Scenario: Activate pivotaltracker service When I visit project "Shop" services page And I click pivotaltracker service link diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index ffc231cb575..7a0b47a8fe5 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -47,6 +47,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps find_field('Room').value.should == 'gitlab' end + step 'I fill hipchat settings with custom server' do + check 'Active' + fill_in 'Room', with: 'gitlab_custom' + fill_in 'Token', with: 'secretCustom' + fill_in 'Server', with: 'https://chat.example.com' + click_button 'Save' + end + + step 'I should see hipchat service settings with custom server saved' do + find_field('Server').value.should == 'https://chat.example.com' + end step 'I click pivotaltracker service link' do click_link 'PivotalTracker' From c4fc734e78f8061e54e3d7374e8db5eb6f555290 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 20 Nov 2014 12:18:28 +0100 Subject: [PATCH 292/408] add rebuilding of authorized_keys to docs --- doc/raketasks/maintenance.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md index f6bd7565799..c8696861067 100644 --- a/doc/raketasks/maintenance.md +++ b/doc/raketasks/maintenance.md @@ -122,3 +122,26 @@ sudo -u git -H mkdir -p /home/git/gitlab-satellites sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites ``` + +## Rebuild authorized_keys file + +In some case it is necessary to rebuild the `authorized_keys` file. + + +For Omnibus-packages +``` +sudo gitlab-rake gitlab:shell:setup +``` + +For installations from source: +``` +sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production +``` + +``` +This will rebuild an authorized_keys file. +You will lose any data stored in authorized_keys file. +Do you want to continue (yes/no)? yes + +............................ +``` From a72a919ae5cf8d8984fef50ed3cac141fe17cc66 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 20 Nov 2014 12:22:46 +0100 Subject: [PATCH 293/408] add correct path to rebuild-keys doc --- doc/raketasks/maintenance.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md index c8696861067..8bef92e55fe 100644 --- a/doc/raketasks/maintenance.md +++ b/doc/raketasks/maintenance.md @@ -128,13 +128,14 @@ sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites In some case it is necessary to rebuild the `authorized_keys` file. -For Omnibus-packages +For Omnibus-packages: ``` sudo gitlab-rake gitlab:shell:setup ``` For installations from source: ``` +cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production ``` From 7c54c63ac14eb8f5ce0e364d709988fcfe4dda64 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 20 Nov 2014 15:46:04 +0100 Subject: [PATCH 294/408] Add CRON=1 backup setting for quiet backups --- CHANGELOG | 2 +- doc/raketasks/backup_restore.md | 5 +++- lib/backup/database.rb | 12 +++++----- lib/backup/manager.rb | 32 ++++++++++++------------- lib/backup/repository.rb | 41 ++++++++++++++++++++------------- lib/tasks/gitlab/backup.rake | 35 ++++++++++++++++++---------- 6 files changed, 75 insertions(+), 52 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cc94e1af486..b5e87c1ec6e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ v 7.6.0 - Fork repository to groups - New rugged version - - + - Add CRON=1 backup setting for quiet backups - - - diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index d2f0d6e7bc1..68e8a14f52f 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -203,5 +203,8 @@ Add the following lines at the bottom: ``` # Create a full backup of the GitLab repositories and SQL database every day at 4am -0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production +0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1 ``` + +The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors. +This is recommended to reduce cron spam. diff --git a/lib/backup/database.rb b/lib/backup/database.rb index ea659e3b605..9ab6aca276d 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -13,10 +13,10 @@ module Backup def dump success = case config["adapter"] when /^mysql/ then - print "Dumping MySQL database #{config['database']} ... " + $progress.print "Dumping MySQL database #{config['database']} ... " system('mysqldump', *mysql_args, config['database'], out: db_file_name) when "postgresql" then - print "Dumping PostgreSQL database #{config['database']} ... " + $progress.print "Dumping PostgreSQL database #{config['database']} ... " pg_env system('pg_dump', config['database'], out: db_file_name) end @@ -27,10 +27,10 @@ module Backup def restore success = case config["adapter"] when /^mysql/ then - print "Restoring MySQL database #{config['database']} ... " + $progress.print "Restoring MySQL database #{config['database']} ... " system('mysql', *mysql_args, config['database'], in: db_file_name) when "postgresql" then - print "Restoring PostgreSQL database #{config['database']} ... " + $progress.print "Restoring PostgreSQL database #{config['database']} ... " # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE # statements like MySQL. Rake::Task["gitlab:db:drop_all_tables"].invoke @@ -69,9 +69,9 @@ module Backup def report_success(success) if success - puts '[DONE]'.green + $progress.puts '[DONE]'.green else - puts '[FAILED]'.red + $progress.puts '[FAILED]'.red end end end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 03fe0f0b02f..ab8db4e9837 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -18,11 +18,11 @@ module Backup end # create archive - print "Creating backup archive: #{tar_file} ... " + $progress.print "Creating backup archive: #{tar_file} ... " if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS) - puts "done".green + $progress.puts "done".green else - puts "failed".red + puts "creating archive #{tar_file} failed".red abort 'Backup failed' end @@ -31,37 +31,37 @@ module Backup def upload(tar_file) remote_directory = Gitlab.config.backup.upload.remote_directory - print "Uploading backup archive to remote storage #{remote_directory} ... " + $progress.print "Uploading backup archive to remote storage #{remote_directory} ... " connection_settings = Gitlab.config.backup.upload.connection if connection_settings.blank? - puts "skipped".yellow + $progress.puts "skipped".yellow return end connection = ::Fog::Storage.new(connection_settings) directory = connection.directories.get(remote_directory) if directory.files.create(key: tar_file, body: File.open(tar_file), public: false) - puts "done".green + $progress.puts "done".green else - puts "failed".red + puts "uploading backup to #{remote_directory} failed".red abort 'Backup failed' end end def cleanup - print "Deleting tmp directories ... " + $progress.print "Deleting tmp directories ... " if Kernel.system('rm', '-rf', *BACKUP_CONTENTS) - puts "done".green + $progress.puts "done".green else - puts "failed".red + puts "deleting tmp directory failed".red abort 'Backup failed' end end def remove_old # delete backups - print "Deleting old backups ... " + $progress.print "Deleting old backups ... " keep_time = Gitlab.config.backup.keep_time.to_i path = Gitlab.config.backup.path @@ -76,9 +76,9 @@ module Backup end end end - puts "done. (#{removed} removed)".green + $progress.puts "done. (#{removed} removed)".green else - puts "skipping".yellow + $progress.puts "skipping".yellow end end @@ -101,12 +101,12 @@ module Backup exit 1 end - print "Unpacking backup ... " + $progress.print "Unpacking backup ... " unless Kernel.system(*%W(tar -xf #{tar_file})) - puts "failed".red + puts "unpacking backup failed".red exit 1 else - puts "done".green + $progress.puts "done".green end settings = YAML.load_file("backup_information.yml") diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index faa1b3b4099..f39fba23cf5 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -8,19 +8,21 @@ module Backup prepare Project.find_each(batch_size: 1000) do |project| - print " * #{project.path_with_namespace} ... " + $progress.print " * #{project.path_with_namespace} ... " # Create namespace dir if missing FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace if project.empty_repo? - puts "[SKIPPED]".cyan + $progress.puts "[SKIPPED]".cyan else - output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all)) + cmd = %W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all) + output, status = Gitlab::Popen.popen(cmd) if status.zero? - puts "[DONE]".green + $progress.puts "[DONE]".green else puts "[FAILED]".red + puts "failed: #{cmd.join(' ')}" puts output abort 'Backup failed' end @@ -29,15 +31,17 @@ module Backup wiki = ProjectWiki.new(project) if File.exists?(path_to_repo(wiki)) - print " * #{wiki.path_with_namespace} ... " + $progress.print " * #{wiki.path_with_namespace} ... " if wiki.repository.empty? - puts " [SKIPPED]".cyan + $progress.puts " [SKIPPED]".cyan else - output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)) + cmd = %W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all) + output, status = Gitlab::Popen.popen(cmd) if status.zero? - puts " [DONE]".green + $progress.puts " [DONE]".green else puts " [FAILED]".red + puts "failed: #{cmd.join(' ')}" abort 'Backup failed' end end @@ -55,7 +59,7 @@ module Backup FileUtils.mkdir_p(repos_path) Project.find_each(batch_size: 1000) do |project| - print "#{project.path_with_namespace} ... " + $progress.print "#{project.path_with_namespace} ... " project.namespace.ensure_dir_exist if project.namespace @@ -66,30 +70,35 @@ module Backup end if system(*cmd, silent) - puts "[DONE]".green + $progress.puts "[DONE]".green else puts "[FAILED]".red + puts "failed: #{cmd.join(' ')}" abort 'Restore failed' end wiki = ProjectWiki.new(project) if File.exists?(path_to_bundle(wiki)) - print " * #{wiki.path_with_namespace} ... " - if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent) - puts " [DONE]".green + $progress.print " * #{wiki.path_with_namespace} ... " + cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}) + if system(*cmd, silent) + $progress.puts " [DONE]".green else puts " [FAILED]".red + puts "failed: #{cmd.join(' ')}" abort 'Restore failed' end end end - print 'Put GitLab hooks in repositories dirs'.yellow - if system("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks") - puts " [DONE]".green + $progress.print 'Put GitLab hooks in repositories dirs'.yellow + cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks" + if system(cmd) + $progress.puts " [DONE]".green else puts " [FAILED]".red + puts "failed: #{cmd}" end end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 2eff1260b61..99e84f62c66 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -6,6 +6,7 @@ namespace :gitlab do desc "GITLAB | Create a backup of the GitLab system" task create: :environment do warn_user_is_not_gitlab + configure_cron_mode Rake::Task["gitlab:backup:db:create"].invoke Rake::Task["gitlab:backup:repo:create"].invoke @@ -21,6 +22,7 @@ namespace :gitlab do desc "GITLAB | Restore a previously created backup" task restore: :environment do warn_user_is_not_gitlab + configure_cron_mode backup = Backup::Manager.new backup.unpack @@ -35,43 +37,52 @@ namespace :gitlab do namespace :repo do task create: :environment do - puts "Dumping repositories ...".blue + $progress.puts "Dumping repositories ...".blue Backup::Repository.new.dump - puts "done".green + $progress.puts "done".green end task restore: :environment do - puts "Restoring repositories ...".blue + $progress.puts "Restoring repositories ...".blue Backup::Repository.new.restore - puts "done".green + $progress.puts "done".green end end namespace :db do task create: :environment do - puts "Dumping database ... ".blue + $progress.puts "Dumping database ... ".blue Backup::Database.new.dump - puts "done".green + $progress.puts "done".green end task restore: :environment do - puts "Restoring database ... ".blue + $progress.puts "Restoring database ... ".blue Backup::Database.new.restore - puts "done".green + $progress.puts "done".green end end namespace :uploads do task create: :environment do - puts "Dumping uploads ... ".blue + $progress.puts "Dumping uploads ... ".blue Backup::Uploads.new.dump - puts "done".green + $progress.puts "done".green end task restore: :environment do - puts "Restoring uploads ... ".blue + $progress.puts "Restoring uploads ... ".blue Backup::Uploads.new.restore - puts "done".green + $progress.puts "done".green + end + end + + def configure_cron_mode + if ENV['CRON'] + require 'stringio' + $progress = StringIO.new + else + $progress = $stdout end end end # namespace end: backup From 458f8c1f80ff20ba3d6e439c65500ed1c1d81ba4 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 20 Nov 2014 15:54:39 +0100 Subject: [PATCH 295/408] Explain why we create a StringIO --- lib/tasks/gitlab/backup.rake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 99e84f62c66..0230fbb010b 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -79,6 +79,8 @@ namespace :gitlab do def configure_cron_mode if ENV['CRON'] + # We need an object we can say 'puts' and 'print' to; let's use a + # StringIO. require 'stringio' $progress = StringIO.new else From 2e0bbe68cb713ec35a6a68c7a54f0ad881dbea58 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 20 Nov 2014 15:58:03 +0100 Subject: [PATCH 296/408] you have to update gitlab shell for gitlab 7.5 --- doc/update/7.4-to-7.5.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md index 737aeb9c1ab..c12becc1e14 100644 --- a/doc/update/7.4-to-7.5.md +++ b/doc/update/7.4-to-7.5.md @@ -32,7 +32,15 @@ For GitLab Enterprise Edition: sudo -u git -H git checkout 7-5-stable-ee ``` -### 3. Install libs, migrations, etc. +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.2.0 +``` + +### 4. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -53,7 +61,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` -### 4. Update config files +### 5. Update config files #### New configuration options for gitlab.yml @@ -79,12 +87,12 @@ sudo -u git -H editor config/unicorn.rb * Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) -### 5. Start application +### 6. Start application sudo service gitlab start sudo service nginx restart -### 6. Check application status +### 7. Check application status Check if GitLab and its environment are configured correctly: @@ -97,7 +105,7 @@ To make sure you didn't miss anything run a more thorough check with: If all items are green, then congratulations upgrade is complete! -### 7. Optional optimizations for GitLab setups with MySQL databases +### 8. Optional optimizations for GitLab setups with MySQL databases Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. From e683d9c087cc503cd8cb11214ddf265897e726c0 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 20 Nov 2014 18:18:16 +0200 Subject: [PATCH 297/408] Possibility to create Milestones or Labels when Issues are disabled --- .../projects/milestones_controller.rb | 4 +++- .../_head.html.haml => _issues_nav.html.haml} | 22 ++++++++++++++++--- app/views/projects/issues/index.html.haml | 2 +- app/views/projects/labels/index.html.haml | 2 +- .../projects/merge_requests/index.html.haml | 9 ++------ app/views/projects/milestones/index.html.haml | 2 +- app/views/projects/milestones/show.html.haml | 2 +- 7 files changed, 28 insertions(+), 15 deletions(-) rename app/views/projects/{issues/_head.html.haml => _issues_nav.html.haml} (68%) diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index d338cdedfaf..f362f449e70 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -103,7 +103,9 @@ class Projects::MilestonesController < Projects::ApplicationController end def module_enabled - return render_404 unless @project.issues_enabled + unless @project.issues_enabled || @project.merge_requests_enabled + return render_404 + end end def milestone_params diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/_issues_nav.html.haml similarity index 68% rename from app/views/projects/issues/_head.html.haml rename to app/views/projects/_issues_nav.html.haml index 1d2f3ed8118..1e14a2deb8c 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/_issues_nav.html.haml @@ -1,7 +1,12 @@ %ul.nav.nav-tabs - = nav_link(controller: :issues) do - = link_to project_issues_path(@project), class: "tab" do - Browse Issues + - if project_nav_tab? :issues + = nav_link(controller: :issues) do + = link_to project_issues_path(@project), class: "tab" do + Browse Issues + - if project_nav_tab? :merge_requests + = nav_link(controller: :merge_requests) do + = link_to project_merge_requests_path(@project), class: "tab" do + Merge Requests = nav_link(controller: :milestones) do = link_to 'Milestones', project_milestones_path(@project), class: "tab" = nav_link(controller: :labels) do @@ -34,3 +39,14 @@ = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do %i.fa.fa-plus New Issue + + - if current_controller?(:merge_requests) + %li.pull-right + .pull-right + %button.btn.btn-default.sidebar-expand-button + %i.icon.fa.fa-list + + - if can? current_user, :write_merge_request, @project + = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do + %i.fa.fa-plus + New Merge Request diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 4ec362b3063..8db6241f21f 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,4 +1,4 @@ -= render "head" += render "projects/issues_nav" .row .fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs %i.fa.fa-list.fa-2x diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 06568278de8..c7c17c7797e 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,4 +1,4 @@ -= render "projects/issues/head" += render "projects/issues_nav" - if can? current_user, :admin_label, @project = link_to new_project_label_path(@project), class: "pull-right btn btn-new" do diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index be638d7cac1..cd1e48ca976 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,10 +1,5 @@ -- if can? current_user, :write_merge_request, @project - = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do - %i.fa.fa-plus - New Merge Request -%h3.page-title - Merge Requests -%hr += render "projects/issues_nav" + .row .fixed.sidebar-expand-button.hidden-lg.hidden-md %i.fa.fa-list.fa-2x diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 03367b7cdbf..0db0b114d63 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,4 +1,4 @@ -= render "projects/issues/head" += render "projects/issues_nav" .milestones_content %h3.page-title Milestones diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 8263f7530a2..f08ccc1d570 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,4 +1,4 @@ -= render "projects/issues/head" += render "projects/issues_nav" %h3.page-title Milestone ##{@milestone.iid} .pull-right From cdf558682640876d950527f02d75f9f4532aa007 Mon Sep 17 00:00:00 2001 From: Stefan Tatschner Date: Thu, 20 Nov 2014 23:05:46 +0100 Subject: [PATCH 298/408] Fixed an alignment issue, fixes #778 --- app/assets/stylesheets/generic/highlight.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss index 4110bddf4f3..ae08539d454 100644 --- a/app/assets/stylesheets/generic/highlight.scss +++ b/app/assets/stylesheets/generic/highlight.scss @@ -59,6 +59,10 @@ pre { white-space: pre; word-wrap: normal; + + code { + font-family: $monospace_font; + } } } } From f68003560d96a9c2b9496e9091c809d1e17402e9 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 21 Nov 2014 09:59:46 +0100 Subject: [PATCH 299/408] The blog post will trigger a mail to the list --- doc/release/security.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/release/security.md b/doc/release/security.md index 79d23c02ea4..d335407797b 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -18,7 +18,6 @@ Please report suspected security vulnerabilities in private to Date: Fri, 21 Nov 2014 14:52:57 +0100 Subject: [PATCH 300/408] Add missing timestamps to the 'members' table --- CHANGELOG | 1 + .../20141121133009_add_timestamps_to_members.rb | 15 +++++++++++++++ db/schema.rb | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20141121133009_add_timestamps_to_members.rb diff --git a/CHANGELOG b/CHANGELOG index cc94e1af486..f321c9c32a1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 7.5.0 - Display renamed files in diff views (Vinnie Okada) - Fix raw view for public snippets - Use secret token with GitLab internal API. + - Add missing timestamps to 'members' table v 7.4.3 - Fix raw snippets view diff --git a/db/migrate/20141121133009_add_timestamps_to_members.rb b/db/migrate/20141121133009_add_timestamps_to_members.rb new file mode 100644 index 00000000000..ef6d4dedf32 --- /dev/null +++ b/db/migrate/20141121133009_add_timestamps_to_members.rb @@ -0,0 +1,15 @@ +# In 20140914145549_migrate_to_new_members_model.rb we forgot to set the +# created_at and updated_at times for new records in the 'members' table. This +# became a problem after commit c8e78d972a5a628870eefca0f2ccea0199c55bda which +# was added in GitLab 7.5. With this migration we ensure that all rows in +# 'members' have at least some created_at and updated_at timestamp. +class AddTimestampsToMembers < ActiveRecord::Migration + def up + execute "UPDATE members SET created_at = NOW() WHERE created_at is NULL" + execute "UPDATE members SET updated_at = NOW() WHERE updated_at is NULL" + end + + def down + # no change + end +end diff --git a/db/schema.rb b/db/schema.rb index 8ddebc5132a..68d1080b6ee 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: 20141007100818) do +ActiveRecord::Schema.define(version: 20141121133009) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From 80db117a339364b7a7c11d44f8fd7f0cf2a2a12b Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 23 Nov 2014 01:45:44 -0800 Subject: [PATCH 301/408] add preliminary 7.6 upgrade guide Add preliminary 7.6 upgrade guide. Makes it easier to add upgrades as changes are made rather than trying to round up everything at RC1. Initial additions: * Nginx changes needed again in 7.6 as they did not make the final 7.5 upgrade guide * Suggest that user sets time zone (added in https://github.com/gitlabhq/gitlabhq/pull/8015 but missed in final 7.5 upgrade guide) Replaces https://github.com/gitlabhq/gitlabhq/pull/8124 --- doc/update/7.5-to-7.6.md | 114 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 doc/update/7.5-to-7.6.md diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md new file mode 100644 index 00000000000..deee73fe560 --- /dev/null +++ b/doc/update/7.5-to-7.6.md @@ -0,0 +1,114 @@ +# From 7.5 to 7.6 + +**7.6 is not yet released. This is a preliminary upgrade guide.** + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 7-6-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-6-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.2.0 +``` + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 5. Update config files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`. + +``` +git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example +``` + +#### Change Nginx settings + +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting + +#### Setup time zone (optional) + +Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it. + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +## Things went south? Revert to previous version (7.5) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.4 to 7.5](7.4-to-7.5.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above. From 44e53aefd471081759d9fba160d9a651d520626e Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 23 Nov 2014 02:28:34 -0800 Subject: [PATCH 302/408] start gitlab after mysql tweaks --- doc/update/7.3-to-7.4.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 3f471500c82..f6d6d1e1eea 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -167,6 +167,10 @@ mysql> \q # Set production -> password: the password your replaced $password with earlier sudo -u git -H editor /home/git/gitlab/config/database.yml +# Start GitLab +sudo service gitlab start +sudo service nginx restart + # Run thorough check sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production ``` From f4038138fb250bd912d593ca83e867efb4ced186 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 24 Nov 2014 11:30:07 +0100 Subject: [PATCH 303/408] More explicit wording of the documentation. --- doc/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/README.md b/doc/README.md index b9aa12f7675..896224fe930 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,22 +2,22 @@ ## User documentation -- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API. -- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system. +- [API](api/README.md) Automate GitLab via a simple and powerful API. +- [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. -- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI. -- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project. +- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. +- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. -- [Workflow](workflow/README.md) Learn how to use Git and GitLab together. +- [Workflow](workflow/README.md) Learn how to get the maximum out of GitLab. ## Administrator documentation - [Install](install/README.md) Requirements, directory structures and manual installation. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. -- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier. +- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. -- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out. +- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Update](update/README.md) Update guides to upgrade your installation. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. From e0c870c0451789318e46ff5cb2b44a8d7d555f4c Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 24 Nov 2014 12:14:07 +0100 Subject: [PATCH 304/408] The release manager handles all releases --- doc/release/monthly.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c50bfc21f8f..e81ee12af6c 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -8,7 +8,9 @@ NOTE: This is a guide for GitLab developers. ### **2. Release Manager** -A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. +A release manager is selected that coordinates all releases the coming month. +The release manager has to make sure all the steps below are done and delegated where necessary. +This person should also make sure this document is kept up to date and issues are created and updated. ### **3. Create an overall issue** From 4e3bf439cba782bf7d3ea326f0f6f5878166f0e2 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 24 Nov 2014 12:14:19 +0100 Subject: [PATCH 305/408] Establish ownership of security releases --- doc/release/patch.md | 1 + doc/release/security.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/release/patch.md b/doc/release/patch.md index 6ed56427e9a..ce5c2170302 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -17,6 +17,7 @@ Otherwise include it in the monthly release and note there was a regression fix 1. Create an issue on private GitLab development server 1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier 1. Fix the issue on a feature branch, do this on the private GitLab development server +1. If it is a security issue, then assign it to the release manager and apply a 'security' label 1. Consider creating and testing workarounds 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch 1. Make sure that the build has passed and all tests are passing diff --git a/doc/release/security.md b/doc/release/security.md index 79d23c02ea4..a7fb57921df 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -14,7 +14,9 @@ Please report suspected security vulnerabilities in private to Date: Mon, 24 Nov 2014 12:06:42 +0000 Subject: [PATCH 306/408] Bump gitlab-shell --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 276cbf9e285..197c4d5c2d7 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.3.0 +2.4.0 From 335320a2e1df302bbf0a16374ca7248c43562ca2 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 24 Nov 2014 13:26:41 +0100 Subject: [PATCH 307/408] Formatting and sequence of contrubution paragraphs. --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71435bc600d..a403984ed28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,15 +80,15 @@ The **official merge window** is in the beginning of the month from the 1st to t Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. -For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria. +For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria. -**Please format your merge request description as follows:** +## Merge request description format 1. What does this MR do? 1. Are there points in the code the reviewer needs to double check? 1. Why was this MR needed? 1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)? -1. Screenshots (If appropriate) +1. Screenshots (if relevant) ## Contribution acceptance criteria From 0d5c8500b843734daed0da4244862fc584b7fb4c Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 24 Nov 2014 13:26:41 +0100 Subject: [PATCH 308/408] Formatting and sequence of contrubution paragraphs. --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71435bc600d..a403984ed28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,15 +80,15 @@ The **official merge window** is in the beginning of the month from the 1st to t Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. -For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria. +For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria. -**Please format your merge request description as follows:** +## Merge request description format 1. What does this MR do? 1. Are there points in the code the reviewer needs to double check? 1. Why was this MR needed? 1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)? -1. Screenshots (If appropriate) +1. Screenshots (if relevant) ## Contribution acceptance criteria From 282ca063e51bbd115e269f1d28894443bff24b33 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 24 Nov 2014 13:38:02 +0100 Subject: [PATCH 309/408] Definition of done added to the docs. --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a403984ed28..cbc52f3eee0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,6 +82,24 @@ Please keep the change in a single MR **as small as possible**. If you want to c For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria. +## Definition of done + +If you contribute to GitLab please know that changes involve more than just code. +We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html). +Please ensure you support the feature you contribute through all of these steps. + +1. Description explaning the relevancy (see following item) +1. Working and clean code that is commented where needed +1. Unit and integration tests that pass on the CI server +1. Documented in the /doc directory +1. Changelog entry added +1. Reviewed and any concerns are addressed +1. Merged by the project lead +1. Added to the release blog article +1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant +1. Community questions answered +1. Answers to questions rediated (in docs/wiki/etc.) + ## Merge request description format 1. What does this MR do? From a0a3eba1970cc6ea23cfdfe5750971215b2cafd2 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 24 Nov 2014 14:59:54 +0100 Subject: [PATCH 310/408] Explicitly mention patch releases --- doc/release/monthly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index e81ee12af6c..64a8bc98344 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -8,7 +8,7 @@ NOTE: This is a guide for GitLab developers. ### **2. Release Manager** -A release manager is selected that coordinates all releases the coming month. +A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. From ef944e83ec8c439350df03c3bb9b5bbb3f68f406 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 24 Nov 2014 16:21:35 +0200 Subject: [PATCH 311/408] Git hook messages: wiki access fix --- lib/gitlab/git_access_wiki.rb | 2 +- spec/lib/gitlab/git_access_wiki_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index f7d1428deb2..a2177c8d548 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -1,6 +1,6 @@ module Gitlab class GitAccessWiki < GitAccess - def change_allowed_check(user, project, change) + def change_access_check(user, project, change) if user.can?(:write_wiki, project) build_status_object(true) else diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index d8d19fd50f0..4ff45c0c616 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::GitAccessWiki do subject { access.push_access_check(user, project, changes) } - it { subject.should be_true } + it { subject.allowed?.should be_true } end def changes From 8f4353a1644c2893b7747e2b55c7a3ad190b7b76 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 24 Nov 2014 20:56:30 +0100 Subject: [PATCH 312/408] Fix spelling mistake, thanks Ewoud. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbc52f3eee0..9da89cc2107 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,7 +98,7 @@ Please ensure you support the feature you contribute through all of these steps. 1. Added to the release blog article 1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant 1. Community questions answered -1. Answers to questions rediated (in docs/wiki/etc.) +1. Answers to questions radiated (in docs/wiki/etc.) ## Merge request description format From bc16f81321b69b829c432af72840a4529ce3228a Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Tue, 25 Nov 2014 11:18:56 -0700 Subject: [PATCH 313/408] Update time zone rake task for production. Resolves #8387 --- config/gitlab.yml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index bb0ffae0b70..7b4c180fccc 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -35,7 +35,7 @@ production: &base ## Date & Time settings # Uncomment and customize if you want to change the default time zone of GitLab application. - # To see all available zones, run `bundle exec rake time:zones:all` + # To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production` # time_zone: 'UTC' ## Email settings From 6818c96db02546a61730d5cfd799dce9e2a85c16 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Wed, 26 Nov 2014 14:30:14 +0100 Subject: [PATCH 314/408] Selecting a branch is dangerous now that we have rc in a branch. --- doc/install/installation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 5dd9388eece..b8d9133ed75 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -6,9 +6,9 @@ Since a manual installation is a lot of work and error prone we strongly recomme ## Select Version to Install -Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below). - -![Select latest branch](https://i.imgur.com/Lrdxk1k.png) +Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. +In most cases this should be the highest numbered production tag (without rc in it). +You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar). If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version. From 3e60dd7cb510fa00794701925ae0776332c09163 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Wed, 26 Nov 2014 15:49:25 +0100 Subject: [PATCH 315/408] Change it earlier as well. --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index b8d9133ed75..263259bc2f9 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -6,7 +6,7 @@ Since a manual installation is a lot of work and error prone we strongly recomme ## Select Version to Install -Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. +Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the tag (version) of GitLab you would like to install. In most cases this should be the highest numbered production tag (without rc in it). You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar). From b82a205b740840e2a7d0fa3eecf3e361ca73416e Mon Sep 17 00:00:00 2001 From: Marc Radulescu Date: Wed, 26 Nov 2014 18:51:12 +0100 Subject: [PATCH 316/408] added office analogy to help understanding of gitlab architecture --- doc/development/architecture.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/doc/development/architecture.md b/doc/development/architecture.md index c4813d22eaa..109b21ab2a5 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -8,6 +8,38 @@ EE releases are available not long after CE releases. To obtain the GitLab EE th Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical. +## Physical office analogy + +You can imagine GitLab as a physical office. + +**The repositories** are the goods GitLab handling. +They can be stored in a warehouse. +This can be either a hard disk, or something more complex, such as a NFS filesystem; + +**NginX** acts like the front-desk. +Users come to NginX and request actions to be done by workers in the office; + +**The database** is a series of metal file cabinets with information on: + - The goods in the warehouse (metadata, issues, merge requests etc); + - The users coming to the front desk (permissions) + +**Redis** is a [communication board with “cubby holes”](http://cache3.asset-cache.net/gc/52392865-mail-lies-in-cubby-holes-in-the-trenton-post-gettyimages.jpg?v=1&c=IWSAsset&k=2&d=OCUJ5gVf7YdJQI2Xhkc2QMDTqXzgg%2Fa7CPCCcA9Ug%2BfL2iMdhkcAYaLLAievbZlwJI9YEbpjb1pB2Fh7Fge3%2FA%3D%3D) that can contain tasks for office workers; + +**Sidekiq** is a worker that primarily handles sending out emails. +It takes tasks from the Redis communication board; + +**A Unicorn worker** is a worker that handles quick/mundane tasks. +They work with the communication board (Redis). +Their job description: + - check permissions by checking the user session stored in a Redis “cubby hole”; + - make tasks for Sidekiq; + - fetch stuff from the warehouse or move things around in there; + +**Gitlab-shell** is a third kind of worker that takes orders from a fax machine (SSH) instead of the front desk (HTTP). +Gitlab-shell communicates with Sidekiq via the “communication board” (Redis), and asks quick questions of the Unicorn workers either directly or via the front desk. + +**GitLab Enterprise Edition (the application)** is the collection of processes and business practices that the office is run by. + ## System Layout When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git. From 8ccaee19792ac10dffd7a86be0835f2ea5674d0e Mon Sep 17 00:00:00 2001 From: Marc Radulescu Date: Wed, 26 Nov 2014 20:54:42 +0100 Subject: [PATCH 317/408] replaced hotlink --- doc/development/architecture.md | 2 +- doc/development/cubby_holes.jpg | Bin 0 -> 132815 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 doc/development/cubby_holes.jpg diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 109b21ab2a5..68c813d4339 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -23,7 +23,7 @@ Users come to NginX and request actions to be done by workers in the office; - The goods in the warehouse (metadata, issues, merge requests etc); - The users coming to the front desk (permissions) -**Redis** is a [communication board with “cubby holes”](http://cache3.asset-cache.net/gc/52392865-mail-lies-in-cubby-holes-in-the-trenton-post-gettyimages.jpg?v=1&c=IWSAsset&k=2&d=OCUJ5gVf7YdJQI2Xhkc2QMDTqXzgg%2Fa7CPCCcA9Ug%2BfL2iMdhkcAYaLLAievbZlwJI9YEbpjb1pB2Fh7Fge3%2FA%3D%3D) that can contain tasks for office workers; +**Redis** is a [communication board with “cubby holes”](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/cubby_holes.jpg) that can contain tasks for office workers; **Sidekiq** is a worker that primarily handles sending out emails. It takes tasks from the Redis communication board; diff --git a/doc/development/cubby_holes.jpg b/doc/development/cubby_holes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..afbb58bb950f85f6cb9ad722426158e880ae0a70 GIT binary patch literal 132815 zcmbTddt4J&*9IEvg{sAAd0|q#WDn3TDP5qy?J^710BBauj-lcppGlcs^ADd2kv z_y$L}lSmfe_fOy(e1=5wKCo{$^}9VJ()XlOqzmBnF!1{)(izg1q$4Cc=`1Od6a!xQ z1-!BzytWUV6G+V}vNW1@i z(oawOfBpJ&k^k#w|NA-f8`8Ybr-XcVcGb}!zIcqjJp=>Vcvnf-jel~6D^y$;Uluk(i?~|s@n?8TV=3O(a4xO_2Dty6~ zUvEA5eC4;rHP(k))T_3h{^iolSqp6zEnc#EjosR>*Eu?EbKbtgW%qY`_IiB3Z~u{_ z-p727pYRR*>CD-nbHO1Ikx|hxjM%uu%U6<;uU@-;`_A38^o)DIXFkk*l=qnXXa1At zC8cFA%3r>!cw1Xn-@yOt?|)j`+B-UhAG*5566wI;kZf2!q8Zmt==77Q!T4!hQ%F<) zJM{n0am@qc`fS>?snaYzjcdwhF`ouLZ`$+~n`g}5b;#mWxYbu%e*JvGx3?Y?*UVhG z^|0Fd^e-*57Orv>uhx7T+P@?F|2MEp|EH1tcVPcJu5Qv7Q>TE%n>vpKk)Ev?47ri@yo zC45m0e+XkEJ&?3kXN+S-o&K8On+!ygo^6OQlU%jkzkXo_w>2S{F;Jl(;$ogimM(rc zZkMRshQophBP;e-KKg|6!c%Ngxp&J5yCr!U-AW58dfQCm(0yImu+U5@^tD}=IrCu) zc6CExb>maGksDBr!S3P5RwQHKw07dNYCBgmDc($agXr57&!NfVW|BWU%S<{~xU5Mm zfQY=R!+(XFNr!kOg~*g>Cb`*jA?q>*VSd0pfiOUU|+#PeV3h;~Y-ne@tm zrWCTZ-Bq6nh|kTW1WAD<7E1c^g?Pq)>IWBO%|S-&N4jlL1@5s zAq31Q2D3MlP)}En2MW&kHrQD87CbOyvWlf?Ts8a#X#U9{A^?l+?{)scOlpM12pTtG zZ9HqUqM@y%@W@PRP$4VqcJ+I$VT74(w>are*^-=2;v~{2TZsl$s?4M*MJz3X%|IS= zZqn4>HqD`=1nekBBQ&P()YM$(AKY{)z}`XBB%`2s{i#@fBKfd7O%C;51L*2w8DltH+>1)>EQY^o&wV>56Ia zg)cH;(_Ti#Va<$uansg1;~wE9_i5)UZr+PTl-cmEWoV+5b~%37gEzsu#5|-<*Ejq9 zDh`W3pef4v-n62W($%+G(X;!nX*q~yKgFgft~^{98xWc<%{QI5-Eb_=?osa-UXJ?f z!Oho?h2^m>+^)JUtZVpjv*Wra)~EhHabld#ePSlf*SVH|ZzlOcLZc7$Bz}Kum&jnX zD}o*#%J14}41{DwJB8umm&_g8O1NgyTkr5f_${Qb#f#0P&nL0VMk%ohE{e!h6^^|g z%TCX}d#nI?(2(BPvWW0N6lPLqfwf6%+5*?CHIvSmN!@<^vLyFSa=6r?o@XgH>c;Ye zz=d?8i`e4)kZ8&WU!k6iC6lk#CpeI?xjem(nUroOl?oena*PvdI1#222porg^jWTM znDM$caI4MUiZBSW7HmB?}(ZbFYR{R3t_7AY~j;rvX zh_Ilwzk}4f;SVapk7(g6kKki9*b)}PrH+RNP|y{Vqh4gAiVbe+Ccs}|j%Lyj9h0hy zsOVK}wVAX_2dmlEgJ#l!O5p1bJYxZH5kUnF-D)P4L3i=}6c?cmUuTEjg~H>w~bbj4C{ZSPE$lkw>AEQIblmjm-!s znrgK--Mv(Cdi~vnvlrZt^n`;{YdE)hE{X4V-?X1B&HuLBOj>B_GLyaaLr3D9s&j*o*eZ8U!$%e_k-2|Kg{l!Wc zNuc2VX3~3j9YHX`?Wg6MNtf&xbP4&bnUw4-dhSz~63V`jDY)*sRgBCFhDz-kygX}N z;OmZ*=?cP?XaMl@C3?$Dnh9YkITk{&S_@#`56Q&mba1t#5|hU*b)&?iYYZ8wWbgq{ z=Y&qwU(Q=(CRHDarfFqXZE1)hqMAMS(U4bIDbP+nL|$;N($xET=xUop?99`1(U1kx zFM^&2>>R(^i^ZTBZ6+HxSiDLvSE{aKcK9=QTjC#z1>uN&gYHzz^l@sh@+%Xwq1nK# zoFz*IwoGPwWrUezC73?EWRi-Wpr~)a?;?oHMl^(Z=I-gO>yLR~;oj?PIK^6<;I(UC z?*__?JD&dCZWX^TTyv8=JGFo|bJlT=F@fcURJ_Wm%I%5J^fYVZWwsx{dcwZNUqCX9 z;Hs^QC0-V$8H9ukDYpST*!^0Cd+3@10pQZK_=qv;u)Z>kmC>Vi-Q@sD+Y`v+jwj3{ ze!h`BVE>_-JQ6@k49KBSP>PqP>`Dg*+c&|U7iznuEmENTA<9fjfhE+>Q4X>@M~8pI zv;gY_!!?`TS1^~NG%1KC*yo*vmAj-47IKzd$3zy5O_^WXyQTK1(D8IU%7zZfm9(AN znl*mE#dlqASVs-HyXha^o-ntkVbMC_o}f;C-lfKMQswBAmMcmuI3Dt2`jkr}3~LG~ z3b=VQTObq_!a;L^2f3I@{dc#)TDp((a!9I@@*567;{z&V=Vj;_jLp9!Ai_$#UYSWe zB$cxm;?>0&c<8l{&aFA27tExCX406bqsl=JKj~s?#gH22PP(yFTha1yrl?j#kuAp0 zB~i2ld<30Ev_s;XaDUoDcmz75ng*Od1Gxsx*E!&FO5Slw2^E{)Eg;;0UBH(SPh>u$ z@vxcHdq>d(@NnMlFVm{hZMNkL@y3jD+%*Hl{U^ukd7aNPPhc#L3=gFbAT?@(h=iQW zf(;X9(iP`;5s~Z#=tIUm05eR1qdW4e008(n|D_Y&&LF=Z zpoIGToSCG)9h_mLVQ-LfM`$km7P!+^iox1+E0<{F%z=wbV~JidVwft2wH7UG84(}U zTAgy4ap!A^?}tDC3{*${EI>b8T@Se_D=DRe!tdH2{WA3G%*pEXg@wMQAqjz_Eg6uV z;6RwJbsO{#=La?Ks_tuMs(o!{A8?5S>6v>=@?(hX&{jvdOC?3Ftu8NC%iaKU@$GvM zV{_?6@FxK9SIg(E5U-xiCT?M3e#E5@zmH|QNd+}4(^sC z0gHvD$S4)fLD!=c;%kZ(3fCe!0NC{s4Hy5MrcRPMsg`6YfA60S>4@!ZuzD+495;rf zdh)~I*Urk3DhEQF9MePmaY3bk-4;sg={oX)!lPA8Mn{};E$ZvgFwKmpyI2o+~TXp%-kpS05L4Wbq@LoV+s;dVX zwNie4%5r;37e!s>8e5pa<4mZ~LMJmxi`z?#_|)ls^HOWXn;B1zK2uo*$GdC_AE-zv zxf9kHdcJZ@=YC}Ax7oA${<%F^5};G{Jmw_Y8*G7B0fPASSba{S+rSHgK{U0QNwP5& zn`^)-gdZqhuvVay8c5AXf8p1dNwX{W(Ia9Xp9t_rMPgblb!kYQB@Iu)oCGV~t>^ZAP1j6cEQ?but0TD0vih6Qn*M2ex`= zC;t0Ou!0a+@&gG4{lO^rw?okk*RCFqkVW1lY>)Ab^E* zifV`~^4Kc^!Rguw-a$3fJq$;9ZgDXLM^*%vv(Ymm)b8m=GpTk3(L^BQb^r~dscMDl z*a{rD@{il$cGf=>QW&ggtBnI5?U;?=Q8m{z(3ka4@4?15E(E4{0z-}L{V~d@v!_r$69fYJX3mWm0P?l_CU}Z?VgWR|`7DTi zUv#OXdx$FNMWg`lf|@OSd9gfja|=n!Xe2Kgni=V3t1ulgQTA)dNfY#?35iFSDUc6w zvN|&AiHukn;(SssOP^LGInTbHm-sFe_jelYC7%&QHEQ~}8+h35og)KjwkPww>kj2L z783To^~xqI^r9Lb6`Q_g?tWITVgE`@V+Lx<6UW#eo?<>?=n8AA#x4o0&oeV=U}q}* z8+BLnW|7e1x_k)FfP=TgLP7v94`btQ7zNExD#S=0{LteK%!Z~vC6=7ie)tqcAIptg zeV(ezG0SbKR~t0o1kCUq8O%*GD!jvLX z^804ehZ=DS9@;pR{3$e~!$uJY+HWR#rFu$(-Yk#bTb}4GbnJCj9)so4oF0lsOvD@6 z!U6aytt{p(G7Y^ct&MhHsrGKFD$Ds@Petzxi~8;B1lo#NTN?hrW46!c5;G|>H+7)F zEkn&JJhCTrj3e(!@Bezpx+a8WH0P6KnR6r1Q&=g|VqY4T;(W}*qb80P8_D1MDh^M%hcGHVHYDmgXAL_P`F z9(J^r_*$fFerc2-x5-yGVS6=~eUYn`XUHj0Rq)Z3$_nHvhz21seXoQ7X6S;vQ?-?Q ze6Bug;U8->lb#Q2>(?E>;aiYWMRZss|8e_dE7ip1Xx8kT-SI+F5vNa<<=ts%Q{>)^qaCm zd&#w#s~aAKawZV#uHinX<-foZ51zEvPNV)+rK0V-+RzgG#H&5z@6|tLJ?||)qfkY? z$jk|IVT$TAiVYFb{*u@Y96D+pIk%R81tahjS{0=Jfql((26{_MxdtL_6|tE4nTB@- z+bnKc&-g`H2Yu#lc`NXVqC#L{iU9U#X_S5^o8UW{9A4`wW>W5XJPlS0FyNU(7m&@( z+l8~!C7SK)kNMq;KksY*h-dNZ!)jl3D56>s1e*!{BvXQ% zQ~Wcl?tQ(9eMwZ}3>$L6obH4_meXP>^Pl~_2R?PSdlirA@Dn1N)Qtzdt6tmzz$eFw z)yENOvDb3!2gVzN@IBmC#u>aAf#IyQ##pGrOxh0qtBhxnm)EK{vFh3)%%oX|+vL&d z$s76Ur4JoF`ZiK7H5G`hArWKg8^=1(-c8}dlD=VbJ$=)wZv1w z=&WFF7I8ct=V4lWZDug=0z|5sP+hXpZ!ZKZ!sZxFAUwdjCv^dPT@6i5<+uwVun-=H zmOSM-bA|}xYJEwYao)%q^G|% zxo(ct0uX?OBDs>U!`^Ih8V)i|ui|sU^x?62I6UAaFsQB%|5reKg`cMH^n95!(@Yu^ zD3PBgfR{}%lZ3o)k|@0}=aX2KQN^Usd)wh1s?>+|+ErmDihnI*o3PaGzSDZ zg^)dRa6%vkvXx9OR48drTk9q39Hbq`Xw96AaUO&;(yrw1&EC1wx!f-@MLSAp^|cZY z%=$7~TW@2#GO;eYo8J9&Lh!=Zf7r;sQ2B|Iv_}aZllKomV)^?|lIAiw2R(rDfuegW zLJneUa(5-9(IgTuMN?$^NxLlO*8ILOkfvDlWPbjM;i2x4ho;c-#&>P_kMEw9x?NdU zS&H25j=~~E@9>%UGbm&OSz68QhpkUAQcQH(`_j}SwJxs+erIG0e%FboRDLm4LN?6_ z&31E%0Jr1w8dvDlk+bbiZD|MR*K zp)j=2{rmCo?bv~JE|t33VzP#noj^ ze<|@>lHK4wnrQrd#0FAP^cs@u&TsxJgSdyo|^{Cs@rhzlQTx#bhxE!me zT;v615wi+U2~Bnnh^_+bvMH(k{NM7vD ze}1{2J=PW%U=;bia-#&c zy`2VVXEVMtQ{88eU~k81$fwFydcAu4ym?PR3f z7dyuB7(e}vLK9#bB77nPv(hgfHIvS>ljc&}jm!kI};#eIkaNSd7!?&g9|# ztuF1)7Nng?_uMQS6!uj_p$>Br>j*wwmDV#5V^Bh6l+ zie)8_9jF2*(y*ky`Br#>Y1;_gZA*FW+`Xq)#{c!tk+_BVcq)$pn}WWz=>O9NT>Ns3 ze~CpEki?a1W)I5X)72{Pqz)+8bWmc6^R zc<8@})y5s}GvM(tZf$)0i4%kkzTv$=5PHb34|9VWHPE?FBIfjC8p;hP1^x@144r-X zdP%?rSRANu$-rt}2h|3;a}Ke60q$Awd)O%fTV85z=%EkgHooo|KW&&%6y;!;a_)~f z@;4U3&T4lpj!=mz+w;KLv_-y5`?S_V-Sy*V4wUTI9==m82rVn=VPr41j>vGXPm~6;gvVmrTUP0Me@o zK`ng-taYZX{eAW5K3!4I+h+nNg2^rgtb-21*GNUzwuNa9!J#k5o^}wnl_IWBRlPC< zcD|7PifJajMmgX0F~6^_O!?cz=W-!3YSmA*2o@RHC><4Oh zKsD&Botlh^l?LGkFz)FKGu4j~=qNdh(`c(?jQ!DvNG!h9-i8qrTG8#cRzkbIkl$HMr%fs`VK5B>r#dFsc zzwLSa+-O;(`Ne-{V_s!W?+v7^gkqdkFdnTO97Id>wf$VBwUoVxxdO;hesawD!N*|O z5_2f*>XrLK`V7nq68}*>?;P;Q>xp$aV03@TbiDL6U?MIO9piku1?E6rn2;kRT$Nh^ z>-~cw=V%otxo;@*0(P5RSzX4HQ%A?;oD$I$diQYnZ*VAd(PiZq{b2d6T6aow$nZ7a zeVgy=+;)LrY9{R;Y%vP4$AHJDA5qvH8XpW0&NJ)VH#odnOv0?01A}KCyyvR;{rcFNSky>9#?rbHu54;6bz05 z@VDDMhjEPh>@=QBYz|-%^y}3SNW%x{ogOHlc(Nl+nnuZ^7QhQ7P-+!4@N*~p7s1J6 zSX_yW)!EZZ;Wn{i82CT!olkDxi5kmfBb%xv67bO4`ZKX-ZFwcfUAFY}9sRkbO!=G< zOXD4((yxK(%oKsoIMC#wCFFXLYRiGByI;e<<|rx)NGQeGb(zgsZ5qsiR=en_LeOn5 zxi5N2v0{d?O-pt6IYJ9wq0jh_rYYmo%jqZ>gFAwU7|bM}Bu13y4WK=Wb&3xht;!!= z#+~BNnk4U%N5w6QN(kht%lGX(vEpdm>0pJDH~3e^h>5j+EQ)>A_eHS-=gCNBdY(Hx zIHomuU`8VzV!4$cV|34d6w)c_lO$}^{!m#`_F7*vp=(IkzRD-VQ}&Z=yNqkl$@iH= z+KTE#)7I&s2?1%D-M!22P&@n~d&8o2WsXddjO)P3kOadH zlffJdpaVHNafg^SKezBa=j&8mSW6;Z9CJV@RDDnq?M;rf;{`3j5Op;giy_~C2@LxM z(ZT>^Yis}H0i=sEpAVbXav?lq%n?TIJ(OFWjqDjrYv6IWVk9&-WcVRq3a_J@0AiV3 zh#Ghia*k>fY&!?_R?F(D9fhjxrsdiB^%ucnHoUqGKO;#OuhRV8#H#ft_#*WI3Tij~ zkquA)4)Ri0N-6x=o(|9i{GSE%rl;p0X8IgDbkMV~{I$!vv)j8r?DVed%)ag0 z-1Dbl8Ifh8vpQTX3#*eKU@;mZD_0&JThe-7#~uasB1}iYLz+9GFu0p~JVVgIazuL$ zzZ76q;Ev#iJ5;ETKy#vIPJTE6eJW_qjavrKXAKDjiKC)(Wit25AYl(O!HYVBo^911 zEvJnuV`Ga%8G#M@4k*hw`ESI^5yzSWnF(1qT#w8L7yZL20+bVWYv_kF1l1aX8xbDC z?;b;n!R0%7w|I0X|NS&zNdm=*z61F{1x&^#>_6Px_3QJ>pZ}KY!+JD`E;>k;nQdY% zLol`^AuylG&=AF;12hKz@T+nlSW!m%pug+Gm+(`ADxbM0=sKGCxSRhSBOK7D>+kbY zVHoRDL6d&?Wln@b2-+4Lcz{tq*)hlFch-1TM|{D79zU;baSnCsPKO*-(D~)uKv0}o zg$v|LA$Ul)bAZ~gdoVLe)cwY57FF^Vm>DW{y zY7{o=6OexI_7gyi;6U$K{snLAe|)wzL-q6Bk+4<~H{Gaji(tAj_k!-0f^sdKY&5+N zo%jy2bzk!i9@udnRti2n>y_ilQd%8-n^e#dRxQv&6W|{}y}=30mDNnqVg@2`TWp~jIYi#o*&@#poBlb~FSfX@kQz+pIlH0F=!v-KdBGa5|oA;57 z6+ox40MZPFvmf7qdWiEYcbMFmv<@-`k0779@9d*+v9DnYd@3QFh~;HA1ty22+um5) zCcFRTBYCgg*Sq{p|NKpR-D3NKw(jKfDD1>wiGkL>t`7*%a?qjoiL<%(iYq;i?8AA;%HPNH3siulg|`2Di&<> z``Tep-Zs(pPoGso`|D1im}pIHrW~ru{pxG9N%ChN-jIL61CJdR2`n(JH_*MJ8MQ=W z5MV$q9;i^AYkDifVPy0wI~JwRG{@0W!~SJad?0G+Q#oxqv;hzh2Sba9iB7sINf= zrr-#5PZ@wxK{6e5vGD4Dj-HMj&LtN|XIE@&d`PJN5ydtgAz0D!o(z+y7yHiilSuo9 zK%{$4jDv_zBg(9AGZGw?B2x|vxyCKgVo*P)o1Z;>6dBish3n)wAbLzW1_2c^S75PS z9>dWpi3pk_97qUDz-p+uNx%iSQ<4pWj%TQkzA6jStW%3{kZ{n(S&w{%M8FRJyl+R4 zCkPp5@FP&n2qt*xO@b>X`k1g=1~-x?&PVYTiW`9Z?dJh;LKKuUsdT1I$b-$KF9ogD zG08Ph*=P$HO=EZ}L6wIJ%Jrul~0gJW7WPf~8u|;;?$Z`ZI5i$XobYqo$r=w`1(b63@Mu3Jl7TjFz0WgBB ze=&|oLKDRHp|CNsL@6*(c)mtyjryaPSU@(2G-}5L2MkaUU4i6f2{~kYTy^(dqC|gA z(EZD~if&g?Wc-Bt>tq)&2mhQ8*u$O)tSUP)_3$L#?lZSA9 zlwX+VanOWSRHi=(S`CuYp@E%7GwGDf+W3c~NV#EY{X%G56D6)X+{oJnnqI>Ds_oF4 zpeN{^gPmViSKGMTbU|otQAU%`)6luV*wq5D;Js0pH3HWX3HHkIip&k$g6UljD~B|WqnXu0 zIpMH1IrCG4!ESU352Du^JL_dIY8fQxQ4?hoE!pkf<>fTgVsXGCrkr_`)dC6;beWHt zR8r(N-M7U|k{U%L_8r(vHJ=aKeJcG<^}|Ry0{k}hU)R|Hd9Xztwhw0t!+^{Jjf6vLqTH+5dlbUU+gFT z4r--_a4`D?dJ73W3Zm2WgXq`=*4IS)5*qN_AkOTM_u;pGCLioaG*nIf;r&Hue1@%s zhJ2NO-PL{OD0A5y7xsPvEZQY1gMPTRUqiW$giz))zj?pKvX5ul!V<7HYbdq$;${!b z$yo_Wpsu!l>Z%*_q?AR|7cbheNUCRKHh1K={xO!JF-)}4#5Lvdk&5c%{k0$@&tkO% z^ERYqmmyonSU$TQmqP-odeW%pza-v1hS4y}rMVy<)##+;;&`}ja<8zIGMZeYg25gDzcdi6i_+ps4L(k-&;57CR=)v#1zi9N1ip!;uS7&V=bv(!5`}$v{+sKL zR+Q!xvO)>6XtF;$GvRlg&8Ut$MV+h{B@!&;EOj=qTVIafD+Zc~)A;t5(GpJSeE@U2 zXy=IL-?PLVC6CM-Ez5%y2Uzbo&DDEeDo|k6-h$-1j^p=hBA~c~(bUj}^w;#zxLA2u zLBS!=7ktUO5X*Kp`H9Zm0}s|oBt3p`_*?rtEgZgIn5idN|KU|XKs zATN6W&g}*R&*M;d7t(5u%s%$ZP> zEzE>4hDl?HRO$-Xdh5L|=)1Z^p;0>Iz`KULC22bndcr#K)7a~t!O2fmQQ5K@feeRT zjqyHzFzm(rYp&j~o-*fLj7x6=V_`Z+=B6;-`veC4AN?b0S@Qv5t~dS6Jgv5P!%H$A zH(e->l8EM?>s#8ke)CPID$rE3O3hB%JfXRW#qy48l9iJnCsVx!bhIlPeI@E@z7L2}10hb2Qvl zz4%#!1(xi(i~R+=xmv2B*U-_`Y|N^POY1I|>bOcv4flubukrivnZ`j)dMfoR=&mR517GHHboDv+WsIe! ztuffIWmV_M=sqlN+i#GhP?K_d_uReT@}%oIiq*iiYEQQRV)V|8dg5L5H2cX>Z}-+? zU8{R{I!^ldR;>$kOG{g~-~l&OKOyuqa!Qd>7+WaRCu)$SC^!*$&c5Qh+4KcQdOhPG z)mz%MF#wgc>!oc#7ZxXf*63OWbiD|aj>*oq7e`Xegs<4DcUN=8V46YxDhd!No%Ty z@w}(NKnYvoFZIoG+GnnZo=SfAzi*JWC!=A_c0-vG}^h$oQL6M*OXvT_ZxiY51obNvX=pF)ubjjyE00QjAFqH}+F zC+Av&nZ!Jz==ycAWw%8d&H{}I*WdrAXOrjqZ{;~IEo6tilD<>#e;b<ea@ShaosD#Ee2Hx62@EdmPVl^?7>ENH-9bY#yz#klDG0n<14C#2n63+UiS` ztcA6tki7=07D#LpM|T+^GzA!fqQSRCy# zEOe0GrFx5m35%Rg0Jh}OIttbvKsz?Ud&b&cuXpGQ)w8P=dg_%pZw^6rw_{lB!VpQU zLD+D5M0>W&%ib?NoGC<(LrJ3i35F|F^2bQLZP>PXX?vzZTH$cC(RkMO#d7%4E%PKYS#eHsAsn}i0{cbh(0#V0kQTuE6) zkAL(kO5SMG!yW#~C+WSRIvQ?ugZ{?0EBapiw;=w<^{=RM0?x;#(~_(lfOWg-RL6w{ z&B~(e-ig*mdd|h;|5^DD|2g3O?|S`j>d14jIy@QO2LzviNNenW_VesH7i$aP99Z0S zDcQtgQw{N=3=`6ak(J+4Y?()^GKe+RD_b0?AUg1~xyci-(m_~f%gLTvPn-9+qI?PO z=7vI`E*K(Ac1aUq&PP5a83R7h7w;z%aXVuWmY{9xEOqDSg}Z7$;<-F`4CWNMBfyt> zJ~cEHeMI^Ql_bsr=!G+rZ~XA${jQ<}E)}eD5AQi#Cjg^e?;TsYPxAXitC}YYctWQ4 zY$il)m_6n1M8d!S)eDmYh6DC;63(%AAJz&UX+N85MP7uF83iP@kDNP%zI{jIja zcAdH~xd#z%ZZCVC#hY+=&PWpMrSJ;lJZihDd&C=TV?kmeHJAukW-Xn}`L5fa&w;u( zfJ|JV5Z(!>QS(77&}G_Ra=l;g5l|NL-;QrL`uwH&Ay_a|ed1BJD&fd}lfw}OBY-8a z4h>N1UNptJ%Ssu?C=({w7)MlX2GeBg#biq@Db3GXOmMxk7{<2@FM308XmKWs+8hu4 zW6l5=N{}ILs4~9Z*9LnAjD@E3T3{x9ob4-|pqz#WENHWTQ7?ne%*LQd<9c(j|e8 zc%GM-s$~sE74`yy^dZ^=Dhdno;*zS!7&B09CfR9lkGzB{rk!xj1|-a(`=NrG^@j4f zj%SI|j+WST<~XBQ&OBw%e;^u_h3Dezu$4jewvbwdd)dxc!#6CXv}Zr5^L?S$Z1--h zU;M@b;Xr9M|E+iG{^s^z?PFzb50?(nHi|OJIn@|6r@AVk*eEPx&cQRQU07`aYwx_} zOeGehJjohfNW-M>tCrEb4Eo!+qp7c=cuN>0{*L zGfE-!dl0xi`hAkIJpgMsdOBJ!r5bZ~`n1RUE{e=8q;4o{bqThwj<1m?Otx5_(XFJP z$Fq&UXoC^0eZu7UFl!n%4N#jUA>|9ZJ#MpI;|*wnX(8sNw!1jST&`~PR*ObYsbTLD!8J$r z2bq|LtwF3@WAi|_vc^OwKG?28J=AqkQrk@$b{^AP6G+Ue(d0IKC930;@ZPGfwJ})j z7oMXH=X=D>nIA9Oe2WA74eih*U&Gw~h~i&iS))*YYd_^)I47|TB$}n=^_+PjF0vcU zQ~eDZ>)zxZSaL^AxdDgKQYxae5#%OxjyxZ#{d&P;4{Wu&l_vT6UuQ?WH(^l`i;np6 zEnYN7!)Nt_0hA5Q=`KoafI=SoLA#rjjOl^ z^S^N08UImmAf2E=v?>ma9mO9x7CP6L+X(x%t3_a+T_|xSHb%X)KPOc}z3EHFcfsOZ zD1z$F@q5jh{R{fTW>Z?jPvtLK97)2Q?dC{t}l8*Rj?w-?8+-dq|bp?YP;h zyo~SNMfRA;oK78IKmyBI`*hw^)#k$4df0v=10t@ylPn`WM-OA?Bsr;jbzV{8qQGYy< zwEf&R*{5u)pv@}jILWs>Okcop*IqAgc2;)uGIh{Hhn|937B$&v_E-dH4C3+(*60dJ zK0Awman`*F%--k>TtAuly&9^8(eE8O^RO*oIZ8Y_oY)IaB-|maEQ7p&()ajEVW0?; zAg?WexG&S#u#x`UYD@ILQm?j=)RsNa9R%P}Dqp zjh(cQi$Sv1Vu4WsYvr`}$yX|Y^-RMx%S(Zi)VR6N3UPJ?G61;^brx6Ohu_ss;P&}h z{T`z$V>bYSMn$ufm)p?BCm(^c>ChR=@8Q83$$@I*2#tIA-Sckyw4p@{J5ybwXKgfI z$oWcch@)(IwkVk{4h3P^$1gUPS4qS?aE}>-2BnCBBGVM{z0}Fq%Nz{T89sHH>5!~# zZ)X&Rb^sX=>NvTC5H$3uY6UY;SS%QoNweseaxBy_NfXEy3}{~|nC8o1hi7FN{_Ep& zCq5}o*JRi#7N|BjY2-&CaXH-o3?u;hG4+frI>&GiK1W4YldnOOk66o*{X>MXF`@jq zvDpSRaLvYhbA!f(#OKd@k%VzQoZJ9!>W1FHsYX6_sd^4}sJ#f1w~3J(V0%?3$Rf00 z16`Mg{z2AND2SSx+(wy&oe>d(h*s2NWW0wj@KE~=oGz{qqSHRSm54Zo<-{tuX6H`O ze?L`!i|;f7h(HY}Joo>R`5jH;h`$1dcWj)2SX98!>O)*}prHV~&@Haz2qnFfu=ec$ zHI%`6YB1FpzNh7<_&1T*j7EsB6n7E1b|#N*c*TCg!;=U<$DvO!lzF*u+i0=Yk*nF zRQo!x<@xXkWI7=&mT6SJdRXm8?%CLEAFrBMV**ea)C==-3WNL2q(vjXLBfCg%a<}w zx&Ylub4YjNt1{^gKf2EsF9uY!Mved9$JfQN#*?Y#1leIkFo<#cz)pqwAT}h0bM@gUY-eh2W|>k{vlP)nidGR5Bpg>|5_M<6T1&C;tjTTeo5A7Pj|xiQr`D$n zPYHK<)$7n4itR=aag8Mb5mFxNK&e+HWo(NM_!`fK&hW0=Ea^zrYePiSFvzAp!7+8@ zUZ+zbS#mhrdUCrv?b_tu&s?uWuY7)2v)v^&b(UFX3P|Chv9_egc+w3o&2u zvwsl2)f?==k@va*!F1IQ<0S^~wrdQ~gkRy=l|B*S3&8?TdyHSd@Yv2K=osn(mqrYe zvGpt@$Nu5GyO{@TrK=_?gU)^rY>FnaFdq)xE18QU17We`1W0MouC)!J!EN&vYO{}OM z;1X8EJLLIBkiaWTOlPpws5RJPCPEKG7Os?B=6SRV7SKk4@XvrXnk$FvCH~g5^t4$n z@gFM8q+`mws_X+eUq>|p!@Lda`Qze#sEaM(-dJ6pRQtwu)hi&~<0s&=daz|H5u0!_ zK_&;dW-5H1dSkxaI6zpxP+MF9d5eCs&fU3$XleyJOblkygG6gR{|emyC)oQKGU}^sR^kr*vKo8_zh>qkg6ERyd{wY-x;qI((O8ELoG&;^BWlbiaRhGv!O0s4*pj&Lg zC8(s24eX&D^8)_&rGE@D4`h}$Kuvv6(%?P^+s7lL*}$MjHN^iz)tARLadvIn+DhGs zf{2PTT2xw4sS2VjnOdu;RP(rjf)JM~0>xB8i4Za>Dgt7v6lg_A)gq!oTnJQwjI605 z1Vspo5R#~oeZmrkWM=vu`n=!vhx!w;%-nO|XSuF(t`mEEuV4+F(QxK(t~m+^*9>x= z6mf;qML%|umN(ZF%54h#_u9&tFCp%dXNOk`?LV#(n#J`P*y9Lusirj@lHH=>?s{e) z#)2cWY&`QpF^i8Hl@FklU@AyfaA(D2%F8pyJt0#5f|Rt#yT+y*;~H%Q|G|g{MC8!@ zy9ZXzM%#9j?Hlr2W3o3K?YTMJ)_PR5CGa{k2A{uRqx@vUH9FfRofnT6Aia~dJmjs8 zSvZ2t^w>|QtJ7J$&yEQA2g_;n!w%b$m4N}bNq2Q%Y$z}0U>09gSwM+<4+7rxh3}cm zXcr%RCiNxvLX<)BO8f~n)<%Csk#J4XdY_jUmbVQ57(l3~g_(=tcC{4pK(Z zabxWipP4bQuT2ft@DlR3xQd5r{53%pKf5?R3uP9LixTc^Lyx2tR{5mfYr9!;Zt0Qi z=*`&q{(C12p59^q%H48m7=J>m(9|MvZDhPl=Wx=B7{_Nuq&&t=#dye`2M3LjZ7NQ$ zVj7>vOc!MrM!jQC5kAAD*}XPef9`n7f^^kz;%)7o9s-#LuSblczQ_5GjWIlbnwxnP2~P*7Z<8ujm?Hg2BclHgcQl)uK4G(8EiQJe$)lhD=2MKKoq1hfNNR zE7#XS0oyI34^|<%B>RH2CrrM?J4@2O!?%<++x_OBaj%xTh{*5qYrg#`z{2=if4v!X z$=0qH%0GkrI?FV+do~YQ&3TiNyCEa+WP7@zS>;=w7BeYx`kTm{E%=7iYl3{GegK74 zJ}f3(lMUgd`@s$|B2ezG5QwK`kl|A{Wl)6=Y{J(Px)1| zL+kVfxg~bRmX1;1ahMClr<~@v_4F0U()$spR|-lHU1DtZzRkYGo=M&b-X2`Ojj(;{ zeIy8C=sAG$icP%o<2hlnljjt(b}g_pxEdb9cCVw{>Uwz8JdstFOQf>1Y)*>Kd0Uc!%wkq@#La8t>w9+j2wWup&8ry2IVmNP%GqehS41`pC*Ycs+p>EeUD|y7gRwqf>GQj3ku^kXPoMBOkoL}SLQw{n z;P_|t000?T6-uWms?L$w0G-i$XvgpVryU@)%Tp{?!+{v^iCqh4$!oXzq8H;3|2XGcc!>IjL9C|Io94Nnu@}~Y`!-u z^m1Xe@R%FRHTMg527Udw`#W>^?T}=1(MTABlsXNrnX#l^rYNK=@8m1`FxKGciM%rS z@1(z?*($=J&shOIYHX=m69wOF&^~foomXS~Q2jRsLDW4$_t7)7#n}dR+l?9_bn-MR z6F;I5B6LS%Mw75u`5IwGsCpe~Hk6qi@K^Q6ShloTr}to^T@q_(d_6Go_|;Jek^|K^+xx_{;{X_|Chx;OAq?`|AMA}=~ou2z*Thfy5wtb z{BO4M|ENR+&cdKMgwx^p$Nxr5iHBBx1xZ-BWND#@DG4 z?d*cu7EiR~JsofjYaIdrvmIh(C_cgV+j5!)-(hk&B?|!haj?7h+qhbKtjXzeJGfPG zlt;fk+Nr{O4ftShjqf!_{seRxJVt6*B61sFdlnf6Pxp0y{p~eiJe z$uVCw_#DFB5y_Ogb@K=2!z0rfG4T!`kAC+NTMTbT72LtU|CnU_wf)37x^J0?ii+_9 zu0=_OHnA;gbVph$kl|T?AUeTy%~gupF?ntk`sy*b6$Gn?opwGdu5=Av=W@{M4 z?i3T$=K__*KjZ$sfhC~EA2_$vPnDI_s>XomX2Ssc;w$Lb#Kz8nj*JPZ)SbujZ@z)F zWw-uY3ROERhgA}^H6>6n;tGX8!=uWfAyilB@^Kn@@0k&t>ZK(fUIHv1+LwbMy?eR! zpK;zyC1GvolXj2wJv_OQiG4)5;(ZTxUoW>l|7`z)-sQiEM^8k574=*aEqnE3WH_|` zhIeaY+9E`!D%|2eqExQaV)3@D%mJsKJM)>>e?egN-Q)cH$INS!FdD!b2NGrG>h#%K z*Mz0R3WSuDaUaXeW3~^{EFV2i{ZJe(2^PTJiU>(L@U}b1SS6zO^6mT#l`2m=&)czE z>9P9gQ4CD2bP29$4m{G5Ht+HieR&0d zi%cm-Lhti^H#J1GW~}QKcM2_EM_}ZhIT_3y^y8ZUB3=G0mX5hAhpIxdp7}G1i5V8P ztkYyyN_r=MO&!$qlU{vzy;1E~+U0R1#?$RhO*)G33hu3t% z>2cT7+>v;;0T z7JL2iq3*t+QDi}{2ab?tAI&PzmQ3uT;b{y##72pXl3dUp8s;UyB6!T}5L@n!<|FF} zk}QN(h9-G0dOLIg*y_dO&8!9ssf7i8sgIB4lF0XNs!PkB6zdDm$>xT#7wN11h(<^_ z!}(_M9szn~IYgg1oGM#$`qV=;@BA-&T3S`R$?)%qG-@;i(gLUtvLS6MWnvtU9k9J2fVmY!VS87+^GZ9$K<2brICxAc{b#SXOPFg6+lcK9;Dq1fQ5&v7I8E=YIqf@3yejfwAd6T zj+2HeCUmwOii7L0q99~%=L~B!VNfvBju_%WWrDz2e3}F zc=U+mvJ6phYKz#CVKiJkG458_*RS`At53drA0yC69=BJ8X9HcRER=|Ur>J!uV`V%o zUF|GXBdT&2kpnX`ZC?$GE%-r*VLW*TR#kKXC6e_?QA|3q0((>{I@@mZYUvP84(5|> zK(lN&nb8~a(eY#~Xf(yEMCYjeTBH$=aB&hL#MY^1%+~?ttV>va?Zd1gVz^CLfyd8* z^#GoSxJr64(Ck>wkh*LB5U3BFmN70(qa+HLf_3m$A1MtY-ab0X-ro3h(e+}ps@8~x*{HE0=k zCV3i@i_cMe2!yfZ#it?$yHN2oR0Afxy1bM7J4*bA1?#gNpF}QhlxtX(;&?x&XZ^Fe zA*q2Ah|Ig~Q^`SY_88?YLTiTlB_zW|bEu=U~VMBU}}?X#i-U?wZj00Trra>Wu3I0ODSFS2+HYiE=xvKw2?NWkJidCS{h<3G zd3f~KZ+6+mS8O{Q@lLWm`dQiRDiz;;sK)P8PMZVU3PCh_`Az6&iH!VSG7zB9u9yLE3ZD$;^oBY@&k*S35*%fh0>U060 zVL!0)82ylz_E$-VjHYWOmzj9T#1@u`^A~5Vir1h`L58<<5oC`6whEs{*&@EZy7A!+ zGE{ASKH^qh_ci-7=^Gn8T=n*9`vk3snf}U#UtJ+vst@r3w{i8t`Ay6G^^&QLD+;z; z&ORqUK=4?5TC%1rvtAzV+j}{C+jn0#SGGC#J;_Wp)-Fg|bNGtkL7Dq^4h`amk((z= zw{UBjrx`wRU}l_1vlzqVL=ov=-IJCR{9%M%(WQ__E&FZ4?_HmgqRP{@?&Fqoy#uk* z#c0QG_|%^HbCBMzi8SruhhY%C#8!%>Ugex~r{C$aDLdh^&K4YMVT5w%$1HX^F(Iyl zMY`G>`1O+BpN5JQkC-}fzEjUF&6dsOOgTpULTx0UXpWJL#|N2`;G7;yrYFaU87l>4 zv67h>NkBqTEpBFe2%PJMrEth+LQu}rM~-%Lx!Y463R3t5`4J7fZlu(;EB%g*KiT_d z*ekDt)weU;-A)Bs1-7WX-{<{~5PyiZW0|L|NoyQ}!oYSSDOqbJA;2 zp~joyI{H(1bR3av@CI4VXl7!s$y?w>#a(1C(5r-dh0G+Gd*lv3((GDrD*J-`#io8rCn^m6)m;v`IZFIU+>WEZHPVQ_~s3HG?xK8ECt%c9KaYsD7sSY4@FG7-C?a_O~opr6Vnb#ea^Gn2zj zKa*FrqK7R4d-@V(qsHkWSz9z?^`2Q6w~j0|@SP=>!7&`?R8>byMx)7Sb#7288#3TA z`OkBLGXo-WSMT52fvpjV9jek!5?|mMN{?*LTmP zJ!h&cN!(yVs&7>vyGx2X3Mni3_E(JjZXh`6dli{ei1uu_Ka@p^%Gk4p(Sgnoi_s1l1g{bKTaM=^O2 zHf0WdH9|GppY+v^JOc+A{L~Zjh9C5W7t!FF&?;4v8y6$50ONv}q19F;QR)VQ|v&{i(TvSuKD>N0bp*mx9IQQ1R<-ZFLtvztOrAe@5 zqI>k_N&2G>PASsR{A*#MhnA)&ey>mmUC8MPscP^c9+%`R{ZHVWXLJT!*8C&L=f(R? z`^i`^);>AvHp>o4Jo7ofe}f)cg=3EJ!^;eQ2fG75AHWNTfAK#IMq%O?ug-iP-!ksm zXd-qPvBW!axWragldpOhk7Jvas+|)3WGO)gkR)W=7Qf0@jmO?vj^V0Ur}9f&AvWv@ z9f~s~&Dmc`7xvU>tKVw-^5XMIOYXucgQA}8Tis}f+es!i^F}q@r}0C1sm!^ket2>~ zw&{n|e*h^b&4HR4Yw53*1{2}m-+tlUxDJ9P)`%2>?vdl?eA_{|zX zBy<#D?AO90~BS*NdQ%;BzAFhkDRb zG=q;Oy+v_)+Oaa?=N*!s+=Qw<)CvD%C5<_Iiad>;8A)I7_~6}A-5l7qW@NO2DkLP7 zMYiDvDK*6SYMhO!>9^oUR2L!ZN)2YjdXF&!KSK^KlDJHqh|v?OG2vrEzP6xHMkZl?PjK7t*C{IJ8O(4d-)J_6DP@)ct_qf#F%8xYTohvUmjo*vgKk62F1EENuJsk zTFm(aNzu$Cdwq75>aV)~Tv;vlzQ04&vqqo1;|(ASnp2>XlMEQ{U{$DeN2M3J8w)D9 z9I{E^p0}G~_HUmRb#f%VH49Ix8Krw@Ry`KPZH`v=_!w3Jk6f4}oy;AL$Y-hufL>fm zP;+P^>zes<*He!`>7Pwgd3bqYAn&cf?MDNDD9Sh~>Pk}B5J$poB-5aK# z%&g@SM1XfMcgX3;KjX&Xhv}7i04hLyliWnhViN|yq6lk1VX}A=ldg9)L^TdOjm9vI z^ldyWDQG(=Q2rEcq&-j3=Qp;t9ti$V34V4oV&rJ11d}mYcj;T%B;J8Y5Gyp4GmdW$ zzMUxeXWUWN#3R?v=V6+Q`Q(*bA>7e*#6ROY3}*c6=}Ih$`vPDII_s!zS*1>WMY zXh0O>XH9dTuKsj+-8E9KamJ^!SB0duJAfY245l5EPBCPg`RD^wm#8(_U-fxGGC8$) z_n)U(HgClLStC($%_xNMeCI>#rhxK3`-0jCreDlCt$KuC5?XXdGI$e&QXpOlshC1~ z9I$otAnjN+$Lyk0goLz5shR19xWa<`35I@1X@n2K)*8&1SU=<}s!lHsQ2*ljEql3c z?vIlUKaZZ6>yh^8*7LiCMTxKbJbi2tzyY;BQa{kV6uXE+PXbn1!T1Q5bIBEhKqveN zC*LzjZFV8)g`YpQQb~E=86^E$)F^Ue0ntFjlSr}R74dwVbC4oejfWBxuPL}Fk4?wUWZ>XhU6V!9G$SmrG4UPRi`ncE# zm5^Bo9Q<)N-&R|&0kJVyD9>uQf0u0TLoy&dd+cj;mt4d>3JJA?0f9_u#1h-Zou)}` z2pmR#W|jVZ$}n}cm@rI?#|mWYhYHe9Xy zX}(N9Z{*oL3zcZ7#9s~~1VhjcA6p(co%wdsAZresAbXE_gsDfXaqSH?&EWssiVRh| zOGL;xT)O0V6pP%AIfItfd$J|FlLOwEjp z;NwU0{)_(OH;}0eM7P9NdPQG53t`$+k8AGFjd{#WHz~{nUful!U$7U%IXU?toeVh( zvEetN0!+5$2|Vc+{}XJ4{_US}!C|gjj2UBEq0obSN$K-t$V7)n+1_DY+(Yx|2Iy@K z+C0|~DV0hkt@TeR@gBoJhGrV^U{SD>@)9xGloB2=+Hderr2x8!*n}zJyyH>4eI%udWC`9$TQ(&Ip+H<{! z$t(JB%4}fpuKH`AKf9M4`l{!8z>+O}L-ivLkR%oOo#+$}Z*BDLY%P3AiR%v2y*Nin ziMvT<3-TFw%FSaPodewrAFxTExGJ6%R=P^COAYwWq5gB26xs_CW)@!KHbLrt7*e!g zz1_1yEpzbcvj5_E7pMiAu(Z8&CT;1;Q>q{eD+jt{SGiD?93wKgzG2ayvsVe69--uVDnN;k zm<_-4-PW-hi{|~S@_}yEwim0INh>4Y3tz})!NL1OEEi2~hZRj!r-?AJp&#Q^4^nw) z)Q@|3B~N8-x4PfOEVlarxAJAq?s|eHtGf-dkK%K~bpbW+J}QynPSWU$nqhLIPt`+{ z*!=AYNHfqu3O6=s@O@nQ&SGtURDQwe2sl*Z!?D-xiu(5--^beq?}Zg+HV*ZKr_S$( zBfI(kil#kOpR0y0NzZ8I|5K?iL1!>Ss`tFQkIqYA;ZcS71G=o?|LIt_9V4HLrmjHT z2{aqg0oufMkI#8e8c)q5{h~^u3!LHpIb>1 z`W|b*6-11vdwgw`z=S}#lu|Wn3miq(DLmX->@ljlj$QvT#`CLbqpyUT=uFs0kUZMW z8otS!&l*O8|LW~*HD>-ZuD1BrZ@Ui<-lQuK8kU|*8geMHJB>BxfePhHs+)b}r^6sj zaP5N*dY4E4q)~C__pX@{vhSzueR~ObVbv(y;fwr#shep_0=K-YgvQ8fG5pliv5q~y z4fGm9rg19e26l=Fw7O`=8eQ?gfxl>Zq0xQ}X1nZohqQ4`k({!mZ@2dPv2(`g5#1Wt z1i*D`SnY>q$;7^a#Y|7knv;ya_eZOkRL@=SKQwN8U-EQW0d~bO1?}Ev1POhvyRg-k z)xd*pN4f3Xv#pGU2m-W_Kxihwegap(<#Xl2$#u^2BG*Id1jcM&jeas*8S}{$6$qH$ z(p%M0YwPDXI+uJm5$ZbHyUT^vk4C_Pn?!@;F>Y$du@ft(qn5MJ3v2Uc-zym%_U-H& z?p#WVUg@2`)_jbf^zg%<4Wr9OPDYQ^Kcv1DzCx$rNAOFw9xy249vrzHCU!R8IRGig zTismG3Rdp(oM3jY_!|$;w?TGKI=TA<%joOuxqFwzsyp%E$(saQ8*75^b>rr;X`x~p zL0E3cq4=hvws$SJ-VE__iD-h#xwEL%bjE!2QT7EFj%{#c`!~gUR<9~oV5wJ#POQ9%?axc1&;J1l zmh3C?jJoPGgj}znwqeBY9UWt%2Uv$C)01~p?RuZ$xbVI7U2jPvo))hEVb>hK;GoDc z!ygJa@^cU82K7fW{!w;-zDxzvj0z_oabt_`QX66oH}wnWaE)Q&i^AnlCy`G>_|@m~ z&$!uZH9vsGvG33aKD^Dp$4qP^B+O#`D9Ow1npq$|VB*FV@7E_AZoawi4)Msb4s0ws zzr%bUgK8d7j+%+ZmZnww#odgfC_y~2c$1bj8ZLdk^|2mq+K^2*vXU~F>0Lc>jbi9N zZxSD6W@cCLCX>gjCX2y3w`6|vnzFt{eMf?UnZcRUPt`)UW5&28#ebK_km&-1P17RY-S zXr?Y3?D2&<)9dWQsduRN(K_}{O zmpIJGr~AX!AgN+&@$g~3SA|ncxE8O7FBZ4#FuZz`x52B4ny%)|!pi=B^RQFI!YWt7 z?y%$bsI_3N&M_0om0D4v55AvVYFZ?G!?kga7L%r;2ANeidm#bg$Q#ylxJtl7M++d) z>l00)cET0k2}JA+;dC1QBi_6NXpY}jlLt+nA6tUrapIJQHI>>+V|3sdfFz3mf6>!7 z*62m@e$msg640E9j4gHB4+!8|4dC8|OI^@m;HseB)8lUpfjZ1H! zSSA#%8IsF$m=Y!8KKvDpf?tJ778En|Uq(R4hqU)93lJ)ipI)=g`7CvIl_LIkSQnS^ zj75L^+cU$GpCPAg7q`}o!_>p3;@}w=;t5`?? z!W^q5J@@)2k@lsl425Fo?#5ayH$Y|5=CQE@u+BxpRE*=UYmUvIt%ak9iIhis|R)6;?Ije(c5`}Ltdbn=dY2-Rnt4upjqRL$GRpKBNcj4TS;0g zihD%IZKgBvL#nB7Avuv}wPly#y-VTgFJoEQ#<DVZS6Vv)lWK)R18q?Gv7+z%QH~6t>nMajS|aUSP4fSy-Tv0y0`c*wZ6&w((YxVMb4}=# zClOo1%eSa+*)IS4(%>&PbH%@9(It3rlG!fnmLM4Qa{IElIdC2!OzOyWo!y11KY{mo zRj4xEhBd|2RcN7C?;fqc`q*8;fyQ2$;lgQrs2vt0iFNqy`9DSF3i&eVp&*=tAv{2d`3g-M=v^}b~n;V|F z{H^Z|0od%=P-?u%wauS@uT&-V>IHT6q?y;&f}y=AoK5yKR<2F35q~ix-DHg;kC4BS z{Ei>t4D932^88$wJw}%2+m}vtHE>CuMly?(m%1U9NzonRXEBEd>NmX_3h)Ml9au!j zzf}TT;K^bR{cEktyz)ZDuVoh-(W6^ffelM*pQyuF{t%Li9Bcd;d$*(6Gz3X&bHf>j z&E#eC^12rT#V1)NugRR*3F(5SucbZjqXl2LZwZmqSgQh9dA0UiWsYX(T}C%`7Gsbi zVZBRVG$>uzT$#UP_f0i3rJ0ngDOmRzg?_#-hP z0%rt^l8O!7$1Wk`gyWc3zCI(WnapQ=`^xtDQY~X)Q&bZ=rZe3K!ORy>L3n8v90IF= zV~hj+*n^ereGGp5UR0`>h}Eku@|=&NM2BGGn9dOUA($+4S^XheUgh+;zkdtRhSUPv z@vj7%;1j+F>^AXfgd|1&^I}(P|JEI~^BfDa=CU0vZ%K#W-z}$TBsj%I^AnX z_QR~FB6N-8(IWBpP$W1JH!Z9IqQkc*nZW%N^ym4!+5BIy_qb+jbWAAp`GVJ8&djY2 z=Am)eVMTL!DWq7#Gs&ZWB|f+1U5YTdQD!kwWAv6C~a#!61@<+ZGq?bG6x|S3v;a+ry817W^dsvp5Q?e4%VXV~qME~2}qdenWitwf5K(DPOQg<1~eg zn3B1Z+#c}9v4JF=tb|DGsb^<+;umj{UxzPKJqj%8W#aC1B#&%(EP4NY-oUu=Hx}a; zQ8UVqej;`3K1ChWpt09@bj+Aa%DLnpxibb8l9!^)7~| z*16x3wqGgpHcv6UZaI>_$?0c!7_XQb(3Z)JC6YO060!SZCiEK>H=yd}%!}ei>@kNP z7tg4kDX2J4*>93T9jVoL&+Z?d%y&wvDaq9t=VdjhZbv~xqjRB2;VbSq>=4#Es2(YY zvkSKFj=-Oopei#~#F?$cHDBs=uG#yF-M-gpB1vZG9l|i+5XM|^q$&r|D6Dn#4n+^ zowh=&x9;`MNyw`9D+!TCl!UyhY{2O!!@El`IpalePPt|*bH|b?`O$_z{7(+)Ag3&} z9l9P`gRnZqk3^M{3(V?Qx!9v2?^YOcwTt+fOxHVA82hC3Sxh~FKph00>a{mM-iyycAYE;T1p6Gq;93){1+-^4x6 z>FIw7O08kGX;Kw;WskV&9h4g~ugV#4{DydRbnSR5v>!XeecViDsA>C(V*&tI&z=!% zd!P)P+MCsa9e_4YqyBmAqo;jO^mYf$e{+1YvC7;$^c@ePn6r7pMN}a9nCMeg2&WV0 z+TH0i^oV#$c7kj^nZAMGo}p0sVaJn`-EJcnU8EZ$wtW7H&HJjsC!2exbfGuJr*UFS zjkVlw!i6N$4@?zf_gb*#Pmpf6dnT4%xTn|tr#A6c28*$0+a{K?%@Y>t-d}dI=C5$s z>)>!_5<7H_d_U3j_bYxv#3B2a{a>SzTLUc5w}$vM{CRU&$vrrA!)^OX4POP;DgtG_ zjcsgyu^W^J*%Rzo>sG0vX+sOZfP*v1LDNRyZ~|3RnPu3LPcFMF0gW6_`u+j(Tpxv2 zuo@8p*EeD4``51r8SBH@5Idgxmlf7?_X27UMs<~7u^zdiL6KIn@IPQ^MAe-0w%?G? zJH!vLw_~#zY&-(gqG`h4^Dt(1wz|TVt9MYc%|ikaUC&9e4FK&y+L3=lSEQxB3>-!o zT9|tZ55QaQMM9;Dy9pnjIWkmOl6R>)EA44&QI zdWgmP6WE}iJ2;}@G)0%XfhK?l3}A%Y;B+dhkj*b{uWW98Thd!^{uJElx49$TnDUm% zb&B-VZHrLJqwIv7mJwmqs<&i2(kv>IEj0aGQ}<{@-fPhggVu(5E&Yak@;Nxnni1aU zCnSv4NW`IN^?N?cU_+-nV~!)#1V6#RAL=_Z zIsw@{oP-#z)4xZ|k|{d+r;{zJ|MbHGPaV~dlmhWULdc^=hV!_Us4d&P8%SdM2DMDT z2twdAJkfTcK26?-8+Lna()14|G#t3Q$9*7-LfV3Fr+Wvu4k8T$@4;RiJ-L~w66Gs7 z#%QEU5#!#JzFo<-^nS;8H_U>iDmO(3Vp7=x>>!}`t^hpN;A3{sdJXNk&I`igVALuA z+fPED|HIn&`&ga*CcRFsN(@{)7pr4w%_^WasvLY=+{1myxcrlt&JIV*I;}f{i2#D zztR28*!04)a%vx57`vHF*Yd{k+1>P{fv$R(7Zpr!x?!vBfDEBR+zML%h>rOas2%Qe z7Bxc!r7&4pH+fLJ&H|Y3cu4%O&KdmE3bg%&nMrQHPk%K$yio-PXe&)+fHs2F? z8NZnQt~;U3Y4M4x)752_lfux~IOSpY%+gy|`guWNEhNTjD4)56t7pSKBNxXW6upiP zEtNSOfm#OZ-V>6&e36FNqcbgQ<=^aP*c9)-Q41$t0)1XCdC+SH>5E`F$lG6|5bYer zlG;=EqW9KMJy-6zE62xJojBsdyogKVY#2H``@)=GbMEem+5E`2y#Q(I&Pt|-y|aWy z=+OA^_->bMeuL9+32(<6X%crwk?GKf+`9o8-rMmv@rx2ef7t!lV(k)c(%o^N_}U0r zuZ9B-?i_HwXOpoRhE*&DwW$F~kG2YVm_|)+pH=LnuJMKNB3uJ>>*U=H`QijIf<1Pg zt~7<3!oh)+qKVf>@r+zyo! zz}%e4p{kiOd@8joDB?tbZ`srK44#2A#ENhKlz7)Na)#O!W7;BE zL{fc<>^e5{a~n4D@^yaJ62bS7ifH8xua$6%Q&u_N@UGFA$H@LYK1y+44M7()_tQIHPCZ4B2VNVsdv+)1O`&P! zWz_)S4bSOefN?jDOm)?!Q8O^jn@q3R;)9T*%r|*9WMYqmO;0mOHa9_bBWz7&A1J%1dXu8*w{TA~+Nf$Iff5&(UO9UKd9pkTPI zplx6i4!+1qe2^^74Tgm)!!(=BIz>4W*Edq97EYBtv&>bFwTx7x!W=v>9~1GanxKb6 zdh;lwZbo#}v5{Yo1njwYV39CgsE+K*Y(Kd-A3{Wx1}}PN!UC`j3t-u23LD(wO;`eU zpqn=}V0?NBS1a#*PShPY_cEv`I~9?(PqeLWq<5DD>-YsHz}f$;*Sj)bj1oVO-9fr( z9~9E$=azR`aX1M{~~dnF=MQw$mTwZ2cBldu@xmx}Tb^s`T|m z#@O|ad~BWoI~EE{>c{CxfjMC#fR@=)d(gzpl82=4`v}#-5aUX>lhtmqP@TH4~u+jwXp_aD_N#^tH z+b!Xv!El(z(Rb^X&8pOt%d%>GgC9i>r>Nja}DsCDQY#sNdJ>wr6PVR zZgIiSFy*F$SWrthGB7irXn%a0cF7GN6BVNtnq?y}PR|y<+HO-!yicPr!4_h|@4()t zaf#di);Rloi~gV+ko+oipf>6+lxR>(65Xjs;ym`KO#yqSB0f?=7t5i%(GL?MwnV}n zB~m+Ve@4|#tIe)%-Qut;>w9pR8&+)s-9KqcZLdGe1b!q(+v>Svy*}<-G4IrUtn4KjtgV?`XaMKqszzpDW$qe< zd1x%W9vSdB{8#@cr94KY7#%V)*bAJ^wh?~dqQmIepp-sVTuW(O4@j{>V4j(xpS#w# zrL(6KLN3XF-i7WekSH56tNaZ@ua92kO_)|Sf{dxd$8_t!m^#eUfr3Xz$whd!*_9ky zF4iF`jT(pKJsFKszCtDnOx2;MTd(n6{Dy;GS>Bz@3Cn)?BV0u ze+hzeDwGYoTQqdf*5Nm(=*6yMV#!w8aX5=H4aPAkmV?7P)@QF*R@V*uZ+@*V7T3~_ zEs{q=++R2Te_YvGm7YmaG#kcf)wN?}oNr=6BtA^Z*N!zX{g3c-opUS7$Y6}gs5na9 z1HNP%#J$6y7UtDhVz&`S;0<7)z2h?QBx%diupw1--4IcPb6=&~?raxBrtX)}G~fx0 z7l`$KFwGIafY<@jz2|m!8_+9oZUV(^1JoY++by3BV%5>1=_0^hx<{B32Y776*caUr zNr&LZ--<%(kr@X6fI{a9;}ts;6`(Zd%2M(1yRu2 zbyuEY65G+XItdlEM?8Q%ze|3cFIJ8I%zB48NL-B)xQCAn7%hF5Fn`X>#pw!K&aJQ4 z=Zelfo+UWMGy4Gx*)uZF3nGQ?-!2?koJeCv6dBMdMyZV!5WU^v9>%c9SM*%@$5jVp)~a_%gku_j&X(MfyME7O_A6`#c&Ky~Nkf#3P{wBRrW_&RmG6;m?xhotTMVd= zztj3r3Th=UXiLezb z4-Sz{B-8E)X6k(}K}=o5OjGr8EGbugyzxl$UXB8Qp=~>#`VI!Yf5Q0IBpMs`Y|AQq(z{0TsJ%{g_dUf? zAg8%RNX)OGtB`IuuNPe$S~Kc;GE%tk)lkRECTw5oYI%-AFd}Kj%(2qxplfR$j2+S2 zSMcI(*(R48P&n}zdee)&ZnOS?4B{fS*UdO3>R8~^TY{Z%4>yEOZoMEPBQt|vmi3RP zAJC%m#>{WV78tC@gT^P&SBO#zxulnGldToJBY=6s_Aq&lRN@y~mc_RZG8TwMD0|f# zCXQlujeJve@F7-O9X`d6#~gGp%}5U}Jn~xX;MeBp&OP|tm}#4le(!u4c$8%aLpzcF z5J(GahLEN>!+g@)DFZyRs7G~s456>SogCEB=3UW-f8;7Sn@5rmjk0G8F#=n0Y|<)h_=8xE3|vG6sxm0uw==yFIO4x8JV!I;)8YXc5OI2X9dnxKjd8q-_> z$~Wa;Mdm{Ku1A>l6|;-L(!2#_f$~2|)a1S6fe|YM!poX}5VYO83`yQV4IM0Nc6~0;5xPgD;nqg@qA7#WTIB~lpR7`nQ|Sg zoF_*``ceoXcfqPpq=clebpqRtnn-4Y3QOh$>Ei|vj^uk+rba2x3Kt*opoRXh;$4KK;+oS%X9?0J zvw4#rv%{-li8Qgqe|N33>@+saNZ)_h`QX^ihm%%w!seaIEC{Iyh>|$wdL(3dx5VTH zM02xDx2UV^ubEmIN?5g3*e=g(Y0>^AEu~E1^}~{D!&B$DFZ<+)uCq=I9$4AQeI=%> z{5!;_*|sF4O6$n!+HP@TptY;`z^kEjZ?xBGZd0V269p%7;2W$76auJy4S?S$TFwr7 ziz~fA0IfGveUw?yz_#L7Z>{&rbHPckaULe4=E2{CbN;%^ZpjijJt%q7c+He^LTchk zhkb+6E|r%hC*}1uwwxShm%(IUUR*5tWA5>ZePOs1Js@trAg5>cGpD%|BBq+*TJnTM zOnnW@Zb#oJw%l)+XRcD8c%bmdp^-ovsBbm5I_yyjbm*|O_Mz8_P= z%zPzr)6?g$2W}&%exc+#GnH~v3cqqapG#Tk(>std-ZXyso;MLN7s#Vd@VQ=GCAMBN zJa4w3Vn6IBvr?x-w!^L~U+|yn`OD%W=Q*rCkZNOjw#x7Pq!+1SzjxFh10%BYZhcOE zTi4Q4Vas+hUuTdbs}J1j`Jg^hc|;nutgyZh-2kehE!^qS#HolWTsv|$LNzV4g`Jj* z{Rs4= z;7ZLvu6)CPbR9dhjhZA$xoraot4wAGR%WAldC0Dw)$R7rWO4j zB^PFT`Xths91cFXB5OyBzc(oI7DEvQ%m>x$DoeI5X`&9mh5qbC&q%T^hokql6o%eI z9pZ4F?EoTbDdys2rG9aXiiz=92NRE-VVf`GScf;_A&Y^Yi^9S(Ham(6cQY?r!YY;Y zpG`fDSxWc!{qbMvX(Dq_ zEvjhqW>DGUk7;(yO_hx^k2wsH#67sx-}N93#Yy*!9PM|aou-q5IFUE=2h~RcU(a*8E_lUz7tM}_Kfv|nBe8Q zZcf*3)hO6FA_Ychs+_6|vL9trB~Kyzt~TNER6SV^K!<23+CROQRT(%@U)ao{qJq(U zg!(~_41WVdMc+mpc;o>JaT-+u)&MGAQ15pujQ564t{7XIHE@4%M;nH`K*&W^!rGF^*o*U(l@ilAY9nzpoy4BI z>WoAEe%s5m!L1_^fvSN{$vbASpPb`V{x=fYT<0q>DtD4HpZw1|vl#!m@}|4Snv8VSzK|xy&;n6J|Kc#5lCXrWjYWkRnUOd$};>yv_s%v z>zgKYrT=6vf@@b8xY^Wv1|hEyqHd_ch;!yRR`?bcq&S(*qX)yJQQkZI;4ww*=Jxcz zFhMqo$uZP!)S(D$^y0TvB$<4l+77CME{e}SwtwXopeOpvu(Oy&e*dRUU3k;EPlaDo z`w3;)+CTVM<=XU$ZcQ>gvRS>)TOlU6ya`Po%k#-anNmoG=sKY_zfq+Xl_8fht;ieq zVUk-u_dK(hZRcj5b@NEe;=ihM=dP+N3%0b`oP0CmMd?Mf^*TyGaM+`}Re6Umk^`eG zj$bCM{xc+%OZ);mv7&JM11L{W8epP0DC>bg%S2)7;nKKgPR%OAF8a^(4*4_AJi$g% zdMB7YhbWNhz2Q?Vu}7D)Z+2zQf2G_@aL#Tdba5Vt`f7HW&P(skBG)fiO3TIxZfvMi=#Q?~_clNOYWuZ}U$^_<4vp=;yd2#J$4{@NT8R4tBs;FP>_N|0wU)&6dm|25 z>6!@cWb(1rS425!MHcORPT3r|1BGe*$4ooD`ESlX4bJoIe8~XGvU~ULk#QBWX+-ss zZOOB_Gx&GE`t`HVkhXIsZAY94lzKlCdDXM03ENcp=;?@gWqLHCE1EW@~M_yJG7m`QEFX4?#Wd(_}y>vH$LxX@JhDrNYK_5Dy%1Zd;8s zn>6FG(5}|PUvB-edcMW#p9YP4ZoC!N!0H@M>_|3&F+rzGKQ4)=*`Sr%eo3%kz0N&N zN(BX}rT4KPmlYAQ6{q>}9*Z2D*HgY7wCupIrt`M+p-^MIt%?|;0e#VIo?GfPWqTAVVY#idNSQ@zVcN0~M)O_{Prjg%=j zROD7xmK#%!IcZ8|YGz91RxXe$Q>Lh>xTFY_h)RMAUg2`*`#7K9?@vwh-iPNo&pEI2 zdc97G+Lau3j-}f(z17L2FK1Z6xESMsRaTSo7{-jwUR+Ws(-;Yn;uP7+^uC`~k^6}2 zRJ=R067j`mt1_PK!us3N-UB{bTmn>HbtsXZ7qLF`^R<0c6Gl7!jXEp;O8~qMJt_@? zFtRO$I~T;Pa=!i=P*^-uMSz18hi(|?{4&ios#+J34M;O`aw$AEVG~_+`P)<`UL)xU z<@3&hcT55F$4mSSP4w$H9Wi>mw{P%4hCbh4(+8FIpcjDOT{~#(0}76sy;;~4ecbJg zt>Sp`*@BDiP0ik%EY*vGJ7d~#Y&Gi0xDl7DW1hf6Bx^1!pL!kK`~%guoY=QlOiV1 zM=wf9d1#`Ax%9DCXJO^%hIlF$u1?gBD{}I^;ciRK9`Fu-!Xvhrv(!r;{s_LceX|pH z2>*~9i{%t>fsI8*4=5d2Co6z)w-SyPsAhCWn1D}qK7jk>3t&7DrhuZPvoy*41n`Af ziC;my@ftmvjn&z~0a=FQy?9AdbEgcA>c<2uWg%D&1O!@B2L}1$+wbWHI;*B1Z{vF7 zq|*uT*ld**JuK3_qEUyTe~P%yU^v8+?0QQMOM|L58DQ6;V%dAbZHzUAR{_w7Tu5;> z@yKDD9joSk6(V7!!mom0`Js=%Avp}gkIyhH`Pwwlull|EJSH83mQ)?pFv`NSHt6#J z?a?U&Tlr8X66Tza6LZJZ{Ci5-`&IwWnVODJ4a$JQMFy6tKFmtup6_1^>T7YDf*k@{ zOS5v;u`9|n0W@iil(Qzb-F~goX$}_0OL$-;wp4E?#b8@1vdyy2&He<6LO{F-a64K$ zk_kmRgSx(FmyhriNsVr|;PaK>Z6gV{fq28)t)h(OqGaDJ3f|u%2mK@filkejlXBnE zT)4&sco2MGt(Y2LjIPqNcF+T#d!Law^Mi@e%AJvR}&V$#`$E{#~U^ zvg-dPOyfC(ZurwYY!hRz{_$*Q1o4z%4G?muAUV6Pn$muHC`~{2{Bt(gXzn9HvfT7< z?ec8$a+i+}ekk1>hOUVw_FM1ZedB|~X&a21G&OSO6L*4`<43**Ev0X_CeDU=UIUE$ zqF%Ry=PJW2qeRK(^;yf5{J5HtJ(9sI=z0~|njQv{6(++PUEHP~wx!04Ui0e5?+kT% z{*1u7J+wm}s-lPb=Mf=NIG!qjSJ8J8*M59de#_AhAknM0@*WO8GYDgtF*-!+N~z{@ zv*|>TqPL~t%^m;fJ(3q8=B}cF5OSyB+zaC*1D)|<+BzG^~DL7iLl5*)ca&-PUpmEA2wIxoWy`oH+=!oo>m}r zZfFj{RzPc!$$I}YUBOJV;ONBgd2g|I^S+JunR{{s3_BXRw)O01TCkC!hOW=t4vW`! z0uHOS{V$w(;f@KdF8pCi&h5N@Q#<1GX5Z=yCF5Yzqy%(s5ij3mvNF)j&$;Opn#zKPY9a zVRWSj@$`|S;Q!abH(_757}E|_QP+DBg&~7u+4c`Ww^}iz`wwtKr2#~9rQ%W9q?~9{ zhP8q)S8ds7VFVPqd>F}_WkNFLqrOjYx5CI)IuH1kuXRV^44}tXs$_{_);mF9fRvLUy= zU*PY+_5zc9zc_B>aoh7R?(OdD6%PygFrP{2wSHLh?f;7su+`+crK^i}4UW(8CQ;f2 zK&I2^=6nKVFin7Uo9P(`e4saz{U>bLJyJKFr#ER8{Uq{1%oWz-OX6-uUqOWGC;qw} zC`0+|X$%fqbV#=$uxvKlEnx7p_DNha$jEGvZ3}#`pMF%(1lG&u~Yc z&I+|bs2Ut;rkZIAlLWUM**$&JVOgRH8Hyi>Ny169_?p8)$D@RMPln-Fnw%>ji)CFv z@fzoe)o*`G^-a54~;+H1U9|4Pn0PMx{)1aGfj3ceCHN zS2+*j;)$Ee!N=&z?WPWj?m}d+#Y*#L8YtT>%g7aBVWs+HJkYu><+Q_b_d0L0U5$Ww zx>qXoTi3Mi@>r?B4VN%QMYiA+6o0W*Oo--~{t>#53gQW(FHy1@$8W%Rx_$2GXVB&` z)rCk5YM`5p)!=ie(+YthW2}lKEgzfC8v&q`NFfKw>nnW<(HxOU?5~x&ioQb+C|R*w z@%PBXy!)pEeD}0gg@9W?LP|h)*VSfin8Pv@M{fT4{d_FX4d^JhA9?-q?l{+vZ|=v| zl%;_GP0+?I0l0NI6CSa``_3xo=|6Yc%%HGt!Pyb=svT@%pTw19j)9WNh-f|%rAY%o z6S(QNjn`%U)w$qC_E_fHQ-tvlAJOOHH;LYzzAZjFdDfOf$5R)NC$giX%lfD+%muc_ za2X$T5FJ(nEe+p99akr>8ZBVxIb3jIwsQ|cLBJjspUAKn72(v}p0q({1uiIibillm zz}N=YO1p?=ffX0yn6~{C>?e)^$S+`Ep#WYq3qNHA*Me$yC0Re$JDNr*14OpbQex(^UAb&qObbXXsdYeKdsRJ^*D$n>;L;oUBG*w9ni>! z7uJIX!^?dZu&9X1)&E9XcK@G>Ea9$4#e{xMYnT~TI}Gs)qZ;qw(CFE}0y-=orvA^X z?wf^4vF<{dk^zCmdckqt)u+WgqG&!M_HbynJMnhB`dMp`hVTv7?~Bz9#4DXjiNNQ~ z)9@>3k`Upz!s?4l+_MzEtckC11em-a+@+gbA^_w?+!XykO^*zOp)CbXHuw=ofbL?E z|IXR_LPFht2a<{~2Kc#2ER-SpsDre&%|LpGIki}o%t&~d1}ONwc1WOADNYUPr^qAL zg&= z5pu7orGB>%JI9it(VmcoZ`n`*kAt>~DJXmo6hZv1s?RY{;Ej}_vV@Uv6sD z(hNJteKJ1OQ>X}IVoLBbufTJi7E0d!b4lIxtv51?#s1azgLPzyuP?*@i#-sb)&QaD zS`a4czh4hp>)`QAZM<9HAyX!A2CJrl$h6)-u>o8;1_})DS=e_ee1aNhxiT^3Ey{^5<&=p`<%z zWb`)B-WzHj0h=UHFq3q1kyxoey3qnW{JUZ~xe9SUv}Su+@2Pr6hiYzgT*KkQ`cTbg{#_;kwaWOa4nx3)B5Lh63b;h9Ju7X3dK@h#X$o#|JXwHGOW8uL_m zrW1OtFajtZ&sDovjhhdeu0K1j1Hl?RSna;SS)v&r<3{!)8*8-uB(55-?58syBXt@u zq~Sb&Os!kO07IdJtzjwOqXMbX;Q)0H)N1xc;5u>!UKz~U@zzoU)TB~69gDTihfJWs z3}DU$4w##_=94lwCx13V;_Lq7rtl038I`wh+b^I}()EFXPiq~VJkqrF->N-H)(nT7 zv$mv}TUQui)S&WG+irbp8wSy&^(=qvqdtpc4T4s852!M0jg#S#$8haNb&(V^#wB{* z1Di=%1SHK?ur?Eo%=QBoW8gD)t*$0@ag*WIaLonI&QViOd>;$=>-QMedO;}1i|(kz zE38$N6DTCf0J>DB*9;iNtUNJ(Go1@;+B{JcODVf|_6nZX>;ZUJ314@8l(AlAnZVU{ z4!m9(t~svZ{9WV|BhrTL_;*gi@YIlAgksOC-j(&C;F!v$=d$T;otG7Ky{FXlJ+NI7 z=4tpzaoDr0`U2fYoi;QA z-~GoW#wLJ=a^qQR!mRh;HRiL`Kk#swz5mMv&_79R1s^=4q@9h9e-F%mu~iuf$1?VF z&Cn=)j*SXo0e|F;MNyNYbWx?gnuG6=VkSa#MV-b-DLfPyA*nW(c-FJX+SQJ;a|B0| z(XLZJ$YFD1HKh+br+gcVSrq*>j#Lt_EAlO0zAL1>aP19mL!I=vfmw-eG~~Y@Dw(k` z(5D5%asjrIRnJYz+NYT1B_l^6@qdrW{P-@@05i?i)ccrX?!p&Z5cg^ z;FKYNobxhf%8^EC7}%nBwgqu#OsYErQki8{7QMMS%2CZKWI@re^$Rd;BGA`Fp9uTK z5Zr<`tuXa&3u7mU7SR197til>iTmT(;2vChnv{T=2s9syTf^`-S#P$Ki}{O&r#-xi z?iQe@-yjE+lo|`mmc2xSHpjWjzFM-9UHb@+xa^u-a?SI0(LTwB@n03RiRZuzG-ZE9j-H!Et`nUUmO>bC#fklE!MLcS97EzU)7p=2`b?Io@_hoc(*Ln%E_a#slLKR?td)dmo8}#{>)vtEH%4- z=l+9#cNc6q-55Vk?pBPW%K-btnBOCO55lb@z9bCE!CZldjchfmDYas6gEty z3CJp$rt9|OYx`9HC>s}AS&CVx`5u*lhJxG`!-?RRxR=InG9VQk>z*Hz4fwj$T9$$^ zPXyOzPHm?|ZB=bJzh~ox3$kQs7Q>Lg8}(TtDOwy>&>C3;;nz60<5Jda>aw}uW)s=Z$CE_${6wWBBvc$xKwS3jui9c>Hq-6y6Z1ZF*TaZ~Vi7_^akA?dnm z5gO@(h~B3+W`Mi;voU5eqfB;m0@N)g-b(|FI`&0tQa}KBQX}zZy#XhEpkUBxwUmo~ zvH-C_9o1zIDsIHvmX8(8v7>FTs0AEdFU!XjiMvxZC3QU6@3ao-_xfkNsYedAQGJiwPqi_&&_Z`vCuS}IZ0_i54vDnV zckW^#Kn7Z=z^i4dJu`4Ek6$jFWIsN6VN0ZH9>%zct96K5ds^UH!5B|(`dvLnBP$FYaHzbOzpn8TE;2aGOQb4TAra{A|w>n zfO`&NI07P@TCOTi)T#JhIsV{!yQtda>9JG9_y|spz2=W9{AEa)gt}nu*r&ZxNcrZ< zBmXCgc1|ARuM42bq(cuShN4?6ARIf}hxksqzUd-%0zx0}WXDUW9Ebk=x?D<4xQ}M@^{y+K|>pd*oqk=zcS(`PfNnYWd1{g+@F2QwKBxpRhbr-?lnGE033&+;O8q zI6B=I_Q!CaiWxoJ(h||6BdzR9mPlFYcrh2_-~n*(LWy;YN-lowd?%!$K@wLwNcyu1 zK~K@OqXbB9PeSI7XLqr%^73v6RwFKHCnnJij=wh#hKC2xi@ACO^e?8DG>nh{8B;eK zgW;R8>A|oJP`=wL@G)?G7Dz6fC_Jq9qgQ!(cHw>fs6dR3%NSf@{NHPXh~=g19BxXp z2CJBc=D-2aodG*Q@U?Q$-=`l{9T}u7oYq;^!sfP__9teiOdbT}BJvTSkN$VgWqc1; zN_fw@1J;$4!rk`eMU$Da)R?{T&=kyFrfI3K9|LDvuV& zo%Jk(n{JUgDa$t{JMstlJP=QVqDji3IMAh^kC0D+Kh$LnTK70HyACdw)8S84Y&jS* z1rjw7zHdT}EcN~&uI~nrIfP*~WPFu3XtX3qhuXCuB%r$NUzrH0${0N?Gk zwbjPj^bq(46+0|igoGSw)d^dEUwg4`#$w|l`@{y_S-YZSqpMr1t()zJ?gJLb<}eM@ z+bBhPg?1$WND4@Ws(UuC#6Aky4`iyly47BM13(9Q#b40;?yde$(78xSxfBK0ZKnG= z?cJ+*1fmwlb|?Dfi#ANd%Yn1MWq_Q82OjnhWWU+?EfN!TULcHfJT}NRMWP!b((N*G zM+fGsS`yd@j_38GkPd-Bz>J@bkt>JZ6?ujzffgSu%KWn`Saa*aE!}2hVQ<|5tVwqf zG|RWqFRCo^7-65UC(?fg71avJ7o^X5_h~tQ4fRxC=HhM@sc1seSon0Uqjj_udDQ*c zzKcPET(#?|5SDq}=|@ZFjoMP$HSPnNd{O`S)WtGBWvye}0BTZ%Lk(bF zn7L)W*13#$WzKvF(Q`o$9r-PE*m=!3xfA>}V#!07dk$jP#fGBQSQ9q4O9mW_a?ssM z%eazkDPhHkS6qD9`zzT2vDhl-O-9B(H46O=+L|J83lqi+z>1t#0e$HBTAHWE7ugp? zeF4`Vdph9d1HdB^snf=qLT(M~Hw9nb{7uE}JW7 zg5L~v9lv@rXN$nI+`4auvv$X}2NoiQOBsKog#EV|Jj3aiV#RjzP+M zC_@EsP$%@nEZtrygv1wUm7}AIvraOFiSmcl+*bNJecqMNpgQ~g^}ll}z>NT#GRU_n zx;03KZaG}D4qKhH_B7VWEhgsZ3-sV}#LrTQ9-Dr}U=jh`kCxyCZ-GiHW3~RzN|-0Q zHg#E6KJ$f)!VDc)Gcjo6~pEEBlgt>us=#4b7Cgj*uW`Q*rOu~GecI7QQn%}&J%Ow18%lcR{en_$+tQdNwtpkqW% z>X1}AUEgnaW{k>NgRXt|wiKL}O6YbL$Q}lI_)`rGmq6H*vqdgEA3*$p)|q?$U1_Dt zjvbRrzAzZvcK1Tp1bp}HI+BU@y3Ul*PJZLbByI;}sa-0-FYQ;JXq-gGd0F!jzXJ4- z5>!}Bpq%(wqFM(zP`vt}`mKR|bHC zx`UJ~&wDXSXwYL89KN`*rh+b}Pxj5z884P*57kP4c$s$u zcSd|1$xZL%dT)#~sq49MBQwJN=!e5+MqgC4_gyW7H_j9y>y+fdA(m?1Pl6H(?z>C& z`F}8kkxyw$Ta0!K@{MvY*=v3pnV%TiX0sco+S$xVJtG&Hz+wjdmsbdx!Uo;d-qZBe zZk`^kjLm`A5*>wp+JhD@0{Fh@2)Zob*22~Q+4P3>0^F}6Fue0Dawr+Qze$O zsyLf&uEg%;EVbnXxLfr{xhZymeD!`=Lx<4LO}8SP6k zN^JcB?hv^J`f>rVk>+aE_r(2aI)lS6^7R8Ok8;?jGV6R}p;IqA=sq^i_<$LHoe9I= z;$Avnu@Pab?w^hdiX|)Ggp{T2oTH)WINEuU(#ZE`r$5kkCw_oD&^atVZ zoSdJqGn%igUXr%*mEirk*Y>43hYW5<$pt<8d;2CWyZ%w{{L4Qf&T(6Z&vwz%fe|g7 z<~La;5aSea1&nbw;79h|$mzWn&Iv)Fvq(Tbv^Z5nU)|=-zD6$VvmEFAfcp8(dMvI$ z_b}^pCp5Q(#JAG5>IOQ1K^E-S=V0r($u{t93nP_D(Ou=FFLKTwibtJtXw(BGW~l?& zY+8m}$L;Lr@CEqtiqOLSZtIRL+^Zlb{=V;Z{rzN_yn9eBGtr#!S-iJu!D6iO)&k#c zmu%!YO)mzH9(J;BvsPF0aqGNnC_0N^N?>zoNqlc}==gLf78i^yVs!uMc=U>t(Ca>? z$-=E0?)sZ&fbU_giqT2gV)s(ST<1o&s5sg4kzcB#{tx2c$~;A;s9?|Ie0$9QHiT*tf6CQ)Q!icsZ9FLNYpK`y!JJ_Q_P|VM>us-tpu4{Kk*KMe5sO z{+lwDP~f_S!L=Og)BRKu{jG|k((H|FsQ!0O;})KUs26*uK?}a{nfQfh$WyP;G~(tr z9i>68N7{|1HMc-!Zy4lSU-5f<2sm8&Lp7S^Wp8X)2Lw((v3nIbY~@rsF=j zlHOeHpC?jh6%@1a^_R~xc395RUtKSjV zw3nbUc3wsMz@A@>B^6;m(iaI%OlK`YycemlnpgAnkRwd?y-Mvh9W=@VOtx1X(}U6H zYwOTSt)9X%$$Uw!xgD#1a;h%~zE;X5WsRNOxpMeSOU_{-Qj#{&hmkFZ)3SK)xkl)s z9C^C+3to2ME5|M{jL6(PAZ?BO=3RLCbz!^;*GtwMYsKto{5pqkIbtV!AJ!42ZCc!5 z)^uHOaH*qRU47QJu2lnDgB3aV)*DldKmKrJ`Q6_i{AB;nSMJGRK(2wW?ZgcPaE+j= zu&nld=m?Gh`-a31`s>R;b+&M5^yr)FlJ@f*d1m^mRbHfCg;CZsSmLc`&1TxHvGJz0 zSyr|01r5KcnC7vy&b>`Xh6gX52&ejQ>rg5R*lk(KTS~WktUEVyRp*4aKI^urg6zEI zK|#?=G!jIG8<&SceO7_^Jx#85ocDy;aoS(NnjiqP_uW$kEGuel|H^v;;@sAj-Frv< z?y>6R0(>F3jBEbqxRsYCp@2Mi#Clv`jhm5`T}lM-v}j4V%B}zuXV&U@`lxAH`B&eG z<`AaVpq>rAI5q)VB{?Eg+t;E^HgpRBA6?qOvEDXVpaU#)u2^VcysGhM&YBqj!tlQ* zu0&klYX0_zTx8%58ceSU%3F@kRhfKtx?)hv{!S`lDn7w#gxzHdUgP%gUcP4h4!Cr4 z!Ixrf{HM3zyElh-!v$URpZ1I{_;(Imr(6qrb**{u2gh;C?r69veL2}5V|`Ey!r3Dy zm2VdnUOW)4*UE#c8%nv>6^wnO+}Ebpsy61H9ktkl*cTws0RfHEMJFlkkCn73BB6<8 z9X4E}(WoP7YF>wI&X)l08`)UfOL}iGTY2eB&fXhVeT2nw`)m5J{p)Z-mN0%C4kj@t z(GVmxd*IAyMgKrtHsbM-r?b;&(K7&S)pJjDa76k?4sbpDnBd*$=x6-hg0WZcV(3S{ zyA}2KOLwzs+T`BnKTZT)=u7>gsk)p};prTAhaH!F;{M7FWN+W>-mbo*mpOVx5H>D= zeq+aJpU$Kc;;M{aG_;a(0x*-#ViBaWdLtj@tJw$IfbgQw;FmxAfNkuF0g_?hVtCi@ zJE0cJWO$cksDHYz-*qK-a$pg_t&rqut6p@0Dny!@TktGkU^((yMR2)K(YZ!PTN8Lh ztCA9#fMoWHCmX2x?CzZS>m%vkzsUm1{!*T}r5)*z1>N^>6NR;Hm925Rw-4gR zaPog5bn}PNiaIMNzoG$hTnd--;KQMi>M2m#)Y5LWR-Ms~A) z&vNM~>5D!A`f-#>h=+pivwHEWK#_kR_Pyy)#s}FypwS1gBQcAYoXQ(DCM_+d^0dy~ zT8t&@D-2uKtQ-Gf(Z6%7Amjmf370|JyH7G2%x0g57?m5NhihLq1g_cf0Xyz64Eb;F zn(+1Y^&e(rY)$VPfVE&D)rl~xFp8Lw4KDkmV#3ORKS&x%LI#n=p2Em3O6KAb--tLH zkekXzo!ddNs&4iK#sXI3vDk_(Tc0gjV1)qcMS!-rwoL`OPJ)y;wvMp_eamj(u+@)T zp344t*M9ZuP}>LR-~jD6#yc~vXI(TUBs!tLy3%P(ry;)8GXX3yK8G@3)Ulp}co-BF# zYt%dV1TP+YHf>B3cffmf~sIsu$6)E8^V{}4z z`jtVdf{4@D-R_;fi@VEMkA%wB(hjXpT(%z!3;p`=e>>OM;S6u(#}_)|R+qgyxw8MM zMw4cwJ8RUMN{8ibTykLq{h6yN@p5tHf}#Qv_MiG+>(gc1xI+&ur+!B`*2 zFmE5LI#=N33kL?KVL!H({2JCwfmC2uWR=cPbq50+)@5SMFEQz|OcLeK*F;y#8 zK|I##QXo582OtN1Zs<4&mOL4H6zpHldx+0we^o+BY)32G&U%ZDN_L~jXk;DnLjet^ zvxjD`!mRf6_vd#(=i1!zbZH;*Dsx+~xQyehjiocCWnJS#I((Wot@|8)~XOGqP5{sF7XJ|VxMg|4> zO{hl5Qb}J7SET8iqWIVt(R>`yMho+<<(!y%sw2mxprSY-w+pR~%o@mC({Ly0REd*W znza1k%rXB#BQHAc2kFE*hY0@QE*~3cvHA@6Yokn%3?1N=e{E*`4cA&dsfDGwy6^#( zJjyNu+ zirFD*%>8a&e6vgh6b=Ax+6d)=K*c=w7)<2Nv=OPXU2n^D1ig#JE(}&CgM8{<1EaVH zoSl6P!&&&)mb@I9SD&8RAVfie*l0q)JcVnI$zhl+QjHbl~4PKY(YlaPS0&txgWCY2H>PS=C3_ zLl=SClX_-!T(DM$t-o>_QhG9xEe5p!mIFVCGoXpY&ETo=%Rvx3CLxXa1R$>h%~-c> z=IRqNjh!7taaFeBjo>I7#Q(MG@)Bts_TvxS^oPNYa)Y%I)9b_01wQztNc3*^(^W*f zqu%5~%ZLCu)sIqC;jMDc01LVMslp{R&>N#4*H9Q}dxe1HLlx?5mB7~VIg%hQuVE%? z;}W4)B}`)!pwV|<_T!8wYXh?ubfI%=K>oFR=p7P6V;7@AAc~af|17aS7`+<0w9Z{N z_I7n8@Q+D(_wSt7)V_A^L&A;Tz)q0HWDrd|Nhs(GltPV8E5qbP^qt7zBAol*ql!_% zWD1X^n!Eyn-!5M&?hCS(W!F6MX|v_x<(b(L>}`KVdV3Vp^C>ygZ@SO4>?qoH`;=Gl zP>>}RT`&%W_E>%>aFc?^{0I4FB#C~WCQ}u8WAd~LD=t8y1cPAeA6TxKh*L;j0dqMm z!An>YG*A5AjdqBgi5dO=+lF6CGEQCyPftTv5EOZO)1EIc&Q>9e)1^pR6$1r?qcNxX6OsM;p=^BIKN0xoj0pj@nN z0{}UrAd>KHdoXSW+~dM@ZEF0}0gW#V##gt?Zd~2?tsKP8sS_GweQ7A6XA4gf76&XJ zC3urQWMcBYuq>`lTb7y!`-8nrSb^4v4_8@Rxhbds6V&MPWDXpG4)YYPJ|%X zGq}Pc*=usU?J4qH_ct#%qGB&gO5p{8TF$+vEe4k;7+SD)z&TcF*jT<6q%^YnBy)EV z=IC}kV8sd*bt7cuDQ=u8tQyzZ0Vr<(+ZnhHR`!7ne_f5g1@~AiQY|e10{^q;=S~9L zX1#07iEG@>ajPh zV_pC@G8%(kr1RZ*}%~7t`_a63|vEKHeTyP4dqXlbrSgJhtN8Bgxh`5h+VkW&}V4%2s+uMd82A`vZw2iXHoh^wFKJKnE|s~9qa zYe`|^1in#!55hd7B%?og1>bP|X-*^kjGp^;G-|bMuh~MzWitd1K4_E>9>xUwf!6fm|7X$lzd`H^a6uH&buXUg=Uvrtbi_*nC$3d1Wt-J+TkA^?SUUdd(s%NQe z-@D+y-=HhUZkwBd^NaOQ7rc?@HVpIwbs~?U&Jud4Uh{>iHI%cuq09W`vT6 zRQo*edtC+pzhgo*>-ivecvV`z{3~!Pw}>dsNBb+HBGu&*eitDgR-PvJ5+q04>WK@1 ztnEh|g2FL|S(o0JqZyMv7otaadBKmRa$g5%Fb`kyVJ{fC)(zK|w~rzCbhPViS@MH;!G}#xQ(snIE+KEs8ZY9+ zke}xsuXZL=vFZq}A!Ea5AUtIb!gn5c`DPT3Sj-1A@g&hsgi|8@LpVRc;cb`nc<9-k z2TGR2zCIw`_9FGYoBtPAFc+CjqI$L7=C>pOF<>8hW$G!TCCBo}Q_Wvr9$m!69o_V- z;#=B|O!M>4svqQ#O&p_@ zz*`Y35QAgs;hBD|^HW=3l>HrP3F*yfMb2N0lOf1Q$$`HWZLCoK!!>uhpZ$mh+Vs38 zq(E9!$|V01ig(ec&04p9Nk**nBEUNtIZ^0=i+l6Sf=E}R)Zc#m&`wETO>21jPS7qN zXnuzIJ{Uez=70Ct>BJzff^<7l_8ZSm4y&t6|4SRkfi~`i_rEWyQ|gQLc>Us9K%2py z>fe2t8XYHtz)?%;F|P6QzXSHzEfw6r?(LFE;uINTcon;@|HL4#4!Sw8RnOvlKkU0m znHyhj2G_^9HgFcI4DLMbAUrKEf8bh89QoL5n9o+&8U}6Q9Sx%Xik;)E3e44=Zj(6u z&Mnrg4U@j*%~O5x)NCDevH~-2!MM?Rh;uG#i&Q?N8+TO^Hs3B*O>0)`3#xE~I@q8_ z3P48JhIBR$znG@0^Ig_~?o%1pwv&BFEOx}!naPqimfY}hmmcl-W_t#JSuY72>jr)O zeW4SNdut9>dF6c?{OVztYUR@9>7IS1tRQgD<&y<3tv8Lhv;@nWVvLJNxo**Yha@;N ztc|fu38ThT4X-PVbca>40C;ZpVTe<)|<0TXeR~0i+Mp~)>)W?G{1#l)cmyWx0FxiJBtp-ja5HKm>x=UV;{4ZqG6Wk z<4Zvz$_}3HF9tJZdLvd?j<_B zRxXIU9-5>2s#jbWsJXb){ei3gq*SnP1^Fk`$5&(y!~%2Ga}kz3T=Oti68u_-E_Zz| zdY#7eb|chs*Ki2*Bf=8VLYjMBLlo|A)!}E1W?6#=ex*9d&!fX}-}6z2u*|Yj=@MF$ z({AC(RLfR%Q}mfcPY<)Av~%hdb{zfNues7ApcB9Kd9zDf?iYdYWJPjWYGz6ocKmp| zK0JJM#&(*{tR>&(mZT|MrJ}oJBlo7Dl>@H~D5`<>p^+hiy;zRYkai?h```M(oF_&na0ODl>lR3Qaiu((|CehFLb+ ziIK9GjHd5Mdpf$Jo5nm@QLd^ob$CR0F7NV4za48*6=KW1 z^X<#YB$Q3L`Dn8u@Lb*|>5sLc@|GQEIo}?omqtf;HOQZoY?W+SSzQ{rpQ^ZIb*bDv zudWc)SzMGrpvgbRRgpA6Td3zNj_D4g?)s-3Tl%(k&UUN?+tUR+_U+vekTx4$A}?lR zHi$8(>F^ghDmQ+j8d{CS)_8=M_2#mb(}zo29ifOK1U|m=QqYy6VIcen5tm;ViTPve zRMH0Ks!ULg5EQm5>U(^#C@%3SUhzL9ND1rgrNYL2))C`b`zcHdl5WpW?G0ae+v=hX zn>Tpegb%2`Isv<@it$EBzsTZ12>zJ`zBgR@ET6oRXWf0{q((vm9iD_o%U$uETcu&OXjQo z_bpe}Xt7`AGiw>+T~wMJ8HoazGy0TB}tP)t(QT{ew(c{y^M_*lwagWws z;gK&5PZ4fg>~YoGpKPUD6}X1@J7qobyF$EAuqO88f$rH|#`A^DeARGFl&6L)9`9j^ z;kcBh_9`o}Vhi4faPU>BHd+VD@aM6E2DJz@bf#o-m*zSaIYnf-rzXBy{Sa;%wzl=E z6g`lSu)9V-@(&4W_}g!;s23M}%#J#Fi+mxC8qw3-A62Bqe>{?{5&-6Ueb%0i?VADS zd@EWhaJ&N0t-oak%(}-#kyYS{78t3vF_9x|M?Y!w8FxtX068bN_LyAFcVIje+o6q4 z1=`UPWS{a|1KN$w9lVos?Se1M=(Nv(zX02k3GIm4BjCm!eUtosHuICtW?4*@;p*R- zbIPgi*@?@iip~<<;_{v;d%W|a-=2K`E6YEE5XRkKs9*Z|2;m{Y?}WJ#XUX9Jjl6l2 z2-IoDh=YpSz-4whv^;r2lXpyut&Y_@xA_Qbf2|f zhTd(IvQ2%s<6%9sKxecC>ySJvvqCtH1FwDsF;{_lI>O7eK@`VWqcVDFXU{sMa{}_pq ztrtc*z;~($&&qFx?c51{+3EQGAA67gfxAfO8mxBsE8yH#`I{Vea*`~4DWZpI+a2!1q$?(r7Q0VyT4+WGkhymm+IB7paogML69 z^<3g4kI_~PsZe-i0Lu$*3?_U0Z&xigr;hzZQvPXZi zTvWrCcmH10>-fsib5*$3x$Ztv=D zyM(4@g!dN&ws-YJMy0_eYMl1_{(t9)CJ*n9|H4iC`t_?mX1aZV8;w=Kp8g{@PaKFe zWf^EaNi$%zAepUifPXsS2i}+{06aJMKQx;9eAHtzOTikQOz`Cj0egeZcVTPxZUJw` zVv^p#%t<|ELi+f%ji3hrN@JQXu4abi&P}oiHHM$x7#fmdK13mA!Qxl@H`|y6bygY6 zJ%o;MlL&pA#bUSLWv{Q&BUybDXw19S4YmcYl{WupMskoUVo2rzZYFEO2yhBVlZ7v3 z31>h?2V+c1<024MSjcq29!l57X^WWhURXXZ1p9umY@7gg1m#4-4P0HEg$z0&!kSn43&!lpl@K7HvEu)JMd zc^-G-8%0oM4_12xg|LJ{nNepEnw`gOTRYF>uCAeR1shvueN; z)VOGU_eZhnCe@;O918xq&J6Ww9NCAUQz{TsFyUtYyKAdylIHlBs z2CQ7Tkb9D2K06ish8FA4+TPR-m4fYSY7#~mUAH-cFE)vj#;Zh<`WnXXS-#D;u8Z7aD$|6vnldxsu!MU7n zkOOu#@&O2B&z?0P9M!V7HN6nO85XO;e(Wr~sH+G%Ib2zk;RKzS`1DJ@dwE%oSD|Z@ zcYw%)_6M!6IumIbcJ^&*Ju;%AJSM-Gvz7cA!bk7-O&a4_$;O2s# zngMTbwxYh}9I|;|Z9}2O&83W;5w1?_oCDrrVc8m}HI8RZm(lu}=X96kN!@a*h1e*s4 zUj{@;3}8#5QR*`s?KMmVIT>$Y(>E(W>Evt!*+vJz_@1v(ZKjJrSHLQ7EaI{Lso{9C z+2)qnjh1BrCNy$az+Xe^24=Htzp0n(8{`R1a;8kKr|aiP|C=~%ufIvR*T9OV@w*mw zZ%cV-qAAyCFW3^WxH>8>o;&z}^*T61+ z7=xQc=L58W3SAs_t2ZOX5v^%uQr1)@$!K_Btzg$9Fewx`4GTd-X(96vLqN{CS7T$8e z@V0O~r)(NrVwTDW<)a}0p-rWtJ6oP%K|&-{COYp5 zg%#G~V}mU75&Y7Ud_}3XpKFz!7f(Y%-}+;}y_1yIHnK`jR5$h19F0BeW7U z+Yp&g`)_13)TL|cOwP?`5^$}2Uw+ebXlHN0ZU0}hwmu|@1&ycG(ox}PLyXZNsi#0{ zTL18IY|X$N74_~O+IHsb2(B*MjUrZ^6F@@QFCA?GF|bnq+IFwS(Y-`kcu>&^wfNJ&3>=5Um^B7&QOI;VU=#&j0LZ6|Y1Sxua(7ykj& z`!k8`+dbBW>?~dv`87Tc5^n+f`!f0mXxe<0fscNjnq=G?H95!yGux4kvPmuGPCo?h zdi^1c3nA&)Q|JPi^O7@yYJ)T zuN3bcQ(9!4nPPi)J&K7IexAP(vw6@pB&%%MQ@-80bTUp1XL2Yd3{WZ zPi~5MVu@x7k_{xuhrC9@^;z|tCy3&@h~s*x1}ryuBzRygCclBP^C1KM`EhV{si2b< z6BBhjVp$Y4lYf1R88kzEz z9pqolOE0WxQxeSg{;~X6dV^+|J=vB6OmayDbeeEKQeAPkr`>K$GM*4&xOXwMPDe`Y zlf!#1hz_(C*kRzSAlA7Frc|Am$=5E%WCKr4uiR>jkjp8AQ9Ou(^CKx!@pw@^IlG+$ z?PM6hA$>12vq@uWwtoH;zjAJ=THtjJ+&TbplgI6E>d>yt18p8h+_;XS#YTnKAt zJu_5vnFooD`XWcnSY~fJ<0xM~qfs2f!<{tqoat2ZseTJs0AuYvL+#pjB)F+Tnhd(gri`^*#K9O3}2sJXYSxy23t%vhw~a+K$@_P zS~hM1p9I7Vt@GFHn6U4I(HSE?Ip1DAc|;c_0t4m&Mlo^~a+@hb&Rd>^ixo`8h_!I) zpHPW4JT8$uk-za-MsvPTa|~({Q1d9tJ4cvK=080+UO{ymRDJ}#vZ-JR_A?K+O0O}+ zh7gE}`=L(iUZa`ICpfIw9xmiB@?YXk!`X!#z!8RFbaE?B3kO-frf;lQ@^5_ zA$j&|VOUk^ZiCL_mB`ZhTh0M5_Zc;azUA*<-#LBz@>q1j$>ZiWx)btW|6zVFg?_8t zV{L42fV=y%1#=oSvantB#^QNep8&5Lo~d#r+Ai6517xfn=z{GoMxCrRH6u_h6q+}&_us?0 zo|QPam##`scc-KB-*`io_daD(@o+`68+z(z+_yTZs+U|3OP6R6d zv0vAuJzKyl?mI*Jn?)Zgw`GVUK5G8C>l4s&W4?(5(w{!)>uTdqMTQ|rOcz~qo+ps& zPZY%0LMwG-CBw~E<#5m>eE9xxdt2L-kSIB0_Q7Tk%Q@d1a+>wc%2gk(aw@N;ycrpB z8!wvTIkjQ=7{4ZGqJLHYZ)+!Pk83pPE{wKfq&FO+hi>JuXP8fz8Al zn;x4O&!-IWrUL^s!Rl@hfhM^?8pd&_v;_}AnQJZ#=4-UP=?T#k1m%4UDP>9r_yZ-K z*Z<*co|UXz1;TCUOM-s0Pu-D0vlOCsLZIW8;8u%Yya1hWE`Opi?pm;#KZy<4(UJS2 zDWWdYky&*Mp3eR(KR67$hAGY;ai|diWDW15KJ0X0h%) zDZbI;{8^#ikap7e0Z?vIt2QO?i&umdqtIA=)>4byOo{xO{% z-`{d{j7Z77jSiBrmp}F;Qfw}PVo4l*s*dya2w9IBMcVjscT`E!r)~S+@m(ar&`6cf zLh9)6tQ9hdJc@@d1}|Qy;plThu^@;K#}acyQ`*U#nxLK!{5XlP^u5sEIVRHhM=PsT zMxa;1iMEod?X=E(38#zFkb*6a<2#fh8cjGX&fR+S6?t>IO}92+i)^0L;_Dw?yIzWc zudT?BGK-ybdCtFu$nRFTE^5Pw`0z+W=9sDzD%`*MLZKP4PU5s)om(SKtt@`F_py1H z?zmxW_=^zs?%aKmPUpHA98}s9$v>X9^{Umw>No7>)UCZc&=`|5vhO=bgj!ddD#bT# z&RIT(f+8QB9#*sqqDBNB(^QoBtk5B5;t_95Iaej(b8KF~c#ajP8CeFoIHaDL3-y^m zhzqKvyvvchn+O+$HoUiZ2kYol^w?aE@G=bs_wl$Q@l_pTfHWpW%z9!ZoH zF>C1-cp7ak9wZ;RQn5?%pSn9$o_Y#s+C=TkMy+n|+2V_`-I2$%A*6)4=4)T( z*UasfqmM*N^f$BzIAXIcowt9{OkN1*m)Ww%L6j)_-MYOu7QSHJZ8a+X z{fxo5x4OE{#xSr`-u&BD5K5`jrwT^C%; z*Z!1xU)}c0+()&X35`#<7d2rDqp|BKP!@5XbLx&gHCl=(O*%v_olM z{BZ40w~Y%eqwCV|V29i+uVjgn`CbtosssBr?cH)cru@}#cXC+km~+uHeV4PUMCKkt zsD%B!%o-kUy)g#5t}9W8OA~i(JiKC4AROSw%^ki^+}4ri?GU!|#nMtEit7>mQ24gXQP~L;ZJ0yM0!zX|s2D`M&OGvj0C%8jMn(e%tul4aNiNY{LKP zK>7+3k1U0+%{I34gS`(~XjSg1jrr_-qdwa%yLFaECAwX*klFoc8?mOS;_}wjK{srA zRF9?uao&C9HR&e+IudzneC)3jg@i2&oqo0>RfV;vW%0&EHMNQHms8&loQQo#}a{=QRICr*;@ zj-R-gv)tS9L|NpEwB>wfXz6F+roEZD3d$uR*hE)2=rTDgQT62C+l1j4Tz@UkLLAIoExr+5T?Xvb<^Q2dE=QK0S z@9&SrU5g>_DZ4D%*D^d|+TrnaN#O;z9NE3KM-zTIll%LT!a~>AKbW=ASN-W?=DRnd zcf_)4>((dF++r2E4dI`6uj*WN-FerMgV4T~;oQJWKeu!C2lQQvc1$Bth4qef{>ZYd z((-1Jb6HVx(Cwf@fD8@y`FGatYb0eL3N$8JuD%*Z65H>r{@cxH)>jfJ0EeFV|I;3Oivm-GNKiQY?!Qc`cYnwq7uZHc(=LR61tbp{`|7}2p9PB_QA;kn7q~Nnmp8YMc!xb| zE^?ow@{Nss{p3Q2g_1Lz9H+hy`Szdd24)75WMKLosoe#|hFi6N~8 zwW!5K{CHrq9y_F2l*PXh7)!hTQTl$>zq8g=90H8~7kpB4eX!PIq5e7&OkW=6vZ>{4 zrJLu9QVZiYm#uic^NQ|K5xrD%&}FsSvWocta7Ah6?cmc-_J$X*d~C{fHo?EUM={N? z$1tmR9s32nh|Ff&LK>KzuFY;H&B+$jV(~T2%HHGgi*G;i3*)VH+7%JVd~yPzlz?!8 zvef6?Z8>vM=gZQD`>aEK#qI39*~$y*cYcqHa;*3tFttZ4t%IRGg{SC_vNqQWekJDN zxjmEx0cT%JyS}1nMPWNbri#K7^|_c!5!{}{iS@|VL$&Yx-iwc+QcK4#ReBh1F|)WR}ys< z4MRFab>x>C81gsmX75)|ND_o=!SZf#_Q!!3L3~N0<$;zhVIND|zEo#Ti=Kw2Nsr$T z+j{=Xik1fs?T%^rz^r^)Q}&`H8TpvkPw3HC1!i>(?x<~X(YcoVoB0B_8y+T|<(EA= zt&hcRiK!HqyUY%8bq5=%D;55so>ybeidU(oPcyz@M?TlN*3*-r30qXpnbCx+3fHup z9ycKRgi`ea*O2TJ(`#g@fAB{6non1G(ADs z^p0kUQ|iTG<-T$L*Ck|^u$$|aSxVElU!>*RRjz%&up&9-X8AK{|DARZ-IV?eO8NNC zn;x?I-LtEW#BIptQqN@Y#Bq@mYfu_l61R8}M~~6ksr`6d_Yt1gHDng@ZK!QLXzUmV zI&zS=-iN%JTj)yk&lN`5z52O7{)hc9SK^@Y;MF@dt>Mx^;wz2MNdM&BA&BNAJfgp( zyv}%=KxrIc^$MnXrw=Z%cGC7kmdD6HX!>MP5>` zjzZpp--Z(6;hlbW0J$%JrRTY9+YP?d4nqVimXDoC1tlBQ+z(boTl|3S%d zbn98@hQ(N4=`okptPSloJ%Soqyl|^}Ug14RzBSfr*V-;BX>6Ar2wyf84MeLQ0H!oZ zLerbEJ8>3FYu$GgUokq0P+RQZ1+Y25a3qdbA~n3u4C`bkzsXI)2O5S4xrepsx@ict42laHF<6^s0AbB|_`|IXUOE3T_n#g#NP#@SVN z${4hqT6yTvXG3d!4nK7doRNM(w)jU^cJcOxavl+YSX`)w9uUs57J_>*BT`&z=-cSI zyWO6-q+<|KUy%)DtbmC54T8H8dnK8AYy;7)Ng`I5YI`&X1xrqFHmK?6w~)w(0G#wjL=4wqj&U( z4H2kmP(03xJgvW};4i|hWF)pF+)YCL5Oi6;GicMc21k2;+mf&W2|fKy6g)HM7@qNg z?4onp$joxx4!ca|3*Dw(F=(U;G=h-{vD8SxmD*vS0yNgdSI9IU7HcWefAywfjFl_0 z*r5BK+@Rqm+hg$6L?2}-_FNT>CeC-o;Zgd-sR($Wv|rz7mMlnJivka`SO7K4+(?+no0}&I%o8}ki_vCT;kC7*QlT* z*XOY;>}FhM=A=Jo*P+vnoZ)o(UjTLV_v4nIQpD1SJ;j^Q+Fa=Wvd46DaHh@yK)sy! z0?y68>2%fhIk3F7TzOj7pcY#~`~V_gS_*Rr!S@jUnl>Y)3GTs60pe{G`@Xn)HK_|43CIVbcXqg$HhauMS8T)fOM6=LOL)z+4xY?zQ>t5 zWlM|rl|^6=BK1|A$_g_`aVn3HbrH^!HMe;e%i_9t@A!7Z!{f{gF~`cKA>+}V{33kpsAbu4fbI;h=|hc-NT?u&I-RlF0I@Q+)BI; zSbok}3It%I){~RQ)2H*$DnRvbLN#1a#FV>Rf!)sa-kC^d;eaOu>$Bv%&y+DE`u`CK z5MNQXBNRNU0F`f4qw&}fYtRo>0ctq+3dDwEN^A}reptv4QqWU*%WWf5?fwzhh^okM zaks}|Weh@uDh62batvexsla%ZbS2g(4z2X=+^-xS3u%e!=SVTneJP6^|G9C_C%{*k zrSGCD*n#SJZ}{yAGlC=h`@~|LrgPvl$Ufg9UKW~RcTJO0-)*YlrB=`tjkNEq{|C=r zTxCMNq;ZD3&VD>Voo1LX3CZY`k1x|3ulp`iy*)fmU1rc8V#}oMc_*CjlL2)H6lIp=>*4EQzx#X&4zGrlLw(Sn6hZl z+LMG<7?H~cKx-8o-o!T6jw*D*a+*~ko6VuHtlczpE@>N=ErOAsv#Fjw73%aEHJ zGr7W7pYVW-U;h+CorxVY>bJ&msN*L*Tote^vonCvAnBSSRL=*FexuHMb@8Fs7O)H} z)cOnS`pRUznCCREKTmF8%?2fP?5Y#0HjslB8|@wXGINf4Wes&v7cQ%baku~v41aY$ zEWkw~v;o@q69+s_i4;tIHH^1laO4ZN1Cn~up1a@^YHW-Bf+YdU4#ILx6CkGna=VFy zJ0Go0Cx4Ei)>9S{eriz?;pnuz;Kbc+V*UKbSfiVWClI6(Vd@lbHKppX%NkB2LaCS= zm&G`OQ>z#jE^7xIk3Y;A%w~}9+f8@vfnB@D^(W!dIl&)KB?{mDCuSqVHwM=>a1*IT z-a|PG|IPB&ZY!oCZoP*&8{GE7vsqlv`pa7=Dw)Y(=GSV`jV6WLxUtmD2 z>E8CUsr{C}SdOdmepCjoYvErNNH#DhQbtQo?AdyG@8jk_GRw_MHVLKIv%DVO%?~+Z z!*-?#!Z|TYeFH038>>&6h&ANmM}(Vjk?s)8qV^|M?9zDlTGxw#1CT!j6gU}?AO1we zT_v^-s@*yh&Z~2!)825>g86d@Q{8%KuP!cAb5hmmo*=*pc}~>gn;-sTC9a`hNFcAY z&}l36&t29*wl_0q_dAx94}?m?ezh2EBoPGyDm9dsl25MH7;ZfRS}zCMqlY+VxV3L9 z@^P%z#euAE72X=0;zUXsQX!K%_;aya=T1*SO~0)%X6rEb8R9%M5jHpRLI|g(hcX+# z;oEA5^}Z2TQ4Q^Rrg0b_0WnE1fZa6Ccc{=;Ko2%!33gh2Lu-rc^Oci~PhK&C6r@mS zs^m>E!Nn>{!d*ql=TyM*l?eg$s= z4pGry3%}|=%D9G=t1xa8C856{9RvM}J%x}~GNjX`g?WVQ42}MRs%6YrQxoM*fjuMr zu0^>tMIWan_@vp3vn`EX{H6k>ix2N_NL1*wEo^2E8&bFny~IgKe$_X6`fK7`2FU=@ ziueIENps#&iS6ddFnwAQ|DU)ci+oScZ{l78Sy=>M#~o@^+f?`K9fr~~*Ql8nIM^6u zx8i=rN!AS@_778~u5PU~h?8)i@#c_J&~dRgp2LIfs%lraoYG~R4JUuXLT=HM!BbPy3 z0nc6V;d+N7JtK3q@XFp6mmdv74_8EPv^8dGD;kU^Hd@`mM)jV2zzEMyDcuZHYkgMv zt@S(R3lPzKHzOZON##hct57(6SKI(Bu@pIPrP*Ddl*Q?EanSSGJApUioo`21yQ~R; zyDBM(*vj4{DT&5vWXtd!FFBTi!uXdK%hYLwTJaPgF?KTT;Tau9yC^thi!%{^N$j~n zX{B|q!EZ1mu`0|YA@M~ouvI&>N$LslR&-$rQ;PIZs*psU^x|h?XcCLocVM>uo&J)G8T)g!fxo2}9;@2~SM=VeVP%1DPyq)FQvT=U zCO3k=yt<#<01CI%SK2(ZWE(=cd^-2ep~m%ViH#XNNtw-B`va*ZUgasS?egc86wQ`H z(Z_E7de$G4MmRn{DVVE`stpQzd+L?H^u{?42?8hh7p5WS9mr)Y7N4t!azfIl16}~A5g3MPoMs-jrHrsq&dBojtR$9?)$I3WfGuct5A~vq z0!p)BRGYaG&&gvS(R>35)4+WFZ*s8#@hy%*n*MjTxE=c++Xl$H{WvpFpOnSkgYS4U zxZ?RU@)b@Gei+I~B0Q(`^Z9?)E7VhuHyd95>GihQxrs5moa*HKLm}aDS=NTb=^M*v zo6Q=Mybiezj5x>%vgm5@=!QQlB1^l&48Oo!d^_@WC^V_8oUNtBYuD?`o&0;SMao}K z@D>X}9@rf(QOoug7cqyrW>@FkGaI+Wpokx`(0P zg!itVn3ibVgC9=8I%LM6sc~5wk8KOI431q_Kj`txb#GyLu6g$HrBG{m;UF<`mndyFFPzBaXOGWKmU0exw54cQQJ~|{m59? z-i%RM2ht0{1$s%SYS-RJj%Q6&ST%Icb{lH~^5uiSe}~7{_-rjS#Z&&bZI$>kd%M0! zo_6$Cq;ykT{Gw3L>G9Lc?+xW1c=XU_y>G^$M|X>*jn0QuWtFKRZ69jnGMYo-jWlf^ z^O}&G&d|hAbA+??mpE2~7M9PO5V?7)uqkrJU1r<`>%Y34U*=>07*#qhSI~>5EEa35 z$@vpJwF0ZSYakz~;Ti*r53x#?kl~#;QE3^_G>#eJyS&sBnW>M#3Y!IzZZxenA^!wA zwM(-*#`%t8{(G;nsvv#xj1llRx!6Ayv>}u9cFZ3=SAi+}c@xKDmw!6=gdgL^*U$*8 zi}4yYf*yt?XfGIcsjtDAHP3&40@!Rp2AznHM5aN1g@^A$vf`T5B65*#^i;9_3=2^( zBFPL}JLtIVTd5=?810F-lOK-t=o|njfRQ|2Xwwpzdpn0Gmkg4|pPFBNv)`>V)~D;= zSigSV?ytRML0QiCpFoTkseXPOAXM`%yA4)oa6p zQodWHib)$F(+F7$`ZHMeQAz}b&BggJ#o4j-98Y7_f=>Dx2!aBx1?#>h%!^=7XE8Kh z0r9V^Xpf}nT_Gn}DwZJ4l{GM(K@rB+Mxy(0 ztd^(t8o{j?7r!30(&rKm3bz@dfGv|X=rVFmND!0<&{*W47#V9WK5}ZluRb%~y;n)n zM@F5EGdTUyv4~!~N-0)J2IPMyi~10^DOl%7tWkF*RH#|A<&`CsP?vFc&_DYmJ#`Cg za!Qm`bw2SKpB3?w`UAOG-6c+flB6)q!lJyXm@x#`#`J|g%yedk)d2Qc)s70PeXWIA zyh2i&auxYt1k5tXC9BCA{XNbawxtHiW*A5srY8xrO7-=b^a8yYbLuuzqT*}>lTv_V z|LP~tqo5s*m)B6I{zU9TC2fCABTwn4D)K{&nI-y|)rw6#|H;a9dtZKcGq2EVlF1qs zLz`wJ^(l1)b`1TEx@fopDL8>Xa~Ibc#`0Bepo8EoG{r-JjG#_&(OU2*1^(KVJRh9> z?<~J5N|LP$h4`_RnV;{n`b5Cu7qN!1o$kvhjP2h?=8E*Fm1iFlDg)@!iai>~A$*`N5|Z z49bz6WD=yNhg-3}ud>nBg#6GKFnx^H&L$I{>KYC}!gL-U@7KkM32vnQO(VKIF_I*s za?E@~eH@a7ChV`(;^edW4s}9Xb;12OX7tL-XT^(7l$J#YDGc|@RF1V2_ zbv+#P^|cQB>cDFKf>mZ4HGk$+FTUw|>1@`916O{zr`+1RuCc4z&OKyR*WNLIXP2#& zKAJ2O_ubD+AKJ6Z_r3pTzx&89(()o7Fw%OB(;*=q(ErY?@bBd%Q&+GzYj%Ct9=XDo zF&|5GvV1%+Div>P3s1~B8rc<7<9gnQ6+oqQN(W+IZ;&_`=KLKVSX!RZL|AKY!AiDq z8hPp*W;l9W<=rd0;<7aqTe3xeX0TRfTH&nbdt62qmEL&b8(QWm!%ua3J-hlh!|JAe zRtoOrd#S7Mu)V48wxCk#YiUTC{l!hL`uHe^8%z@OY0KM^OJ`2a*N%|8sZy_Bt#2_Q zqjt}mvFXs~N-~imMBK8vAnQ`jKfR6ivAU-8jgrmf?DzccAyxLS^bBvTyv4&i+%|l9 z{#&x30xsvdh5BT|H$*MzO{z7gY5!1j>qZj0%`P}IdXSn$Ml}qO_WYVP@TR-nH#~A8 zBsIS}jLHtw3G0`abf^H&0DG^&D`Cp?uq0+u{a;i%e$T7M?`s>x}8+7Pj&a zoWoCMoV(svakB(nP?_#VyXA0mlAyXp3Nutqk*)ffqTmAg&gE-;33lRdrx3e8yO-cA zd%ifyUZh4}PA5$17)hk5GNQ_C@=T}ctD!&*<8n}`Nn(Ne{qm&a=@0EEJ_=L9W37j1 zwIldC2VyA(IuhV+xhg@D>&`FOut8&nBlGE5nnsl^H78 zWp=Rbl+LyD%|k_%&{wrj+c{Kr^7Kl|rx`FGZ{D7Y-FE>MaR>tmrgk=H?bP2>1=OKB4Jd0bJB0*c(n*oD9h2Xs%Z2IAf~!KK5Uqu+uobk? zPWV)g`OqJQ`23_E8a*O?A4<>a#-mR%e$aT`e%j^Rd5So#FUO7}iHssj4gXz>b~e6O zlM5-`VuGf*EbbLu7Oo{ul_u@GeNglGrjtoxLeK-{Po1I9?DH$f9of^5#E)2^&FhN| zSsTaoha&HK??-+=M_M#{>P-)7Jb2l`f*L2ZweZp4c{08`uLg#Hu3^*e!}p_j z)1NCHwfS5W&zbP2j3Ld}>-O5jZjVfzGH&%faQZ-smpA6lFG##rRx(An(R8FKn`36f7_0xCiQ{?{W{mBws3^V?fO`+Y(VM#q_f>SE+AA$c zc%aT=4zn{&Dcque`JANh>GQUATYWw4YZ}S!UKf=r1 z3yy4GyYlsmSVlmm`)w!l&~w_%VM&N@QOwyWBGLtt?F)C{8ra2|&;DMY?qsCh$DD{v~o&`jjBpDBCY#Z)ojG0z184b^ly4jJ+klPYio!219y}MrRxtwUGXj-&I-5vr35R0;bJ{%hCk-NG_GoVnu>piK>^_B(k59E6OneE4|j6H#Su;FiHIlJ`R$~iDx@L zPn}3Ld<~L{Ci(Y!`2F9AU_gc-+(G$% zs9v0Lb8dTa6zLl|Mr`ye)+@%}%vv=$q2J#3B4q^E!DU<^nwA_2pG1*98*bT+mCItq zTu~=v!;D=C0YvtwAeWBbEOmMnu$Qm=d^>@Dx8dL*j_yCSeB~}t_JkSgSUSTI+B_(Z zVe~Uy-y7E4pGNold}Dp#U;R3UI9r5nrr_%^YPk8)6Iqq%Jf$A} zvB_GgE-!hUfmRU&zoTkIl0#x|XAg%xEi8dO4r!fu%Y64vp9$Q@zBef?f3BejKfGd6GC% zzuE0cG-;;u;DwP1!MXn3=y81ofMG_vEM#|$jtmriHa9h)u|$ZroKhi*V~-D`Jo=k5 ziX-co;DVu1_K9csDexzuOKzi;7zf5HO z1trNLeHK$IE)4MT?*OgvL2(Q3lg)AF#9?@w7y|9gYWQSQEr+mE7ewwao&)H{-BEcM zJ>E+Irc|(TS8&`yhO$K|K*p7@DUCLWr85ouix#4#9MCwS@)=`dWm%s47EK&aKO6W@ zUiy?d;`laW)DW4NY?n!?nk~9u>GZo`o~Tp(V0HGUqw_UdjNk$3lr)}Ke>WrU7Vnt- zIC3I$+Au6ECD++*lF&P%nNB=WI`3r3^LY3iv@!663CLib5;lE^`_tbi&ZdU&%!xP+ zS*Zl+hm9g0Hy$-s!nge+Wb71aRVtv-aYt%!LJmZSr&(oe2YvX*&YUqU9){at=HrJH z(#ZS_x&kMA-Jr$k8GcMY*chY(@Lv~3u3-QK$mN0g5#)^OAF1;q?|w(QBsCXYh`gc~ zkV|)zBST$u@?OA0Y?NP4&KqEqrq{X0y5bSK?N#dZ9xvWm5U;!qmHkPa>1nYN^mY4F z|C2Yp$g7=w9}f28N}D>yGwx7>dNSZSuXj>4bFYDLiA%>*^d#F^f0Z+zn{2V2ggsUW z;yq@77^29;R&<{}cjLoDa!V^^tm&Gku>s0(3&vK}Yqp0~>JC|Y6!{~Ob~^y8C?VV4 z5}zr2?^+C!$4x7#pwJHRY|jV-{`B`~(%@k=-U++#!(mu2Bk;KsKfaL{hAcA>5OC8j z`mZ`+{S5n}#Z;EcP`lF;9RwzcNje)};(wBSk4~8x*~u$O2b)ikDR)Pt_<{u$hbV05 z;AF(bca2p|7C4TPhM&nB4aCjlGMM#Y;cnSoXj4AtHRoH6RdzO)t`c9Ns^4Uf!3Pp4 z0~8HiH$I+g>K2FFD=T^3Z;arHE+7o_#lkf{oibL0+`2B`t!DCjX!+X})#Gd`rzPe& zqY9XQ!F@AVL31kq*CKraX9s>pD)R&LZzce5&PXX=V<_TDeRzERLY_L$X=e)wOoZBk z&gXP@BY1)~HR4b_2(Vm}=xK_TA};Pl&ci-|nv@O!uh5mrB8&+5?C>;26{1P0i55aX zpn=vO_kV)ERYArx?NG-zifAEixYF6A39S~6Wp;D%G+!U%i#DVR7Ykb&1e~fyI>|Yw zl%?xr-2~;4lY`C4sZlha9`WSy}P{muW}!eCUB++p=*BHhq;v{U6NKRnSHBDYG5= zFbxndyi$fLU9$kJZZsm*Elp@Ay^K1#9*^yvSEQEJAb98uS@i`s(GhLEuVh#bc(Trf z=~70P;=L-zx^|8v>M1bR(kP(JpgySfA9N2~-+({M_hbqhm`VTmM0tPGg=H6y5@-+g zZDQ7AVu?EXdAtH4j19GJYI+CSX9OzTjpQc%BYPTQOYD?mz#fii5?BVcv=oNgV+7w# z?95{As7D0eDsRm$9rfTdb(gG`whVWuLcZAO z{FSB%bP9R!PZfP7T8&7^l({jY8d(Cmf^e{{AkFZ-x>an4=fF~6rergONqFUqpH{NP zQV4l+it$7u_c3v>1wbS3TGSj)a5}@V#Ly3p!R|+-I7SRR@G$y5q88XyJJioB*HAE$ zJFa8&K)e+HMYZg}UFt{lpQBf)QlBJ37~*1~Mys%7lVAY>5d0FCt*r81ei97y$4|Vc zoJWizRWu!?hBq6!&Sa^X)HbBg6D%8rE(45(6C>bL3Edai5Jajkrh3UF$|}h_i}m{4 zY&`a%uUKPB8geyZRW8&L7$4VJ4^8Tf@P-SFC+kV z-UVPCs(g^p=XQByo&E;=+HHQl#Z#2oc{jWdB5u1$G9FWi|ip? zw6J~r?R39(<`)F|v8kHy`W(#;?Hrt;IR)g3Ln7qj1~m)=rz5@8E17F?Om6E5wQSE> z|A<{M(Qt~h3JiCDX30n!3jW>Sv%H6ZY#_}z?`L9ca{tR2x(vK&Zck92g{Gb3d{s<# zw6NXpfzL(jEBJ&W3~U6kKWP_HbpTK5EoWA5bD0~)F>YmB_b@xgnr27l$}&#_GyKYqE|s#!d*1zxYf6sLs6f>8>pN7Yh0>y8#oeK$q2DLO3`0E zGCRc4E@_4$@X<80j<{9Y^>TYA3b?CwSUFfcmNP%k=tdt`BPfejVk_1ED|2O5%? zMpUQj#I<^_#LdRLzQ6DB_{c@~@-=_Fw-FS1?{8v;i}&30E6Xx=?P#L!ZhRGmMrFaM zr+_zXR~W4O2|wk_#r+Whf2dsT`BRyt?s&`I_ASh1Y@mLs{y*J%$!Rwk2d(Z=bGC`+VWGmW*bMx(+}~6e;fKrte2Q!M@2s~S8ld`8_1UnC z5el5=O|XvVji)L#R4o88d^_w}#c5p>dp|yy*M;6kt1I=_oMw!Pk241T1X7fy{qL5* z%Pn}LhrdOAlsWNlo*p?=-#3O4!YcVU9rFx*!;|axoT-Q`x!-hRTGu)zG_N|a|K+ds z`#0p!WWlRm2ZUScC3(m@$v>ob8F!H*Ba92+aAc`9=M>6`sf5-uL%GPlE9q15p0Vz!)mOIp&9MKtP@?j2p z(1e-jk;9u_%-*S?Rs*c~g|c_s%&p&QCh2gQt?|Az0TD&T=!hlf8!9?)r|_q8uoBso zElePonEm7~hUO=<`n?9ZgotA%xuz$m0wzH9jdItOly;sw}eX}XK9DVKWvY7U2gIVRd<84kqyiqny z3qL>jOuEA}JMGw!k+E42bC3MPy?wTJXK zx6J5X-p7j+qGXS(9P~Z&rHnE&HMcDO4dihbG3(K%)aB$ip~A)2V^JwaE|^do_PEz? zic(MZxLi}WRQY7#pO8XIEoB*wDNZ|0sBc+2-Du>j1NyWiiZF7ka+oynh7-@q@bX9-VX1#fzQ}0EZznGxvg_3E_hM;&(pNYA1&tn zA)Ho?pBiDL)d#=3Qm1E{U#aUIbchPzw?{1W?<(lFS$!pcEc7N6JyLQ9t^*3@M!a`v z#^iT;xa@lF3$2UGSH>UG2#Ae*(8-~<%_7YsdMTHPFTZhwCLPh0ZL=>Aouzhm)}A}l z*}?t{nlo0iKYNSx{v)nKmsC)DsivVQ?gWVKfBhvCp6-rQkOX7WlE}q&_-uK3ZY=dT zm~HeDJpKg5-0y-Er$BW}Pv$MCOlRKXPYm8ugYQ>{NLyb9nz;z487)Gq#7UDr_35z> zfm4hPhIG}kw%vpeHh*FYext728Xj8;M!b^oejNDN;qE~G{cPaRMtzA1&GI*l2c^>of#`a|jlLQ^ z&X~bH7*vg=8fNvmU*JJCdZh0pj9pqKV)i|Fj&2XzIjz@EsCFH4?lFvIjLYyxASaK; z$D|=M#ywyBw}c9GNsw~yMyEtw3}?;PYyJD8Y}Z(LIo|r;L0aChc(SE-JW#{$0=BQ)c&>8r`<$ zEWfYW@@kxzj1Lep91hrTkamA`IXv>ZQwyF6wG-CfbmDS`1MD?L z2qf%V5D0ggs20+d;xmPbci7=ny(+@xwM zLa`S{5nHOL3&^$5`go7UlvAhbQUXXm%&uT|b zfsOb`9mLl#-7&%e`f0m@^hVVIYxEgxBR%g5`WCV|W8~jiFvV?ycUHj>7T4uMTp<8s zxo~L^Kn{HyjNRG7^gu+fRn}wlI7Hs$88<^*P($r6Vd;6b+;@VV$aDQ;q2oZ5Iojmp z@DK$f@5~qZ6r3S@X3QHI;#iTbFnEdl$dDft-onVP_3?N`$WBkBklJmI`m3vw^VwZA_X)m8ULD7Br?<{hMljZot z-y^ayj5KKh)htwkM`*?EDpj|zoH?-f4f;-VgUU6a;baPTv z^TA4)Pprwbo6tKy@krxU=;TVF5ak^|UE*Haw!^0TeW&e~HUNiE)x3Be6|w@`3LBtr zeS0U9?QBt{xN0U(?7pTLj?;NRInn_yK%5MWYfY#_l?71Myc_AIbU-W`*r}%1$r@m` zlNf_);2=2A-u!nKK}Uj-WRq^*S+r#%Z2CuA!Xfd9#%5f5--lo`wE{PoP(zoDYk$|Rg@n^t!~KG^uaKkn$nGEvM98Y;Ndwr7lhRWbpR>YDet6mbGiVX?MdM?WJagBRyRC0#>M!{;@aLs)9-Rr5xM#2eAxi%DN|jwukndq+YKMsh?@CwM`f@Fpnp2! zk?PN)zzK9ZiDxt*l@adWMkogvDZJv9^>kw*8W-fTa_ieOP5eV&cHyD()RPIt6hoix z6C|~wbV)f%Ym-fBR`@SvQwA#Bbrp;FbZ`{e9~*KIG_f@4D7zRJ4;Gt{o$$ z`_H{WEEdP~T%PzQB6qE|;~lAW>=yp1GR$E#w&;3!--V>ni8k5sab;N4nDd5~>T^w< zyVurc9xS33M{gdeaTXJBsk}h~3Pi4#YK$wQ(m8(H^skq&N=FLNPpgT_+;S*@ISBpa zF(`B{-qwA63MrOLIRAumze9`ORB-ETs?lo23duKxE@R_k9#@> zBm-?PflVta9{28D!WsEVbM96)Y}g2Q$m%RM zjXl?$O73A;gqEU{OJO1k;vg4XdG_ymQ$9XWjP*~2pN>F2qHO{dHqp|t*?$o(y?;wK|;TP|$2@lS8>2zF5Rlve=kaXZCZsGqp(SNn1 zT#mlG@m={3k2DUK@|~<{grKmJSIr0`DhwatJ+bKDXUUt&+1fL>k(~A8b*o%O*q_ki zfof%fIPPkwB4MTm)m`W>SidiSojzmaRH^ENj6m*VBkblD_(Q+p7OqgriiZ+b>5f0l zW_xKqwp!{Vhp(X_!4YtIy6m_Tvv8chs~J6sofj@FE(7&GwdH0H{HBbsO*Zsghujqc-L9TppX0Sz&faL(y{(jApZZrXkCDfvU6 z2%62wN~7^Y>)+5Ysx->K^#OeW?y$E=J@hh;N3OKU4-C`{t5$k%G#1*gCq*5*MP=rwLg%U0Cfs8k818=pTPy8hmeSFRuauQ;3b0X#Q(cz+ zRes$)tbW?#eoiAi?rx7f_O9q~8EJ)d@FaIMs{M`=p|mr9bm!unz6>VD$${y)XSt0; zh-ac~uE{3SUuKEj?iRVrDG8jlO^+}49(kdDb61OwUjqDK1#91ke)AQ`hp`{qN*aUs zSauB2ji;(*{&-04VI}9QinqhV{EaTMSxxo~6=Qs-3~;e~Aqcl!SOsnaeU?&8#7|qasYO?b;D}!S@=%Ty_zi3nU_ncc%x`>KQq)qc zfnwpu5Bi-WM~>l9^A*+&gs+1svBTFlz(4ZYV8_x+`SW*OJ4Lo|xXi&}=}kcw0X#v$ z)@Ziq23daXD(e$^_v}*HAN!ZWO&2_IL|4^Ia~8`Th-*bM?i`_k`M5P=QKQ8(Hw=5CeUZs>My+Nz6~b;dlX-7 zzYuEUWAa4A!HuzY_+?4iFf|{~YiyvR_TkvD0o~nuNAII}qzJdmF4b!BsmHD3LGGT% z<57bl-q&-^ubZysJctM7t;}L_;q!ikzjye@9%J?2FSR22WV-s1`&s|&p;iMupSzt! za#9T1=eeJRP{JTkcdezR5xkbmHSWc17c>zUd>KB;-Cy;bC8)r7Cp?062&RT1C)tmG zMuj=}J`$#Qgq^c`xsnim$S-kx3IbeTkCVp%PWW4nv|Kk*-ld5WmB?@@I*h8f;^=^h zl*E)f2f1^yEv_Lthqn-f0Vz}8bWosu_q|0Xh7S8pB=rxHa)M1m&ct?8yDh(8`p z!W{_RNeZTxi}1$%4^UB*HgEEn$PE8&^bF@c+CNEr2UyHe^n;lP&UWEEH)kg?G?XXA zg9N(%bM(C<^pJc4D2T8}{>S>w+OhnfWWpvCY^X?ysvV3bP@6^G`C;>>w??%I4iU;H z5sam26?bvhG*XH_e5e7+E__Qg7mBkhGY5G;msH99G3vzhYERluwasm`P*8M zdiR`&);d&{=u`S^h*&%(!gYMJR@PyNBhbsy=%SjSd*QK`rbFGQHM}pTUka*nbF9PD zDH@F;u%`bdD)V5#O*!>kP*Me0b_Gp82Q@%ND*sl=xYl+Axxrkra&*&@Bi9F7!|9I_ zBpT{jckk3`NTbo>|WmJ8XqO6-;)%V-Z@#)77t+4V46sSd*(<3e^Bwg zv0GV~tEbqFq6!>pC{L=AxgQjHs;k=5a37a>?}f9BjJkoK`c*1gip#=ZewodwtNpwD z#*>sTKW}RO>hL&yo+O8k{P89iEs@*~?yjm7rMp?EkN~w)Pffd^INEi=aWp&C!ne(Y z`ufX-H?-u1KH37N{k|tppH8=;h!`O>+($6KFqb}{>i{Cyv=6-#*}xVU-KW#@W!2l5 z6;kp>KQ9%T$C-zr!uZDA|3RFrV|~bSv(GUvulI#SMLvvJPK5ILE9lFLyO8EcL41Vx zfiF7@eTr%AS2FwY>tP4>W zgY)G)IvIHI91u&rx*x_O! zpGxiH;qcRIcOw)m3nczh_qN1k8_(WQHA$;p$%A8MR-#LsE%N^j7_VbR^GAZVt0?$l zTHRU5P*hhr05JW4rlwg#&Hv&Rlw;%@$en5v`-9KJy*ba0pUu03oJ8kC7Xm=&DvuRg zx_ZSFIl9y>R@GV;&lBKRY^>C=$be1wtNK&NqAhiuH|cXqO4iZDKFuLy_UAiG#zM80 znJ8wKGbPB2r|SWQxIA<)?d3UP-=};bkrMwdD|cGbaKS9NpbBvw=KBJrlsJBEx)8Mr z6#yE2*S3#L#r~b~3Q^l(zp~Zz_Sr*9{au`TS!QZKt=6@SWj#Gm7V1AQ(ygeb@^W7P z-Tqpt9c9R=t{1Hd!6T*5nJt1JMM(xWA2kEoDNP-|43c~V*a$O~CSUGb*HKoOySGHi z2s+;Y;jtOv!qu|*_jDXI88-)^cU~c)4CogXk^TJzWsDf`;Hc^rlc7orD7e+U(S>2)rNl?PLxyj08aW$V z+2#0;Dha|BNT(&Pthe>+^I0hrYfh_6FKp`iv*l%w83v(AcqO`t zegU4*B2m{e-O$R~R@fTB2Z->E5^m5XO1)W^hwidUi0c2sDZGdLuR4&v=M>ce_brN- z)#|C=+_p{6!c@T%oZ@{(z#&@*Yd06;xx0|oN3^Sdhre)qR8`$0ND~|My?R_g!Avxc zAQfqwhJO(ybxhTAm*KH$&=Oas`W%HyCtJxUnc2BV-KY?~2T?fL~?N0Li4+2(JEb zP*&%7pqRC@e?^q;G;xcyM@?y@u2OrZy2Z&vOR?L!0*ot*NNBJeD|PCLIyUPCWifRT zzQOK~UpDo<`C0SX)me?8z6#gnqFwE#<18EXP?G3Egp-$56YaOI|6+5h*Skn(D5zT^ z5@enYpH2+-E>KE9_f^KGaBYe%1%ZkflBD-50%y3&jiwlrEM~B$V!VY-iwm7JG$_nrP0UfC}S|S#= zCoTcTkO3het&Wy#@Vy8=gMXoS9#*Il)EEL&f@PGYocHO@wE;A8F?W=XWh(q^lHB%4 z&CKCsfQK>fWmovB7s+T>mTo~8S5oe=mhxoWTK`m^8Za)E?Ul#WUqKbx$>d8KM8q+f>dkzQDM&#DMk~!Mx*so$|B3>C^gP8-bGh zcShtp*t2;S6e4Wmq4>huwKs!ycC_x})`{P60-^=O6H8sh8At zolIjuPLehie7t?;SBh?2RwPv?*E!fYempvSv$w14B5u{kBf|yn!a@h5FZir1H7r_-xgc~~V!WV$HLE5@ZGzCbr)AI5g+j|Uw{`xaLUvl6ftuPaz*9KTsY$ObJ~|rybRgR_fAqwtmc^4* z4+e@v5F4E1{K2f#!A_rh$T|h80;}mIWkPz<2v4mK?c>^nBc>X_#c_&Ce4h24JGab9 zjb3rHF9`E{+xT1^B|CFFDsOr?&qdBk=5?djLHKNsl_$-M9IkK>8jYo>| zF6L7!y7lom1VnX%Y!4z^_2)af9ciAxTRaU3y|3ycf)64X#82lL0<2wM86iE2f|{wT zY=2qiro2EBR&8Px`Dk#ljz1L2f894~e?h_CgH zUqDSYPy=C9pB%zhrSllZFPS>RUYn!aNMxYk8<&N`fvm{Q;$j576&v!7`9s6pX?{PmPCH}@Q3n_zn({?KNC#lGuVO)pV()rD%YEt zAw4ZIVZO}?jwhPRk4$BFGAiDX=}sW62uF~1#nLxX!@uHJP^SJ`Qgbst=Yw+nsD?4! zgrOWComl|l)@bejN=SGG$-u(=E(+6eXQMUip@oh28FVU3MTzBWz;;EZ27AMSKY#$M zQ@d48#*3v?aBH8{07mN&z&vM^@s$d7*ifTozz`nq7Xm2xzcaEGRmEUUzT^`B1w6=J zW2*yj?i_;KZ* z{@Py9w5~dwmXL!*Q&X&Jm>&h}k0seJKkr*+T>Wjr@lyiLpb8hwg%5{ks##gR01M%~ zqm&>o%#x||RVJ6n&&X!zD&e~&g!ZM{dbT3c^(x9$j@_{3(h8#DZldU7rJc+>YKPi$ z8$|_%yq5O9pE8s!m6%EtPenTWgv8koDW@xpE5EM)HLj-rkN7|0M0+9G@PHK%5`V@x zDi_E}6D;bWdS`p)4LI#p5*rv3zu8egQ{hL!n`|g*iwhg>GPB6VJj{boU`}eus2#|Q z2FrvEX6NX|cczw>O;KIiqG@xm=FI`8{++Q`?R()*rw-htf0Yg8)GB6Po!|R_vA1@K zc!(RVFZb5{Q+DEP<#+f*SjN(}FN2hEqiN1glA;u2M2|ynHGTG#9A#)@hTYqfkMmEB zH?;pDRpmY@=Vyt2_A|K+)g_B%MT81Jx&R`6)^!Go=8gI+qP9t+s8OY4)l zrdS#I?Z=QfD4-%(f%L)_X*Sww(m=yJL7jqXg3Zch293tkaa)eF`b?qGcf{``h!$AjyU~GdQivaz zK)vEuSg`&M)0r0EsZK$8ntavG`Ej=*oqq*ANmH6`BAktOd&ovr@2wADEXqVJm7e== zXO9v$90K&u5ij!oqj-n-^`PNO!7@|@Z0AGI5B#p#)Q^SyJm&hhr?rMk+et0A= zkEo{M@gasS?#Qq|I43u8GsV1m6)Uzq`!kgKUh@Z$m*xMts84 z|Dvm%)WZJQ#|jr7*hsxE@9l%(U=k^LYYsYB61rizJKWB&cFS~*KH6vlA~Ps6f#xVA zXX+YwH5m&}qe3x&8}5pSMVOORWPc|Z@6YKs_zn@g3#^%-QrX}aQC8KHnaaigi6QL& zNv`d+P9{9BI+TO&c)$f{&s}b$7zntXC`5@gqzW_nzxuZOHcNna4hT9>b}m7=34)KD zD!<8HtS8?sZOlbEyaq+_=AIR_!d4Xz5cF^VRynh&Cqs{ zRdyrm50i+e_PJf#XX!7f_uua4&X>nGKF_8-P{gzOc)67by9I14Z*pt{{Sa$DmJ-Wn zEmYOp?PxiDbHtR>8+-MG!^`imEdP{W2EOob>SY@1{|s+={mYWI-fc1MyC)_DCw)3Q z%S;{>Yo`+OM)@sv$MU~qP2pQ6kOv~u4`dy6&hyf?7{@M(RNJFLG_%#g_-eK*8q5qb zGFo$1=&gg*s4Ojsv$P4u-dft#@vQt5bW=XdGd4=f`;wNUD5rIfYFAtGJ}kWTDAdoU zU|~vbiSEyhS-;9_8a?Jv9s54G5ezS`KzHHqagjX+r&ZBDGboCkPftNz-&`gqH-Rx) z|A}$c%zEhh)$rlNTRpmeI5(gRqZ(o>%eNGlzX>`*C&8$p*4VZPKRczp%WCq)UHHJ67$V9o%EUi3Oj!o0 z6pgz=9{M+(NQx;yN8Mqlom+s>!!uYaTJOG~mb0*WNNH%lF{LZXD6O#@+ z;4LpLy&%M!D#f251fX?g>cj`j%T!ly+#eA`)FFhNgcX{{zS%NVh9?&H_#5f))n^^g zwhrZ=P-eSa&X`}_BAJ-Hu`5X)>YE-I_EB)@zDj+-C%+)wPN68Lw?3B(J+O$rjJ*z4 zQgL0X#rLi29&>6KM9Q4s!}CT0d5{&S9BM~PaM~lt3jgS((Sm&O?XrwhyE)wt_vD1? z6ThH`$4bJAONq@$Q@4Qej-d668rJVx>ot}CWLk6hm*UMdL?mdoVQ(`K53=& zIIMOb>W<-EqOMfW1|$qN+2?@TbjbR8Ds7K=L*lt8;lMYBD(Dq)kM=D!p4RhtHwO>i zd1SxC3zpIW>Cu&Z8jsI+#j8JwIcu2|Z|>{oYLcgW9C$pAYGZO(A+cHB5>rhevIMgd$wJD@0yzwE#-7sVNuZ#dC%oRenA-7+ey{-Z?Rdl>2WZ*eoOwpGXev@ zBw-*+XfuM`El5XZeQf+k*vGZxpS?PU zPjm2pS}KNxTD22Z=QK>d1rNlSKb#)Jr(@}K1-VjmVp;`TwS=3;I)D}B+jYvs(S)63 z-xIrngpp*GuEy!P-38ohW=fN4PDZHEp!8{on;Hn^eAz*8S8QmhFm--G-BiJSlUD2J zi6;QuT&Q0E4;v#}RlEl-WON`X9<6ws4+@ujcDa1$X(|nQp1)avf*tf2ToJrSee>y{ zdqTaT--~@&M1qjzF{a zdqwTh{H5$i%SlnB8|8ybNrs33>rfp1*59MQI*!;tcfrQMga13jDlBv5`{;;Wt^5A! zf*7>h>}eocUqKtPxf6r%!df%K`>%6l2=pYS_sB zrq=)uRl}cg7tETcZmI|>CBJ(uH0+?qPqeeUDh#pT{kS91?)^**<&jlkb~K@3emi%cDfet)Ik&d`5gdK)n?05+X@poD7zi^+%*i zAxGYKTlJ;y(Qp4c*+2$GeZSi&6xI)2ayYUx_q%FUZ}IYmP`x_7jfAU})diH~mhR-%mv6 zZ7qP^UU*!dLmnqzo!Z>9Nt{D()%(7)iQ{c{HuoCXKD*>?6(1)Qlp%8QLP?^hli6XVX*}paGUODjdFC3!9=^}|dMie8w71)7|8V!NGT;-hhzKKk9e|I}F1`sh+Z4X$OLN{d)Z?#6b zE6*`ZM{K^gj410wfd*-ISO0?K5odgv4Tr+27=WP8$@vH?!?LP|qi&QEpuLJ_+@ zs-mJ&aRu&z8M&4=`2qxPAhY*1a3>qxkqqXxzexVEBW2F}{U=Glz>%!7|N@~2#JHeae z`4P5>BkTl+=-l-wy;aKmj#LN6$*h3#tonDMgujKcb8AIG=1s*UavBMxU!g3FxybFV zwkF%r#p^d$Acf2`^+qtsi_G~#jkya zNEf$i1lVMI0RR+?`pj+9og1k*RIZSV(J#ib$+h*tzit0YJEzibvOl<1P;#7IA+T1jm^5JP!0R6u+*6KYPEvFi}S1jmVpla(W6;HqLSPsF?iNzAF4IJJsMBXZ;*Lizs z)h5XZrK4glXH`i2Y5Ftuk?aT^L`4*iIaIt&a~w~|j{s^V5n#$a#e!4xC0;r!N@s$P z(UaJV77vZKf~p@x8E~3y93L5`>Li^b%g@;Hq$1@`sXb~)P;*CHiCZTmlnMAAL&N&L zf+kOJQL0|LW=dq}=&||#pjo)0cdZJ!oX_GelCXl*ga9{S$@S^)vc-J-ib>gu$m8yV zg{r3Am-|BLv$Ykxr&EyLT)JrjTegF%mkGXi<8?j__vek~`^dO>98so>MVBB!C@9ZN z*XW_|HZ&Z4&oWo}AH6wb^aUwFDb&3qXi4R<$DFtH&YQDH*goY-**IAyZS+#O635&H zpGGE>D@uCK#@~8m#W8ScWl%~u&N2B^i7ea9FWDY+7dLBOKYf~mA4633{QBi#C**SG z+N4^Ukz7JQ$8u(b2a~tXuG8`~W-s8lDI)2IS=$Mon@_))WdP77#oWfTBy;Bg0g7# z_alDpITxezZFk!&Q=iJc`<>+wzS3m^i?j+<4enn4dCt0RCd-DiEDs<5QGOsb_;nBU zNLPh#W%=eCqc=N79!3swtJ&5*Z z=GJVh`FyUs&?&25(CrU+Ecc?*3g1S`qV*;2UOu+U^eRbmvA3sBjmhtigQ6BEZb#Vu+oO``&Qf_p`Ef_@1bkERL+!zwh;jq_ z1}@;OgMJM5t5p^?#-3-0 z21Q-9!50eNX_U_}8<7m;yPQPUOh8-cp8@h?fgP5mhS!km)F%g132*@OJarwv|1<38 zh#y)ZkXnnK-iV)JdHoGjiDM;Zwz_)N%^YZe)t?_GH;TwSRxob=vi_&S!m`3tf9hYM z#su7j;f>xLidaXrycY=WO>zoo#K8*6?GE&5^^JdLtaG!^Jr1nM5R^@2$Isy!zm|Y4 zvZ0u1?Rmzij%g6^Ka)BY&okv^Ax5aV{;-lcUOiUctyFlr^Vh55sv%~Xy#|v8e+z&1 zhhbOB0v8~6yZcS92&U-cBKoK9wFev9%Z%=a6T+Lw6)|Nz$&_Lg?8zGgo2LT1?aRK^ zI*dG#Ty^?3f~-?r_j&dV(V9#T*pt)uVGP|o_0_~BRa2L%t-F~Fnh0lO$wsTX_+aul zMfAaU5aA+y_jo1frLUhQ9)@T9z8gxmvU9Guoy?6D;%{tLt7Elz_y9Yo-?^T#WKX0{ z>Dy4=(*V__0V7>P9RpQh>~OzZ5qrBj^86Cmd5%R`_=J9WYlF}I-pVe3gYE^QZ!t=% z=LMEI0CpRUZoOO8q8BTiM+MrgA-$_`*f$Eidj(E9Y4&qrHRb}!!eLQL%0Q|8wzSG? zG>tcms`#5xY|qSgWQ-2%91li#SBp!=7ri|(5PATumksku1Au^b+eE^Ij31Bn;=7Xk zQ7j$34~1=%UBXgCb_W?smuKIQ<`Rz_@?p@D8P!Q>y}|8Z&h-lqMv7j)<}B4 zK7afezKTfExQ4TMg(I0;um_T^#91It$wce6&)wSpd3M@JZnZX09G|LIGz@hLuaW3WLxS)7zOc1l9(Kv=?0x0pP~$W<_GdNW z*wcB8ZhdOghWf+0XOFanF7~^M_4%qQii2R5VOj@i#SLRrIO*CPOILeC%(VO_>yy#+70hs%=Y_=^J;pxvF8by>3-sOXYGwy@(=?lPkYR-- z3RO&Qe?hj?7!Y&_1l82R+|3ND(9Bsa+MaN_xY%*_aQx6kH%}?W9K^k<%~gZX@50Hv zOaJW%Y5nXy=WFciM&_BF4IO;xZHcDhIOX=yS-)q-2T}Sq8B<_zVy}zuXw=fXeGA5C zVY3>0De55*iQH_n4uoGpy%R=KMQj6BG>Gqbx)E{-MtT#?x%! z@uP}N5UF|q_e7Z1rQvZYi!~C4{%_=Gffzb&=$Qr$@*g+JN3cp#(0EeBs>_bGB};*8 zkw`7oabc+4nd;2I5YIG)q_|AEfRDaBVm2cTTzRat%8J{hkso&-YNTIE<0)|aJ_w@~ zZXC|40?_KkqRjAf)wZlv&xJIsy!zw6Gu}Ew?%}5gw-pU+kBE@#Xr@M#m(yAhFX_LR$(xu89)l1O zO;8yx-`ED;r)M>gKmi&>%!l{4kE&vcFH*z0$w6 z1(cu)z?RbJj0yX+%2v(>v`Lp26#>Kz+sb>YAE#E?dw4hFY$rm%>!%cp~u`@9V{{Ka!AQ7fb-Sk8CtG>m|#ROizIX9Fld0P;ZH*t#3N!HC z#I4ys*Dt$T{`bJl`!@6LWAyMrEU#`uKXJAq>)aghVIe_=>!i~VHz*-!voGg( z;VfkAlrR!*h#{DIXW_#M(W2A~Jra9Ar>CP+dpqdqo!1Yht_Ye6?dr|bjORid04dty zD?bsL9kRj8Lbsngq<1^k9f$_YvnX0~h2C)1VAhvbCs=|l_+Gt~m(ARCq{8w>!_~X9 zQ*UrPsjYE9__@P5Z*%3-{Pci(;!74PYU!qKCejsNeV)JJp)$&X;j{6(@?;sm))XX_ zmPajVl^ET^%2&3=Aftsp&I^!qf0{dR&a7})pz@`x_&X@2j{Xz`=hoXik;fm#bd-tD z+n>wjt=h@F6S+I)A1;gz2`}*sPRMs|&WQe|?eU-YsZ@RM@I2Z=g2!Bj$ed;0AJ2*yc67 zpJ&0)Y?mCGQ0!(?WdmX5Y_s;_Zg(5+U6Z=tbmP&M$0eY*Tr8Ty3R4I2VJAaApdm!! zK`H=~RaRc@$cQOX0V#pvN;&b|ZD(GbuO(W`Pptl`TxdW(pr_DOzp`gS-QD+9RYm$c zITv3)dGC{V>%h6@uBtsJHW$ZKF@0Lu2g@QB3PvB-R^^}1_hp<*Jr;AMLgz)7hn;nC z1ULDizs8vwSS3|@9Jom_EQ40~1$&FFb*;ssr9;rOAuNfHCRc*?_HEmfIV@M0;lm4t z*X!{)(u!|qqO*z5MHPZZZoLFyOcU?$;rJQjhz zcJEU=e2*Y_o&(%{-Uq5xC~59K2`VOPda4m^i_MRllofnYBj{BlrY$#9j*I4x_LRF^ z6RQNh3>zy^7;E`ir+j2^acM`uJ$yW;H-7aH_?DB;*Gz6^%xAhBW8r9nW8d4T`lUQ< zJ^bV{mq^mO{pWU$dCxPW#S60~SmM>2NYSsr!aL400FU~)_@<{3V!@hyQG+}C%*=OM z;3|q~rAe$l<^|rk)kb@tfKab5yCCxreIX7}Vt_!p=cqLWw~E2HnzTGa#hk@P>pg$A zcVkSv)V~vmL3F=Q5phJcr0T(fC8m5*m~tbnB1h#WD5$=k8%q7M{oY&NsIPj{5Ap#s z%m;xc(DXAm?yBXP@f{nPa||$BFTiNZ+Fr~hsgceX9F=CnsF2?u=+8DDM{B%RZ6tE) z1Ybm$(KL>~C1+-BB|Ah|v@+1XpV#F|&R_AU&aM*=R*UKkH_@mdhRWE`Yh(N`U>U)- z+%x^v_V=uh)g4V93&t#Zc2qBYDyZm4TKlL~GfMfe{BpAG3*Y#BPWBbC)QddMn2OJ! zU**{2Rv5j10dFDxJvQ?p)2!i_ZX#zi@GC5W4dW@{AZ(vWJVa!{*};?oTG-^26%4Tw*3`i8xtC)Js$kS+hfRx8Fgw@g4d4)et}O)wWlB2erv%u>TUP^|$S~8b>RZ z=k;CA$$P8SLv;(rm-NU5T^{=^|7a~)LEB4qv~D6Ya?C#xkpalL&D^f*4Uw;;3Z)fI zwf~kgj&9Gz9gR-e?_7qAwA%7c;e(k%r?;17y%u#oF^WMwd+#FOzwX{Tm~k@P&X|5k zaavPWT+!+8AMceMH6JflJ)2ised!1CZR%2T#V76aPN!b`Dt_sB66rcjw=@rEu3Ral zeO8w{as-j<{Br8d=7sJU5qY<5Zb6E7rDdb_ZzdAdR}*|^Ag8ZP(HQ|T6Zr=X>;&(f)tuMMs(@K zpGP-*t_!rPt|)%~5~8ZJci}0#ZGGH##p`tZ4@?amZV6Ck=B@nwJZ{Nhm|mX6yzc$H zB}QqnzeCd`(CYlMxm}}U=zgM5r11X{OL`3We3?(KuUYTyLorilr^4*@?z+k26R-QU z5AuJA_*U3;wmOX~3ap~lr+A}%sof3Mnf4tTNG3$%N#n`%i0{JhsISZQ{XHPSas35v z5gOYku?b1REE|~a)tEy)OEO7hw`MFnw-x(IB1m!vosbs>OVUkkoc;# z(&K?T@!fZOUJT3>kE%rW??U|7zkPI|(Qhni{pJ-31p)GetpR6>ua^>*9JgGV%4wT= z>(N7Xy|1=w&bC*$vU_x~h=-4E?{jXp5ORHSYZqAaB=0l`%jBe!jA9~yjN1x3)Ey#N ztm*I1pTB4T4Xx+Ak|_?qTZj%WdeleJr>>_FpIiHmFlt39f(6u#AJ{oBs#msZ7YzS6 zIi!NXZ#eVo)+(*3>z%7T=ndAp(1l;W_P-I*_<%y5d+3pQxqbE9Rju52uH8lMr|ooS zHtqKOGiTz^RDx^Nv8sp%Ty!;h5yIQ+TpTw8qToH}^$)e=$8GYl78$qgda+18J9}yIF z6qTT@#bb$*k|hRHfD;GM0r)3}d>wU-_Qlk38cexocrEaton=$Ipo{=MFW*5Qr9(#F zI!;ZAon4Y~4pA`n20axGhmpeXR~}Qnb!++f?~MCl^x&3>UDu&|;3mqIg&3`;{_GYZ zol3!w$mmbf)g(%=?RSKh9J^O`&VVZI69SQDN2Dl0R+S}tgP|p-Qbc53Nh^G=p4DEU z<<^zy@jRRHnk^9T=vaoKD=}4zB}$(BW~NPL^Rs^Pzm@1Ec~|KgW3*&KVnLadhsKeS zLXGxbXwD?dC@Dhk0p<>ANxg8?hDR4)o>7R5XHTq)&SMlo>MYFY;p;(E>@b8fpN^wV zqONi%1PH67?v#x8wVycyA>mN#DB4A52pOb+)og^RPN?l@t%S@Rg@27R4KX!gEn_eE zkLQg?S9whRr%N)5{!~IZ3fk3LX^Kiu5a;(77ASs( zzuR4NsbdO-$=u^}Dc26~E$BlJSFa3#LEFhrbL}8jNGL!=sEA*-WU}#=%9fmGT$sbi z4&j(RZ!0n8AsW#a%%Vn0mk0b*lsh?2-&1poaC=l>Z>HUM>g(#&yPC6TeJx0svdXD> zO=f*C)A4c3;i@W2KiuhL6MA7-EBMIy8%xmL0tCXI>0E0``yB}$@AlPQBK~GM=lG?y z6jK(2x`;0o6N^56y4A@|Nnu{Q>b2?DJ*y`DU_4s3Ajp5)87jQm>~`WogkaRc=+5$L zD*R~dxI`BQQGSOVL7Ov6x-T$k& zXM~rpzjPNv>p1J-Ybd$M#qb{n_N@?Koa}Af$-Qto#&IdcEuHe|+C5OG$#5n;1iJ5- zjM@NpxHc>BZzdE0=^s))6YUR|XeeDNx&xIdQ&4Yk995s6z>7b8H*{ju#Hj`=$#pWuE)vZ3LoMgYaE1(3;Ks)sy3b zpBRge1BTcmsY5$+j5P6GsL&VheNzf=R125vRtj4%VlJAT*rfYXFuazlWgtl1TMFOO z3;_H!&`s@$FOBWX5V;YLIWNi?q#A)Bbg$Obxl!;`J8+NfNY1}&bXLSCd%0Fr(RDWe z&gg-KTr{0D<1^X>eWe1`?w+qlWQ|7MMzMo-0bOI?i2D!g6 z1N!|GZ;6||8NRpD3f2h9!Hd0vkzq8vi1^D13Y2#3)pBQIlgB#{8C-L-WyEcARk(#O zPc6TX!R!$H99-&`0@=Q3_i+6$@W_=C)gHuo3Ob73Wu4hn0}pgM3d5ps^l8X^ zNF!&<33iW$tQvQtsOyfwD_nMkc|TQWWFwPa#*;mg8ju#*vspA+&6{+@p`>4I0(5jB z(@uB&_S6@wT|}lsJ}D$`5&6aqSQCMKfYF7!cYU&eq8y#ydxL`LMjfZQru>9Z z!4qJ7NH5qE;t!&@a*y#e?ykCdH=DH)z~~?|n49C|4}2XW9lWk8YV>1VWe5gm5hef5 zu$TqZRpkaLP`?Of$k3z3=-$)G|m=(-aa07mR(b8*jp z*f$4(1$ymm9ID(2JO8)JMCkZ?=6BE(Zq`{O5G}kzMwsp%TBD7Os;5- zhVfs3as8wYPDW&D5N< zYop#y-|%IrRqXhZ#e>vF8h7+9pV@yTQA$s$bY{p>+#Po|E7#-B_ps|e@1v_0 z?0mm@#R8+*akWVjbkdoSEArlIxeHhp9gH!@yQ=)^+rVb0@4p5&0K7-#>&hRX`15#q z!PkFYK9|4i{?FM@8P}Sp0?UqM=n{+~p3#-O_pKth3}a~}h7Y3K{lZaxu*J2zBh_1; zE7)Y(eWviB4v<@DTRf_O+nhyOn(I!tWf?!W=yf39gM1mzWn4UBC`cx5M}RrQ_P9AF{I3RJqd>RbsmGk#M0r;c z`jh;y?;%CY)S0SM`|EmCUZ-enrsoc|5#6N*L#%c*xD&U3uT`3Oir1ZjhvHOa$^F`S zx-wXaB2?yE03Ntuw=4IP;;0xk>6@tJA>Plxgxa7(+{w#RD-vp}O#pma$0&B+G7xYK z?5PBuU@Q7i%(wipwZGgKp{wO&+!QrtU(9YK$xu*A((Q((GQRw!c%*xOKso(BJ#B)t zdlJZ^5bZ)d0qy>vAmf1#9ICAtM%(Bh3(>3P0`EC!26~FelN+gasH)mPp|tGOyKi4- zIH4y|V|=&2k*`N7w%RVnOwpho>CT)1rd2+El+rJp%p#YJ<$a1%g$Ycj=h@KEZX|hD z{SCeOH}0&D8fuR_5-1QYQ6O`j91daKbuQMH*>6jEkHY`+daPUdbLg{G6Tj@PS>bv} z{2IL6h)=%qc+j`My6n%;|1{8&=B9lb8o1?5Ps3h*@x!~X+q-=C8@sd9IN-%luM=2; z4AGx>1SedsHxAYFhSd2B+$p>w-ZFf3=`-8~mF^Ma>Oj=_!jH+s*}QxH6rf?(qyi;r zH1S9z(2JlS$gJB1+xGsIob*JwaY-xDH%<|5f*L|XuhdrO(Y!S8Hj&r*xt~o}ywsk^LQ@_2{l55%pA6_5s=bk? zH6sFW$&+lcxtF0#4>XY{47d>#t)iGwx3RvvEj_6fG*C>?+A@{k(zu(iwEHdPVCy=T>!*PiEu=+ZJ^C?__K6(mX7KrUv8-S) z{c*&Tant7z57|O*#<-g&k)aup3}wexUo+0f86e`a4IXnrc;5waYBAvhhw^*1**WUk zkn&U6IpA0<{F|Mo)l8qo6E0EolL2Hmhtk42-MhMsxKCZy(|ru9I3#u`zN^mja|wVJ z3hC!`vtjz@-8HkwZ@CLW=cFLh4u1GUxU;9MJPs^87PSTJ-|Xd>dvHJH3#^H>7(Z7@ z#J)&Ba*_3IOdh^ZU6!;ND(XX^BQ@M*%_ZmidzS1TpH8B@wL^5RJEs zbe&5spW${`XX>~YJFMQcukEy;6k{keV#!^MMN3|q0)q<1=KOc)o8&%pPP^GfO4+|N zMr&kdX5j|!NTUN(g_LWtY7c+84BDb=B=EvU;k8UVxxk<6`3GbeL znL8n&|13s@Z?@fyu=UEp1ry1Hs}`rt7IVHuYmmy7-zFb=U;R~>uOe2rDf<}aT2p>A z`@}gDAGNJyMSSKBu9?yV_PuYs4+WK-I_?}E(YesQg4-ytW3kcDe5SC$&;e&F;1M&W z-tWTSOPhYQ+IRa%=ufVmGyY;N+4l3QB>mOqm%i^0yj1Z`m$l~xW}O|J(nr|a%TKkZ zhX+JUJh%#qW{^wH>`oIEqxsw(QxjP%zPP*oPrXI&st$Z67w}9og$wr+B&5*2yt)U{ zCPL8$vObX7taaNH12r92=M1&9Kvl?<;vTO_aC1UX5aMQ|ZnZcjA?CiRoZaS{c+Mo0_^H`93{`_C> zeDi2Z_ovtHsBfIDN?!M5hf4x>WZHHeIz-%9W_-0p%643Fuv}?~@zg92?nAKEY#!c5 zr;K=#Y#8$K?~F;{xF$!#AGU(;Ip=+>i7{K3tmfA9fODGG{p_qlN9b55ka$M;<^axd zZ{rTmyC`!q<5wEg?t2`ysWJ|A6+OTR<2E&UO-3_Dt5631&A&5zgz?zeY3NWTxb%Rk zH%IbdRUT%u4gO~`+ynR7@RVOd#;VfC6XS2lzo@ftNdL}lVEQV}B}}2eDovNo`%wJ~ zoaqIa zb>O`6gT!Rk#jH2_T=}bYhricU2%N_F56T`f9IyD6=IuXNE_lkLrql07jnL&RrX{iV=#n=<1@o}IXJWdc<_C%-%% zqU$mpy1)vLeQ?Y}m$1N5$#&eaOmEUP}>zck$JVm71K$LJp(Uf>@mdFyW0{MJj?gr!y2I=I#e z=4xn4TOHG`ZOq9iDDDME8WP4?_g;gF#aH@#o6nLn;k;&rwZx^zo_Dj8Dvk--mYt?r zvXMbMS=BEKGs?NG>R-`R-uyGl{L=#}@s*P$MTw)j??mnwB%O~1aN3GoN3t@jkWKXW z+8OYZAH>6<(Xxb6QgsS16+bsA1dM^O4F!!Bg?+n+uOal;mCwe z`%rU)oW0k%w40NB<&|+8TJuB9@8m7Dtw*zo$LBEC$bs6_hzuTWVMkpF`q*(&IC=qm zm;2;BBO;8jYkFsQjj+1lVa`x3I}T^cD#zg>6qP9>qzSLiuyz{#L@-wiNmyc^<3cC`?Jc?+zCrJCw1p25LD}|(p2D`r zbDUDtuIcKJvyBWs03e-QgDP1#Mjhh;H>P`Jd z56XwdCFq(ytMSR3(m*A%(>K3(2*(?mD@{o{JmkN|XeR$xTn;yUR= z{)t-LWw2Pc`IisJeC5HTh09Jmh7W7RZIc_0^~OKU{h?uhRO@Nc@*`C@rweP73$Akl zxDSYiA?!v*0T6uRLv-AkJ)g~q(bCG8v%47URUY}Dur5GhZ1cU$qB7{IfNSGv(>34YF@A`hfe|lZknB|%CoaH{}+&A0= z2qmhR8xQlBm~)|-g#NQdGNDr#wwMdz!uWg;Yr2gjXNM&xFK%6EC>dRyCm?L z_7ukG-3Q=hbn_;YlWDTr8gR zJq*L;JC8HcO2f8xS8$CzqOnRM7We!BeQvdX6?H=9wQ6Gf@&}JNEyp^Yo*wgFr# zH6}4eV$JbJsWvd5-BrI;AHK)(-ghp_`isadPs+yO^jkx6pnXNL@brPQB??A2ZqLR0mv)zqJFHx0n%opSQ5b&lg(wH_2v+ zZsXDS2Bh;d>geY#KYGh=YIv9KD<3R+0fV}+6!G*_x6f2(O-_O9FCk9Xx;^Gl;YlcG zptib^lT}m?TEpuG?=0uBv{rML-9MAXL;8^R$>EwC2mG9```R{MiAJjDboo0wkTFEX zA6F@@yg@R)x*%{*t8P9l^B-<0T~Td+xR|H1bSm3ozBbmbm3Aq|oZC-C5y)Q!bEInW zUQOP8$=Sg-BjbYh_81?5qgS!_7~8#vGaVCtCC;jIv+}YEcJ(0_E&t{gKSM50W_>f# zb0>su-8Q)s4|P|nj4uLBp7=_KE~?7krA|G<`=Qtrw~#3_r*%$kZ@oAyl{^#q&S@(kARBcgrf>`hb@_M~_7;`P5sl(pVJQ8v>IiX@MHf#`?1aNdW$=K`2rXxc zF^A%vdPJx8^NRxL!;`NUU!p%7Cs6^L|Ho?N#3GJdg`7qv48i~1QdJIr*W;c^Vk#%< zF>?a_ojHc{Y4nzt8x`k8rZe(U^!?e}-o)d0wC{7$ADoU@Tlf~)$?~1w+vF}5@%P=YR+ zYLrrf2BLj0Sbi0MyQf`y*kr>mH_)@T3A0}1lY>WUfbFE`fS*kKaB}-bMo+ALRQM2f z#yi%p=}D?QKJZ1$7*c0Iul8WGh&^)gQZ`^($@(Kx#II4Qhv2MG1@Y^4;X5u25pPZX zYeyF}ad_?6r*u~~<;d$ZrQBt*6|7NnY<>2Z@VQ;ke1~hgF;quj^d4+>;~(oEoUy8| zRvJXPRh59o^A^%V%`(wFjypAwkJAvdzye3;@mXk8XwBA$~ z!>Z;d5X)u5eGkcq0GeV&lV7wJPHAhj2z+7N(Wxu|tR0Pd>qLil{_DLD4x*JK0<^S9 zR>W^O4D;li>Pn;DEvFtP%sxFN~Vcm=a zmFaInD9OET5LTVBJ0x9m3=3^U&!0-pC=C+0=l z)le!vQH_0LUjGEo)L1eN*`P0MDpf%n%qC|Sh2Kf z=keL)g05$wg&{YX6U(3se4$L+Hf6!z?->`_JHCZ_K-@bUZ=kbz zw2g(n+UDkFUn{=5e@hb*;n5!+&~I|(daS;|EzeVUS?69m+X+pZfoQr=&YFH(a9pB# zUJ#u0%G97~0uJZiGsGUjKWKbWsLxbeX#^pzLCi-4h0rc|{O^oNZ<}z5noJEyn!oeL z#&0`^i`oZAuaoCSBVHBCS7%nRN(B#eDm;yBsmVx)jhb}2sgq*@FD*Zl^poopa9+2+ zoNxosi!6=t$6g@H7t8sPyq~pIJqDO67Bkxmqv@=^ajy0XS>n@SXn{FJz}i@Y{vU2l zIt_bQ#ybgM6NL}j`HX_twUurz%*k0mV8YbC-(mo~-uPG2l24d_A_~IAdxsuj5i#6{ zCRE6xHD*&jNgrJ0B3$ANHsj=u$lPZD}1JY zC#P)?5$lY<`MbY0W110hRkGPq4mXZ;mt=Q8$nidE5=IG|QC{y;e&35Y;iC7DGW=O~ zQ-=RL1H*Jh3H?6?FzT7qXNsWFp070S@K2XyPZ77y;e?7q|$Sc8x+veDAs2)rSF1A!DUhKwdmDxc+|QF z_3dbdp5)01v$4H?YXq4U8>@leOBkg)PW~y5zUa!;Oi~%zM*G`&YZsQ+3_b|W&E{pi zCu2TydOPOkJ_xTBP#+rJL<9J!C6|*&h8N=AuRteg`Z9EutX03Go&=&`t!}c2rJBp) z4|kcc_Uc=?+hTXcb`5fRLZ8T&_!z!6P`MBnk=DcdpT!O}?pH}pUiEpZg+Ues9!@Uj z*4>@P?gMU)Y#^u~BGYe3Xx;URPH!p=7>&jl-jXxIn}nBwLr`(E9XD?4c=-blRJ2|w z=nB4pKz-1YSM3Qa^^Xw>@dfskMU$FSRhtdo|2R{g3H(IojPZ%wHD0gtKh&8@q}QTirg_|dyC}p7$R+vp>@zNp!2MRVbAoBig$F?3Bj?= zmmIuu79Yk*p7~5qN5S*^ls_o@p+q~BI#+W@pd{np( zO$~3Y4=v;wI%U#;Y6mNw8D&OidWSjsdp!->-&(qY$$ZF&4;@cK#I`z)%YU8{u~Edx ze^f7{E)y1g=JlLny3WSrsRsbTa}%|SjIYYbXfnp@dR~)jiNor)7u7I-%(Z2600lxz z7|WH$BSYc97wUNYT@)GJ9KWg$?Hbp&sDDdwv)6NR?%9=ca(ycFKBVA8=1BF5a;;u? zy``Pe&D~(+*Z~7k8%!U^Q-*!aXEALv+9lRK4nVaNC~n0!Q1ACxL#q0e(`CJ+6DHsB z%Y9R+_bwx+6Y3F4;YPZo6(;pB1Dc6|cgu~BspK|cDdEg> zU%V?K_S}YcY@%o0m-LRa&SypPfF9nXj0>(leTGf0ZvD%{Tz3wj^?A{F@slbTx^4ZK zjEgY-Q~coj_V9s(M@P1A#l56hvrDe*+Z${AV`cmaez{*{{F(Tveza?eo_%eFaOn%p zxBg+eb>m0`oE?uF<`}#=z4A}SMuwu-^;(CtOHG5UO3f4Iap=Y(iA&F>@cy?ZVC8_O zYaj_DOR@JBS<_x}{@zZRknj{DlOhmb45@!Db|_OHeftg)GcmzI^U!+m2(4~LX-1u< zsqY`^V_tpC^>XUOaE{Ih1MTq@RP|%}(qfY;@A{j>0uOlm4 zYZ}jdPJIul-Gsp`h%SU_RBJvQ;uqYqXxWGzd|0XI11(Q&My$oQTh#-b(dXlNW*+b1 zkk|*#>p=6Yh5(Bp6yICYm6?VOpN$b4$f)Ln*Y}3SR<(x;Duj0LBpn8ls@@k}YR^@v zzHKz^eNqPbFnq=Gx6l+u#mm*4A*IjxIiv)3-6j!YF{B-sk?=l1Nv)ch*PvkZBPH)* zV6RE6zd0$!Li)xXRrW{Qqvok75~NV4tQ^cgu9nGYJNZiu=_%}w;IxR&3JClv{u@?bEI9j z_zUcWBcSWYqNx&sJB)h|AS%$oWcT!&c#{QgG(IG@I@FkE8PK%UwyY*ItZ5`tq1NC0 zZamD$EWbS#M~kMUTV1t)QpE_F(JxcK!!sKcPf&R=qmJ9+c=glYf=5<|TmSK7)wEBd zJl9SNJAS5hI_Rg;wQun~qzZM+;JYi|DugcJs6w#xA<`C7plxEM*YV=)2_}hA_qV;? z`KK7UIuM^ZYL39Q+4s`}2<4&Nmv>B|p}sH9*5p}i^tFunNz0-3T|srpK;=F zhjp$6bJt0a2jzgVAPkM7{YhwGzF-|^s+va&9nI}FcK!L8t1~j4MhFq+-iP2!WLZRo zQ6qu)C+(^J!iGAUtOR}kw;V#00@5pN(LHGU7P#84$#}P8(-x)GT#kmP`YKl4%LE2Qu zBd^@j8xF;%Nk|20ct)LtAlV?^lk;WSTAV{&Z+EXqFKT~ z>#Mo@rHPX@+@dCpRQkN}u}MCHwbxW@%Eya}z`l8)+NlP=pJ9afYpmJ98|O|XYaR9H zQot52F%yTzqk0)Ld(gp*KPkdhFo<|8TIICndSz$ZYW-OLFgE2QN$wgnerL!CfNoj@ z86SG>55p4`OpvUS8Kq32r>QHACp1tc>Y@`LU&(@Jd56Nma%0$ge(6geh2-`A9UZl} z95{kfWD`yBtIt->cku)shVoTPIDD|4z0ZnEcn2!Z&!ilSr1B&-ZZW!{>f8Un<3Ac0 zZu@HU9{lv;IB~KA(=u5;!qUW zf2IE~pJ6e;9a)>2ck<=`@TXbK0RCKGFY)re$b zs_2tldbfiV z$3`9Uce9?5zD{H4y~f+zs_wC?zJifcgF+bdy0{p4(cr1_wC_yq7q)VEiq@csXYggc z4+P~+-%KSL4)bLHZzi~` zA@q4sj;92TWp%-erwOmN3GdNu$z~V^ps)5Ez`dvY+>lG7fsMC}B=r^MMV|%9`%?6@ zIORqK=Lwo?VtoAT0gV7wPKVrI5*IItG` z8y#J0a_jbcCg(0j>-OPw9x!YxozO*k^IN62Z9#LHUmJIfaC_6!wGE}#! zH;7nlr4J}h>0!;qT}0EE+oMrYm#YtuRO5%Sk2k;|p6d=cUF0I2=Q~PsJ*UYN?{1~9 z0`5Vc1%2_bnMPkrzRrfaE3vd6RqY2&*SmNEjCCwzCl|$S1SgDMiIH;I>2i^3xyEJp z8`DC>PXS_YD3&~((dU`&e?RlU z-`+0u_M-<}?LtKiPuiOo3Q%}Pc1MkWNIpd$1!VLkXf8J*Gve0#6)Kysg9*kd!ozio zFeGbsX;Bclczk-BwhB58lKy|#G&g0xxKsR$3T(x^Ib9>|*6y{FWviM=P$*6yFP6>{Q|C9LGq36oZXlHh$~f#^ef?q5B8_- zJfa?);zCh?rC4Xly5f%aH?I}@w3al@KO_-RKcjz+)~@PHgG;l`yamvS*;o)|1+fis zstf1(hRXXobd?d|VTHrIPfF*Bt?>`U2W}6A-ij22_AfPKNpUts(O>_g@w|VUv%F0= znYHFJx#;>j|C>>}hJU;3b$uySz9(Q}DDQd>|G~MMtV@6R3lHu;1M`E!BjV@c1lz^! z$JNYwGhN|PbvXbcM^Y6lCm`7VN9{<$kmhMed)5G_St$Af@3~ z7(qUwmW#42JiIi*-&~LP;=-Qq9;P?gE^8;~8IQibo9pM^_R~-8eR40atH(DTIBGd# z5%V#x)Ezv-McGVOPmPPnyR+o%1o18I7vCT6;-h}IWq9%F9tU-DV*1XuNu_;LP+C8@ zXZhZiQd8*7)oG{gYWAEs88x4IIxbT4>XMxKaa^=I=nCB#NYzDGN~yRVI-06U_BQ`c zuBX6c{oIWuvP39NIUmLy1@zBl2cEN_px^JRhUKL`#q0M_Ifu=z>(iOX?)LK**_^8n z_`D*mr|J8Vrs1Z}z(L0sc1MCEB^7^En53Q^zAApMN=TFV=xYb&jF8Tfmd2lQQ~VIa zxa2Vi*y}XMt_eOK2TEl>lrg@P$Y3Z5@z;>`f>t@kuxL*6p*io90NQOD|_`(0JxXww1FRgr<7 zRQz<*~PMp`y12sZqhU4hwo!SRQf}ZTmpRY=5_z&rkrsl zHg?(T|87R^HjXuWVhNVWz$~zW`xQCkPOyKCJm5ZlC6p#(p>F@jazg zkIfKERc7}y)n9!bs$$?fu#>coUs_hlu_bt{=`2-eyexoYLI(RFIAh&g8r>vPp)U+xtyJFO02 z^f9d2$zBgz3RR_C$fFDvc=ExvnRc~CAY4<8Ah)~ScRhYx+U&98@r_qwO7}LQ!{tkp zm8SXL?vJY^rL*$P*#6eYf94#4lylKYgl#GIJ{0VrUH zLlYVAI3+v(BrG$htPk6+m>$XIc{I@xG2Dorf*v0r2I|)e3nM*Pk05d4E#Nf~I{8p^ z3Dsqaj2CwR&MF$cUB(5RKX|&Jy?#_%csxU#E%F3frCsoUOEAhPoixL$hhzj6GK}Sn z@~(^~6Lv|aLWjF)#4L%pNfY$Axu0?VjXTSYo1m-q=#;pdp%v4ZDdK&EwW75)`Q{ul ze$%Md5Hkfc|6KEUNCEu0Q>mN96l z@DZO7jsw7fMh6ww^)o<_?y-7yrk=yDgwT%MKx+${<*XT=c2^jE8$ z%}Mh@Sq4rOsCwpW5ad zYGDN?vGa8kDhr$-xJWBEi)wfkkC=CMVw>T~G&?|&;Rh=|?OYjI$IO7{6ZSoA16VAx z=?)k@TbNF6iQ8PG@yfryBQP)`0+Q3aKApZklT&(l>T7e7M`B$MdesP6VFqT2kIDd^uumlQ&o z_0L+#w$}`8VY~-92a~U8KGjbdb;)w71Fu!x&4XJ5oETOLZoN5FH{!VR+jWQYU&K1) zj`7x)jmU&2!#I|aA^Uq&WoFk5EytptaCX(k^STU13OIG?n1gJt%+rcsPg^RDxs!}9 z&^1!udYeA*J01^F!4}gf$mBb*QSji!Zzj=>v$2Tbw|A59Y6Rd*!$D)So7*^pNBeii z8vKKtgD-x8H|mPRs&Q(X8n**X>dzx^FNEbfl3f53?7}!mi0R&lf}F`LcFuU(ZNK9p znwb_Vi9Lz|>qAR+yQ zV+L>wzDK7;XEKalP-Yx3Ae6f92r>>8qncr}{RwJv=l$w!M8PBwe?a;e5uZbD;@4f~ z83lu2?TDsGsvx;jtS4uLciRYGfCkZ`|9KKT| zj5XfR&gNir{-(8AZR3wr+VO$tL%TBU+~A1&n6d0e@e3SPi;jE<0A$<-HFeN@*_=SS z1G{PsrTUMiO_ZGmP@6Ju(Fx!1mUh77DHc9W^+q&0HRLmc^BQlHVBU}EPl)LL$5*>3 zH(JkOexJbmM~6>O=~H<@_G zXfn0KArizi9buHsYQ=fis}0+!zbh z>hoA>ArMBClBlz3mOj|bC636^WM?#u_QTe8^2-Jl&wL&LhKbPkAEPHyf1w(V^Z2eL zr%HM$r66RCx2bv4k7lnI9R4ZmtQPN{ZlrFAvs1ayFy&2sY+@LLFjU*sSxmlwrC_it z(+GCTL5ACnPG$IH&-XCflm6RaD7L3M0S@f^FT+fzLC>g%?z@}adeGk^l$}`{f*{gl z#7R-{nH%KpB{>JrPKA{Z4`7*U#M0G1r#U(_9DLS&6X&`(v~&KXF}L2<-Ljmg;48Qa z=9}GIAYIdirRCr?Z93Fuy;8mj!-ZJKc%t{gN3n@pks z5O#?$U?YgQCA6m;4{tMh#U}$$<^k2RWluSl8cxf%KF9VYDTy?ku>jqL%pQ0exfV zDk*7&HSU`lmV2W7UJFTfc*+iuB!hxehdp^~vg>6LA33U1}pI;{Hh z^p8Ph;lIa*52uXMR}-5x)Ov1brCfi#&wst*;NuFsL$lMoxJ3d6lp?MFEbyw48#5IW zxUQwP??LMs+n%|~%*^Zghf?-*`P-<9OJ7A|QKN!N*;q~X+3{TQt&(or=s^*X)CUd+ zh~X+G(N}4Xv{k1i=Z5&+xEs-sm7B}c-{6C(NGHV#W*PMnmsq4|q`Q)@+BmCVNQmE! ziU$8wKzA9|%#B~-TTGT@Gw%_00qfhW`HY-{qkU!?xzpBZn!hd&0?TpnLNr^(%+lj` zf=q(-rBNP7rzbu#;j>L?i5EK6D$qWNG+|2&$z~qX`#WYsuHVIafHyXFJ+-C6?ZLof zKW7Ycwc>DQku3#oOdzq;oK0Hc333LP*q%2MFk5YtogW)5M=jau&uXkCG>C(hu&QGF zUp+$gi_%(&QDTvrJndK`sIX&%M zfWCKEBgqhon_O`T$xoTYivNBac_Z|)zO7t;_(4Qi*!F7yel*%wTes<~9m z({b@HrN2$a(-^NoU47;oM|(KLZ^)Gz3b8NQoP_lQY<9uD&KA~4*TEeP?8R`NIrKFh zYI2a3QPCK9s%a0gTeB%s%?p6kEQQiLlpb3+;>&9gOmocMr2R%N1$mTpz+w;s14f6Y zQKEz@E2+@YG!q8AK8Aq2s@I%YQ=mC?#h`*WZoRx%*fYKn`Gl;IW#{(Fe z1pN(sV=uA@{&?SqCF3Dp9-oKL zda7?n!-{dneWkUNJ}V7Uup@_#!5g8nTG_-J69}eJ_Ca0>8Yjr8uy_8F#?A(qU?$cY zA-4wWAX}-EvtC!xkv=>8w|*M2Dh|HcArZfjSORvm4u@I6rExz?Bau;Mff#;w?IZkT zbPifO-<&`FIuZ3EEX~G!qd+Fs;YcIh0cb`YX}{HcmRaj zulbM4Ph!NZ38puY_Svzujm)m>ZNRWQk9WzMXb9b_fd=mm!~)Fo_nH}*hu;vWMpn;RjVt>vGbg*Ld+leS6XMN9>OhtIn6|Vf!ez}IZPg!N;A(JDJQaoVU$zi4$pW`vZtOnTx8{g!P&H5Lo5v z<-UdVA#MxDnLCPfh{2wag|03T%Yp#f)w>4q2fevyZ$ZVT@;IYtRHOFYMp*y7@kRob z`;&@r{NzCJONTs@#f7TRIO4m7*E;q%go|42zL#weAuhC5t)y2!2(eaN950Hh4m@0C z3L6p>1lTYR)wH>hHCR@>y;|1S0;_M4$@{+k_WbxfeOzF?xl6s}T{$K=>F%7f_5BZj z7i~WL?#TmDXMlwNX<1_W5aVSN=W0OI`dn3zI4JMNu$I_}?X>S_s&pEO{<3;JrG#-Y z`&0aQ&W>Ie%5vF`-1nSit*n)q2TI9*yjgnw=+ONc!wOD!w%{jzoKJ7(ezQxlDKV}+cX z3JGOJ6exKM1bt6ltA3_Qy^_8BEqeyfUH_i^{O^o?yPDQ3_AmcoyRfZ^^|q3nqcE)> zi3xhjORYc}#VM%Gq`>Mw=;a_o&uHT()8t zuE{+acE-Sp_3>f)PsT3^8f081g8QfoIY8uqog7aVXd1&?!O zhWCvx9QGRR^@>?PJwvSj6H9OtGSa#)apIwE=E?4?*7i5p^`0_|+ z^MKs`)Uul`Sxe_?@=qVUVS8+HpmpTX-mgQ}m9O7&Ab4A|@a(~#G)iYdV-xRI$1fw5 zYs&o`VH8n*k~`WKme=JT%MaFLXNrF{^Ij#8bPjg?C$#oSd3K$9d&yt@mk%YYYS3hG z<+y=tmLHIqI55gn!|KRu+K2mT(*4s9tC3W~vsZ;efSxW|EHoFURJq5Lw{uI*X`Ay> zznfr)t!!IKlTz)rkQN)nf-%MXT>Fs&9UyI?1b+gwh=D( zLXAmxh3{{V-95hc{?%i??@r&ae6)-8Q;O8dGNXLq?CeFKq!(sq(EVe?g|ytuu%y_)ijz8d#XcRdYQ3eubV@LPQyafibCc#A^|qu7)4JYqYdE?4jL zthn*2U*xHN=VfJO9D0blZr$poaXq;2jO*i5V@ZCuTXU($M-N60A5Mw*i$C6!-tkw( zXH}Z;xEEr^qruqQChK<3J)tjKJiO1AbcFEafre^z@+i`eEkul-RqZ#@8G7!j7f^B! z#HuY~j2XMlY4k;L>!zQd9e%5yMq$UgOSb4!57m3?t&ilEf5ruE89O>}Uwk}Ie-F_9 zoVoxjHGrR|7>me%Y`5S$gmHUba~82P^%vEUQk*7Nvb}p{DS)E}&*uF_>~D#4x!Yj% zTgm2+BqQF|s(*}np3N1DsU*GD<6#ZHLVpor$Z7IzOg3LbIfTD3eMc<9UudX(<#i_E z6)S9xt39w-9K>bOq%Z<--xGRot$RCq8fcyd0i+Y%LP@6cVQ-{(UrbgzZ-12{}@-NVXU` z!|n9$KksoeyuW~v#IeaVQ^`f+Nw3i#yN!wi8hF*EC$Ba?eO=*QO8&!BdvdMVWwKso zJ?~P_=lHr_=8U)_noT_90(}W?k~?Cx!W5>pt6iwTH`tg=*uOoc)>qxgFKSxXGHK&| zFmz32#p9CO9h~OqlhIBRWC_-rvGul0wcYf6oU@rn_^69(do0Yn@i@0|P%uhiE{w9L zS#{vM$7^jrY1MI;GhEo{f%1$x>ofWN+mVveOj9@AerHY3m-i%|{{UFV9PT$YJ z=bpWnXCC#aI+m`#ky4z(>Z>U)Ej(MBx-7UUzQ!-AoH5+}mN=uZoeBi2_oT|hKA4Em$S&1!}Okwzfrb zt?`CQ>ii}-&2qb~jk@Q0{Jr7ZxU|3Rhfu$!jk}MkN|c!{ragaFrbjj{_(QF;b39mb zQs}`v*<4uFUsQCxrKYjm!$WYR=Iq{XR%~cqHgFA=j;lb=vp(R1Hgf4m&GHIhY7`c2 zDD}7VOFEQ0Or0;>u;&0wh$q?|Zo5dfw7BlbSr0RTpta&%zb28h&v5L_3+VVVZLjN- zRSkUhmTz!fzjzj#;l9-H*m~&Ie@>lV`SqvFG}l|RT^8N>Bt{2s8c@CM@B#iZzayu*O+IcLMg{GrkRY!u z5d(vG1;3@qBhE-C6YVb;Owbd34Qo-CFW4B{6bVm|eH>TlOu2VpVnav`+b6bZLd8G0 z-L-oXax;=*_!Ok`?)(s`#BJ+jj)%LRBW($ja&8L6cc=V!gb~@xN2*ogRvoyWw<4-<*QI!iGBg`y4A|ET-HZe=Wys)^ zb;^Dj>?TSiPhvjShzu{3;=h$Td1;v6jw)LjjDUP5m!;o+S-yWa;7?G(=bP>C|36g! ze3c-chGL^x4t-*QDyWN`yJC=MP|^lrQ2JhpD~~^Px@ZB*PJ&IrmoA_`?|QqlT*o)S z${oMYbe5~Xq+{Y)BUVg};aqUEIX5p5`r9uHn)7!=TaEK!xb68P(^`Q6MYI6ubXXYi z4Ut`;v4=o;+RTN7{H-%upP?K+s#aQ4ZwDnDk%VCitrw7wt>#UAIIdF}<{Ez4&u>m> zo}iWbJNZF%k>O@n*Rul1lED9r+|NAPQqnd5+t~LGvSKsPTYE<^bWO5DIO-ZtImTHk9tl{T>|R=KiZfm>ZIBwRI>&%{XBHWc zJ{SLYr0$#dD_j3;?JrL-tse-MX-xWJ2xzBG-Gj`C+^)BPZd$;&`P?O zH0aB3L&KmOH1hI?c^WXKOBCWC3vwNa4h5Tc2=A>-=?UkG%MH+=u;&3dw8SE%W@hL@ zGzYzhvkp}gk(=&-@zrYuab7-Y`APpwJSbCC)vTB(h*%Re)`We>6^IV$AFmN^$`Wd5 zhvQ@mFjGw3aRsr@EU22PeiuZ#La;p>=K$oq``*Xc)xo9`yS!cjwfMg?URE(d$N&WT z+|wY!T(Wh1T;Uo_2XGof4u`4!9oW!7xtAz-OP>$ApsG<~&GCMOi9ffV$-qM7{+tun>9)6;xydUCC1&SkO2TO}6YUEhNVigE9nS(=PXbU677 zfW#0?<{K*|DV2m-5tW9c#Q3#b)$^(G;lUwpQ@lrG=ixVjb+?5osP%zEWLSW78l%A|i4>YTw48m5HOd15f+XX_oJ!$#w__oAJbt|KoAKvbfMvuRwaYj$_U2IVb4 zEtNznrp!og(XmoQG0rOP$}q{g2}Yfvq+{q0z!yGN)WwFv2y4k39dB_+T)3p3s?Lb@ zs&}U6JB^Dv1c0f2JM}BEB2QzCkM$l+%GJz!YP(eY7CAZomeO~w19|_Dlw{AxujWr7 z6SLrC(m_ET?0rG0sM+Kl?E6W%t-EOa?Utuh-5h*7C;@nA%J>p8fu|M_OtJZu?H>XB z5Cpee0}x_XwplfmT&yLKUfv&CHpO&e1*$sg>es&?3xs!0uD_#&xzjKQx!aR^(FyALPhU`Q%!|EoWest=q-tzB^uPtDl zNo;72CW}qJ?7E|ut*Q00u7qQc!8tQj+$5k^f#(}8rr^`M$6PQzYG;lzDsn0q?Jdy{ zYW*<*3zS{{FBw@MVPOKfAo8orVzuyDSC*5RVy37#)8`Pn&-%|%r0?@abie&;`fAQg zWGQe>IWbz$)6Au8yJvK28}ewJwP{S85GjM~>3Dc5g@o`77EsIRr{I5?KzO25VH7nm zF4e3r{ff~$o>tL~?FiNc>PzqpISYJR3i@AKJL=xSNf;F_9DI`9)Ju8xtPpLQ9)Vt( zamf*3;n{KDfKWNMPl2TV(keQ{XZ5tiqD>i8@b!GI(W9*5e*;-f#DC!8H(T^=6oGF0 z-4S0|vWi7-2-3_QKMKTVo8T8eMW+v>7Mf~vEC8&yRi_h*xGNYY356)H_;-fqIJw$; z6s@NvAtkhwsugnXXK{x$Y2|qYQd&%QYS!?(9C`R|766Udz(>=-`d)&m`AimC(Msbh ze_@x1(Z7bp^lGK#B@*p(A#-c67R>w1q-865T-+h| zze2Q=D>Uub7=OJXx3Kl!pbMWROYe9OetdcTyF0cnK{F^U0Y}bh#un4(u_N>+%(oq| zw9Y|sQk6|;Kgfu%D_dN*7vkv$rBNFxjR90?(q;3dKbQPIyJ8D3q$?%=u;6@C+cMMM z*UkASK zP&pijtGjrW!HDjiq|7Coor|i4$AW#SiJRi~^v|c6zn*B3kZZ6X;8f8%RDA{Q^N~~X zg&mZoafcyOi^08jn9ily;T`wbfrm9~Z?HD0TrD)TI+WFhJVXcPo1gTJy>a^Pn|_J^2!>^Sy9ms0w7B8^{nkDGMj^Odtp@y*Us-6Y0~KGLbe#>E&SD^sYb~M>MxXa0 zsLyUy0;De$pgVz{Lz6c_d&hakqzc$j;gnxNPGvd$J0mtoZf46{9b_L8!P)IY(E!Q` zsqe9qVj>a5ct4mXo&@^AYG_Gu3lvPPQOGETI6cc4qxj#*tz9n)@Y6IA;YYCKYOa|6 z_$@7CT0XgiTtNobgbq^xt9<5+1qomzq_*1WAo!`j%Jr{uMdp8OV#7hdPFc-^R6k^5 z{5fQ(r^GY5{LEYx7OKHibHKazAZkz`YrwdM1|EI@E1#W9Gg|xxUz0WmaL`siIi-=Y z0{Tp9MlB-07VdNSh1R-VUo1dOobD44dwR{%*TfVQyn(!gil3lQd>r4?n;FVAMyiui z8)ul&=fcyk(NpFHuV`P0>Ta}mfE)E-jLNj+Qr&m5M_iS%aM!>NO`ZO*no*uer-1uk z%ZHPlRnHSjc}xVTEqPd3^I$tKz8~q5sQoBbzRa?E{8KSY5MhRw-i5ijP zz2_mpriyB(+Wx8v6OE}1bNio;FwT>UM=g5CuXOSg$E%Ly9)sJ2^t!kmG^x-oYDYzo zz6c)Vgd2rFQG~dguX5(aS(IsJXXG9Yu2~@=qJwhrKzK@tJNPOuiR%uvMQ{0)usAs>lqITzZ-KkdJw0qcZH~eRYP3}v!^ylbRUOL4evHMp4EcjADb8+e# z)owDHWKkt@;e&D%x9guGKrJj0M>Se~PTNo9LU%X*GhU)Ijj(4WwV&HnJeYby?ys4h zzjnr337hNN*0y7{LyRlyT4AWM$()D=A~V=$qzzO{-D!uetib-HhY#ZRw>ySnhUDRx zVX{XKTVpdWOKnrl6x`dmn*)>=wOAUnxS{4rQJg1;YbTQshfWf7%^?5nBuj;;Jm)=& znO$uzy$grC102gavjs8`tmY+v<8gqjaCY1*d5U&h`uqc~y!nZw^_|w7Qt4Qjj1?P6 zdb#Ib4^FB#d9)opEn+0|w1dPsH+iG2tA?ug$CBwYh65ItJ%$MKZ3%u$Gml({%|hRC z?{e|);uxA)^-TRbBUH|Fu7{J>aM(ZLF8xcTfDdh()yd3M&TD~t}{gZy&TL3WX;W-vRj=Rld!=fA|S-Jueh{9ic} zMOy;(%v%b%nDXDK|L=?l263Qg9uX~c(%3%Ec)>b@B^VA9F7LfujC)vL4a5<)z;T&%|ccIWNdaMaiAFhd>|& zrkLQMe59U6nnwiv1vEo+%8m6E!zJ|6k8oAgYxe z|4hr&|85EY5PUq3{SuX(sscYrcF)dxfF9u2nZHDt0nqnDCMcH}ApR=1&L4onqe zw0a(xi%N#?cMb2K*+>0^jdG1p#te8Unb2rmhWrG)y60;B=B3j5)})q0`H@N^Lo5aC zDB7%}Av_HNclEVKbP?&l34OL?N3j9^v9axj-AA`h{LQNt?7p!mEJzpc@CIEx{?e4{ z;y{SS=%={dvDaadkeoid`|h1``t}U#5v|M(H6PW+ZGNck7F18clRTZhIDw3((ST6` z1zDZVs&T**rYLjqbbY#6KpUgg%!{*Wt@3qb7&7T1I4(w=3&oTM>jJwe3t&ZzC%2uW z6Bf~zLCm8Q1bW3b#Qb7(D-_rPoa2drt=omhU=YyY~9rIib!Krwhx5l;&s-Ln&KIU(ELM zi=`}JZ&lQ1e-8eK>w;bMPJrwRQdH`11e_IY6m0GyzZ>6cOztbc0OU6|cu1qC)zfpn zuID^lb{F)0$+15DFu#)bqU*SPi;$i4&eitV&CGA-e~CKG`0Pvb6b@hP!&Yd(a_9C}hI8QlhWbA= zZ>*^L?B?H|86AA=@IXv^OwR^x4Hng$dZ;;fP5umBBD&!9oV?lzYH|Uwkz6S9)g10>%xpOkrIW@WUGx^N z{_?zQ+IytA+PRMUUMqE|S4LB*3PfB{`nsH4)#U+rDH#bKF|C^Vf+y>;vPgAsP;#{E9c z>ATr?D$oGM8D3XR9wbdiVg>&M{x@X?F#Oo3)VsOacz?Y|#&*rz_(Ggjr*r?`FrE6g zyY9k-LRz_m*!4K*T%fir&uR3-a9eZ0x8)g2kYcyrZY=IIEZC)?y~v}jfK6t+Ze(6B zYR0x;UF!UZvwkg0koB&xIno+DzWSGrTLrJn$Zy|;q~E`@#@);1Xj|{93-)Sm1MjM2 zHaTnX+%PuJk@+BUf79&HkITB_bj=wO&4zZZspv5W`4?NySqnztvGOm~82=)V@@1J| zVi9Stf$Zbdn`l6VBO_c)Xjy4*Wa0YN%gl|>w;kQTH}J-_PZqAU(>`u}@2oZ)#exnU zDhXa#V7~Bniv7BCH^=D>dupiK<^@I}tp}q!@5_?B7z=}< zca4P6Z{~=*HvA=>bIAK}z4Cmd{o>S4o&NI~?xA@~ z?bqLeiZE(U-vl~WQir>m1x>xHHTL=10SaX&qGMbNex{W6{DAt7lhqaaaL&B;e5)km znNjbbjeMOqcQfxGiTczuSzfn{r{%R@vkv6EYCm|3HaDOp`$N`}n2yKxEfxXv)6J_a zR!Da=u9T&=`WJMPK~eMyNi3!J3|#ut$em5RB0+;Hp#r^Dvz=IMddT*E^h$<&{Yve( zv+aRe7e`Ax)$u?rPy9?;Gb4rjG+Wx(9)SkCjCCD$DvH8z{m%E*#jVotTMSQ2I(%Y0 z*}Mo{i1;DP{g?8T^E7qHEV7wgYvQ8*vzeEd(h^HpK;D%mUb8c?j4^_x5nQ<@ByS^e zu)4GKk?Pd*%8Y--qrG)QGsl-XcUs0U>xR8&$v)GH^`QZV6Mc$1K0V{q9F#0y(U~lb zY(-j?CR}knLn5s;uW0DFWVLrz`j%a*%oigUTI_19S9q(kyYu{6mPa2x_i{0U63F-{ z@~b`X3MjDGBNsD%G^N5h>gHZ2SF16Yf->vVoW_*{#kqTYANuH9-*@k^`^71z!;`5DGg7)Z-4b&o08?V(FBo&# zE;uDrt&6czOKK&-PoNIyOJl^RUYf*cXR1`;&FD=3ZL2GT!zVbW#2rmpCv2-Oob3ID zV70QB@PpcR1xtI(4vJQRmqzJ}>8~DoMNlE7Vw{fH%-qduRb zWT_K>#X~8Hi1aJ@74mq=ZMTJjt-F-&BoApLv+_ttO_|dbRwOVJl zMWXtkp32H6Pqm))vI;TmLyM8gN9v6~5mX#e^JFM&IKn*|B*oX03vl0BPthN2 zD=BM{nAoYFn_0-4UwHTNv7wYZ?ipWbPWpWC4k#MlR(iWL%|3(d@izM!wym)Y7K3vqjX|>fR(N!%R`+#omN7Z9kO&7Cn@DZ;|Cpb#gfa z{~PrW^fNLKLz4M}_iUfBff{GL+2~HpR980Vu)R%L-@RLvxLHfAF{Q6(jr+ba({VEU zH_&s8Vcu_JE(}!qpD3DtHQmpAIxB3B*=qgCOv|%-Z~gwNX2|Fg z_hkB^uekWyi5Ib(UPwyh4b;o@sp|7k1J5Q7Xv|4|-?9~Rze1i6$ofU8XH$6zBjWd_ zBv-6`wU&iEJsXx2;5p|N?A1Evx1F^5{38(x%bLB>AZj;g?Ki3tEC5Mho2fnA zTOFOUCC7%AT3mJ9(eD^&2r0@QqK%tfwLE^oeJrxhicfBj64E3g~k|A7?kmn{2LJxSLO0MDYwfM?L9rP4=6Um{6^U@F*eju zu|e?T>bukf=;3=LPdL0&@guhL3go=Zd@HH|HleyA>Nxuugto%@R=xY zR0s{j-Up#!o|QP&Db}leI7$y@|_!?nbshrGG3Dx5*4BZx>Lz5ln zZFr@qAU*ppDMl2mU^Q4S8>8~?}l^MHZ85L}29XFVlT87HcY5ojCb5 z1ryipt6e4=8BW$lD!J;;3JPm}jP}^5{)e8}3ghwTWzU3$RasrFV?CQ75CepNoN&0~ zz{89eCf9i~q8ZSQL`R`pa#uWGK^J_UXXc#WgT_ zD*`cHNC7B$?3n&03%o70EV@)e97Y-VUs|Ob*#-NTr=tPKKX{5`uL?~Rqr<)@3d6}O z`SOyamt@CtnCAHc{b)_pRAW0EryUvh9QSrK?Ntv>wC@Uyb+p*rsVnCxQX--eUBr8O zs$78|S$eCzJ}9TwbUQ3q=hZ?Zj~n}`@eBu5X8E7GIh*B-QYg4~-wcSzCzp2k4O{-Q z&AUNoa4R>^bbf>@CUZ6Azz{_t{8V#mPr(9GF~P}xM_s+aDdd4MHft#2n+O8UicTo{ zr&h+lv@{`+9wUreJXna44hX>uFxSNy5ocK{Uqd1 zv3a9}-{;(>GJ_-~=$J$%!hBW!eE-U0Gp)Uh>_39xW4Wfe+B)WR`W!_CkIZya zQopI0%3jfl1TZg86D(i{y*vN4B2|%X!)uW8^L+gh$5n5mqm82SLF7wv zi9Z67_!3n|H5HR5K4mSOgU=H0}9OL}-uGv--9Y!8D$%z@w>m^MuClnti&9Yd`r9s!k@K@!OIeoHb;c zE|Lwk_LtS2%0eeP;0siCFo=dtUfx#%hPJKR${B!@m@~nB0ER*y@F!;qc!ur8(04ke zu_p5=bMQ*S;EsvA&OwrpeW(DYtBTuMhWK)~kEfnQCbB+sh{$w9ls@nk)gag@T061)659d}u4df|* zKJxL@TP~0DQtti2e)F;}E~_F0Ot4A*ibxouA$S8!zIj3SbX45^A8nczGB3?Sd9 zR=H~j(c;eXV6e8sa|V*7Si1xWB;JX{b~9aWu@NCw`Or-n^tqGBuS#=3!8362n?UY| zmA2$MQwkce<#ag@c99doVWH=NjZ9G<^qX9!Y~zskG9Yneio85 zReiCWnK&OHq)cF`ZoUG31vaO3IxdrWLdOii@Zb2e2E~b&3HTw?XO(GIr)PBy!L+_= z>Y#X1Qeni5JIBPBoSXTobA8Qn#Zzd<$t3l`tPyN%Eq)Ui1`MM$0Ldw_3^Tn)F^mV% z=X>|SuWsBUwcRW0&e4@|`)yqg1K}L);}I)C@?o@l9`CpGRUUWvF1y?sy?nfUT&9Ke zY&&nCbLSoJ{R8Z?lM!t%)sudGXrrsk5!gZg4I}I5FlU*%x-!j_)zzimBR0gEV9;!xGvC3^CN=4}mFn;f9fq4eZPXO8=%D zYgn^P6D`5^(3$mkQ@T~sH4EB%23rvXykN$}| zW}FAPS!2n~+A*`Srw=wnb!}t4w<`kI*%HLxGKgexipYPNYWE=3`|Y*88#Ig;yttZ$ z8vf<;tjoP?)n~XcaDXQrZJ)tY3wEekbq6X|$KN0PdSk|V4b1Ex_`G)nQ70!q%xM{M zKi6>b_rgtC%jqi`pFi)}%lMF7-EpQ}tnw(YjA%(@KG#)TC?~(jz|@rCVsiFW_+6iH z6`Yv4Y7DZSM4)+-s|g3SSsb|>zbVMD)5PaoN2GwaU75IQ-(&I9TlQ=dyb7FKA~JAT zEhns(qwhG_e)Xs0+hKN1 zPJd>sVc}bnYAepx`~sHHAr#x9g9|i=cN6G9n&@#(LS2W>JoLzI_nV)pf#@ufDRbM> z$C1p-XHAIkk?<3Eo1r{vS8_ZCAcD3B^ z-9^Sd?hf=Z>>=C0;h_9nd*bF0Q+@8t^H0ZjNSR&54LpBP_7zK(*{3_~GoY}YM~YoS zO>Q#r1hL%PxTn{Xl=nH;4F_?{&}{Fa-j#UDBNOIAc9Ff4E6j(DnG&4q3i%462SD>m$_fGT zD>7pCg5M{RSr8V*x@t{AN~rJsTD6(F*F^^P7+`-Z14i=hTxsm9``f2us! zj@~Za1R|GQVw!0o{X0%C^{GW2rljEe>02VF)%yX;nc{-Y_XpAXh!5!7|3~=2W)*y76^c#VEOG;0>~rM=69)M3f9wyL$cfgVfX`zl*}Co|~gPDdLxwSVFY(l4kL z2bc2=eoSJG(^fvsykYzVv3$jft5_5dsFJPF7@CldnpufE)-!Eu_7icmWsBJZsJ zI%mP5YFdC>uDA8+f!iJ9imZ|{!HP)FH%8k6dF*&uXZ!PI)O&u!I9W<2@rr~ae*Mt+ zmlQ|5K&g1xaWyZ2p!L(t6zEyaTu^Eo+QuV(E#;3;^i%N|a`)HhiD900OXPL~Py1+y zlcN_+KrsK3VJfd#cMQ+ep{84OD`w>0Zt3n^xW$(Kn3)nO5)#%9KW2u-SA^?_J3Zie}-|R>2F&dtRTa z?r4e0-OwZ#kk*j9x(}2V6>5M_rp#Lo}I3La)f~CNt7eX_qEct{>t}&@#4r0OZg>6MK=fT#e^lLVf#Cv&eE7C=e0VLlR9e zF~^hwyhP-(6L`rdEq+&;q)&K_xy_^H!KD(WZTLK6tEO33VftSU1iY#u}h7hZJw+=(U5dr@%`y1T(Wb+Jeu4N6P<`eTVx6W1Nynd{qht*gS zFCy9{i8RQxd%%-njK264rG!Gyq9{CO!-%K$<}Pgqm0 z?XwD)s>vwO%u?ewjb`P0YN>93?5QP0&8y&)gkbn?_gqeUj=fVzTK1~bG~Ye zwx3lx9s}=fyRn|^Lr2%6wwEED5(Yjug>Tty| z?8IZVAeX^Cl>;vj+N^-8$P_7}; zmH6GsC-|de#AyM!iwoEIl2S`G)`-qKRxY>+op9hQj<2Qkdi7_6p|gmWk&~y>jH8dgKvaT`cr`O9FgxbyPd2Omo3iOUh@zsbvzsj? zLBaG%l9a`unc-JXJ5TM&c<-$9O*#e8^N3axVrFcq7!Mvy1qSs@dMPlt*R zCBE|&V+(2^@2Wk=qSF*hu?Ffz&h(r6-iGv_Zy#r__~Gqm-^w=c66f};wy~#%nukrn zTf6p%#=ig8?e2jaNyLFjq;^6K^>%mZF|vgfM5aYWM3CqkbmD)jLDcT`?jYOcPK`sR9}>7-N7&8J$oNh_rp% z3iZzjZ*Di*P>+nxZ4WhNJMYX7Lc7Hi%jf?Pq+(7|RGeWECbL+TK)1j>_9u~xtmDJe zkgs8-ZO6Ap?{r~8iAsr#^y~!1)IpmE0n9EjLNKscyUdT4rBexEd1V`VdX&(hiK`y|!t?_);tV8O=}6rXkA{Cs&tmoh}6?g`;=BNHa~xqMUlobB3=nGbU| zY8G~B$B}O&k|9w#gNWl2^|3|Vdi&@(wEacDetJF{CTb@(wbg7^_p^_QNFnP1GqR{U zL}kC?fWh$LFD26h_Ti>;X!zstLRHl+*>i?l1a#cqy7vgCw znaX^Zl>3L}EIh^NO&tCsxU3BLS@R=HZPhSd+&{Jws>Z{zcpAeZ?=PyppZZo%iL zl%Fz^E*I!bI`4W~aQCMy?tXQ7T~d$VMCyffUGq_A!|rnS{@c=KA3fPBx2*YFm*=Uv zN}tW%#wz;Y)1qNE{tmn(^+Y`IKW! ze^*(DOITFMu=5yUn%S_@^A8B~iR`U^t?Fso$gkxn>P+G#6{?OKnZOX68F=?zis|KW>$uI0O!4OGN;IN5)=f_ieY+x+YEVp0gXW)dPc+&7S01;`opcP2>0{J8O{x34GlJ9&^67LDxKZ1fBE z<|0hP)I}^t#7NiQZFV((BkI)QREv;EIcOVC!msZurJt&}#-BLvDkhXWWihi^8~b%( zrYkI4axr^`t1o$RsjyKp$GJ~Y+B40RrTLql^F8(z8c4&}Gn6wmhG~6_uFCofWF$%L zJA(X%&R~Ci#g6+;U^7qub)-NxE=*wC$drHy{<+n0Ddo6F=AqlO_6UE>9SIIP;Yab^ zTD`Ppb}H)5sPXmqvUh0vcgH4%s#FgoA(9T^5GG)jFzV5`1L)~Um5f@i`oq2Kf1LhW zx~0*qL6VfR8P&~gw<};ds|8!%*x5!*vm95OHBj-j@=NP!tHAS3RmU9AFt6NyS%mX6 z0(Y#VGzK-AL0hr|lO2ue-z5sCF>&V|*3^I96L|B-w>MV2W5@JI`J6Ypy$-`~!j3JP zJ@xZC+d(%U-a3tv>FQrzK_+D%4-|`%OM|WUKnld|VII@?6yzxGESKk2I-$byl*5;Q zAIpA%?=#ci?FvIqUI+;8{8j z+v))WV#GvZ%r%JH+d*XbAIRf(Wb*yG&Bw~?SzQ9@!c@l!pG@id5Ls2+h-iUuiv`M1 zQbx@fCkW-RM17!Vwp6#iYU-m%u55L2P}^tkLpnkIPSTiuy=Du4dIxOqLB&D+`-}We zpz1?Zdnc&-TN0Udn9>>wOk_EMZ;2){|O$_w+Tx z`8O{lr@FhBrRFLAE$MtEj-SzyRq5g~d;wbqG7{By4LurW?Va9l@T7hUB|vniq97ry zu*a03dxMsmvW4y2AP@Loc9f17{=;R-7L#PTR-jiZls@-Qt0{*n6M6%xn6jx(qMsah zoY=$MbKTGCXJ3i88@qmk^k$tNE?1zz zr;cXdbno)of4!H3#*IWe>w=rdxCWBW6BVp!KYi2W<18|R9DUdTWv!L&2mG&o=vQna zHsYBwCAG=(yLYPaf(?v$!%KIk^9}xuH`Mc;w};(Z3W6a!Ws+phY*8sX2TyBa&UZxk z{vKxpN84y@w?c&>`Fi%I+v`N~iyy*O_fFREzZLwp<@TAAW;5yv!V)t0=6Ssec! z@2v3rtW)9&>}GQP$sc@7fesIKyp0l9+h$atLOAHtm1Cv=v}gnN4A!(INxs5@7*{jC z6piULlqB}Se(ucDLGt}u`()HDc2CLc@z(QAl`H3O{|I9J_NKY)1!`42jhiv6od{D8 z%Vs<;P&indHj0eSN62???f0QCmmP1NE((?0UoJBHW?+0v@VaODw$XT0fbt1x*l$?7 z+dVX-C9iUiP2(%?^sQ&6pW)uPc?57wVMX&zmx7Rz*hM|T_xpJ7Qlhf%m@g__bt8!F^z*4wRvvzk7_1y0LSJU+xukPb}tD(0#BD zLKysewiToItwP6}K>J81VVqn&KEfFAYLuAv2t-7`xI#F`b5E#U=qsw~gu(Bg?>%&U zbdU0?VVGPPoMW0n>;fqfFJ z?JVZJjfN6EoqvVdJK=N3CD>6H>tI&*?5h1%Q|Zj$EM;qOj7lMDSsti(s$}+L#NmG^ z6{+hOxSuNN$|zdr#=gLQ4mOc;h_poHa`9C-INqag-Wg(_fy*|O-zej~-@^Tr_<^W` z*mZNue^WLasQP%XNEQ|3mck@Rmj6zpf_8nH{|*$8wHR8x`)?N(1eEINKfX>>K1|xy zW&SfX%I1Hp=oXgmkMrWY>3Kf|V47jL_Y9xjd(o&B^Cz|gI5Q6e)V{GP4H=7w-Iegv zEO`^lO+f?boZclRu~ZTc14XMKcg0RsPaIA7xBj43UIr^lI~~nnI^@482WU(EsiIPj zN9qnE^>j?g^MX=>-!3{lc6Nnp$q-Z(5pVSp-Tk+IYDpv03>WM)Js785^jNCn<_?yp zmfe;X_%-m<5c(Si;cto&hBYvJ_cpM^E~U77T3wBK#%O0RkVx0ElAldtDvYUvy?~S< zB5HgTraWjo7`a_aORoA3PV+qWRy89TdvcaId|E2R_Y&01NT?3j3_kiB+AwUSOb+2h z`jWeZ%GYQkwST#W`4CaNreR`C*N=zExYvBl@$<$F#4P;aKf_e@wL#KB1I)n(=?p>Y zeYy8!8|N4GL;?YUdEta8qbKO|maz4;&GG8n+F2X&)h(p9OX@4UP=a-2t;q|oDy=M? z(C3RwJ)^F#zOyLnSnQeJiOx`$zY3YfN^yEcUSpI$TSEC`tQgvO5cm)=+zQ=V;@8e_ zN%Q2)3e-$e5$hIQehNgqIxb zy|bE<_h~cJty$|=Zsc{Wu`V~wkUil<^XX!JZ}+^;yqH*?`oAerI?({SPKOpbT`!`2 z1`19$BG64R$IbARaRC9sgK|At`XK;Q@BhkvO##8ht<40=-Th>?a3ln!0HqUtmZQ8H zJ+OcyFs%5vdk%o#%79$7rxtH^1+WbEj6oxbEqNlTt9)6Nq`E9ANyvh(Gg@NZ#2^EH zyoehvWQ_+RV`ldNX1sgd!dShCHC&Ev+TNjJ(Bcre`d^lXN!J<`Gnev zkZg2(9)Z2^i0i_U{(T~yM18e37cc8q@&=+a^Wl;sp&9y@mF_DTjD*gl`La zWD)w5_QGV=fQoC()dOh{yaqR;V~aH@MEs9H<+ax93hRWfSm%Z+L@J!i$c0SXRD6Vd zUrAr7xt<0Kt8+|W^L%)1&vISn#NU8(E`9~F)>Rv5UVOu~Ox6wh;|w>YntXHp#2%wL zJE*|X%{so^W^eOMBp|jtr?T`jYOf|=G0Re#W>a90Oy(CyI6Pomi?ELr9j3PoUqSo1 zvt&+;(SlqHY**a5o~m(2Pk@p0m~aBA*y-+N!mBlFGUj;v(h8;STrD||i@Op#^Zk35 z0>3&V9vUI-rssSByaf`cAk!DX$Gsw^j_8ec!~s0nR~o#af%ENKw!`CNk(9dV(i~k1 zv8u*V6!s5p0Wr3YOFE;58^SBY#9MpS`(fRE{FBUSXVW%7wA6Qw2d1Q^tB94`%8KAx z)R~b#v*IORR2{0>@XqnzDIIlQRcMx1Q1qG9 z)X<;vq;n|7&`#Ibs(r7EBZZCB3(h~qdq>>Y=ha6$eJ)nRjd6KeZgg$rUnjc-lSezm zp9fN5J9C(4MG^yxSYRcS5RGV&E!VUSJv+qYGPMvh7juNabKY1kZ~aGe@cHw0;&lI< z4gZd+MtGWjQ@jZ?h}e;Wl?|hP8$Wn2Ej@CpV82htt3nsauS8p?B&I3*@b&vH-JK5= zZDdeycR=v=?%0lMN3IUVzXRPG>wNa^PX31VqT>*y7F{_X;JdWcFazmE-zy6r%(VMv z?$Vzt|Gt>aPtX%VN9D&gW>*ltEa<&G?Xu67s>^x<{{?l7sSj&o@5uyH6k&SV zrnOq%g?Vmn!**EI?!wiv`M2C>b#^N&&%e_~1)|oyN&@TAl7*3`-Z-f@kV6v5za|M6 z2y3r=AZ_oD9NFwNmSL=QjHMk{=siMnBkSGnFePwWriQ@`dqk>GE5E6Nlpr5fUKvOs zcX(Pe4N%o+9mdGpDXVDu2^}<7MV=dwlM<(}asE%KL3(V!JR3sqOX@NChyHq=ytAknvIe@l1a}RhNxnX6V1dowHq_gl-M;p9pX>G)YTf5Xm>vk)wY`tEuAxvxE>p%n|((6 zKf~1Ucw>~ls^f&hHyFHFupeC8Fl=Arw*tL#5YcXkeTa%OinHOL5$S>Z48{j{1teII z0h?6FunX0r0bNBSEHORBeuB3TM_e_HA0gzw=PYUuE&!kNQ|vN6xiFC!sCl8LCepV= zm65x~O984YLTW2g;GxDHo(#cL?$6J47m|Xs0h&i#CY$1&2Tw{Oi138y95%J+s z;K9q;%Qe^dhY}Ep#h2mFu`e+N@kRc-(8n zh6!jjRM2!fREU*E9DI!y4_4eh9p3p@_rDXOQ{wK}=vm=`NA{fS{-shk%51@@lv*ie z^E=;1<13!46wD&(G#J@{0oXNcYdkj#hTHSmtBP@ELF7(%4Mq1REgOBA@=uWDeHDI_ zr=QEAc8_b=2*t1Xs%+*6Dx}JhD`WiOICAD}<2)EySwwyXw|FVrxo?;?KeGSl&TuSI z+}K+Hi7HJ$uVyiBW~9JRN~M-FJY{5CL;uj)LpkHAf8FliDUR7v7~)cX@aLE=X3w^O z!@BU;+j`!yU_3}a6bn{L*@(9TUxrz)=Db0RMah_SO~-mozBgWc6hZ>)g_x5I2_K@r zOY9hi!HNhi-=P!fOGislr87+@S2B&eBX9nqHi-yMq*bj2Pn{f)n3F5R&N|xsgKr0* zyqcS6=j8B9d8uI09kdIR)3JDuPW$2E*R?aY!`bI)$NA9@8s24+qU+&sI>VZWMvnz# z=AG#=kgCsi2tt}eAV<(sye_d#6gneff+!zeWwm`kHi7>UxESjj7cyq{BIHUhfxo+3vaG@T~ih{^^Q$Zfu(A-{_2xlA5c0vb!2ORwJ_<+&T-Wm;X zp_G~?4pv*4-Uk^BsgKQ9REt4AOtdH#N)O6`ZG-M}nXQbh9CXydVdsu{-|^0RNSz8q?iL#>NTvdvyiQx<@` zOPR3n`LN{cs?ET9V1br64>f*Ibx_$kV1BG8NF*=`qc^XYC~n02AB6UrhEbvwW#f;YuDajBpt~Iyqsv zAtJ=c(Ux8ta4xnXV;o&lTuRYgbz$PtztWt)K{lh&{e(x!dZR=hq&p&fLk} zZdZ{#7Qnw#Hl1wubM`JEnKpNI9COXewx|WBv_s`WgJ1XhH)+sP%0S=PymNF=_tsG* zLNncM0UxwdLF(bE_A->;mpgCptd2}0YY2zTq2uR}!Q+mcBPpp94c%`ldb7Z@{+8#o zY@cpq*XhGc_GV$?!N%<&Jq4d7G10)t+gF{;_{8Bw=pS$elx4N2m%~Q#s;!|yvxbxl zi6l~O7qWd?99N#fIbG6zEzB!p&qp3dp3_gR$oue}no5+9ZU_lMge(4=@`irfkDND~ ztDk_{{RNGzj%a`L`>i#P?Fw&K5A==(tXBS8)~=~(xhtG=2s*SqwC)yk<913SCayTo z7u)gmU&ZoWpmcOWl(ze(=Ia!~Cwv74GPCi&;l3Mq8kX$GP6F)<>F6u4d))^0&cu)r*An63wwdP{>R%{V|4o6L}M(m&*1R ztQ)-*D9ZZzW^~=Xj!){LPo1ilgAa1sTqYt~W3w@5{Aiqjw~W3d(%puOd(h3)y^#NI z@N}B=c;>5fAVAMYC|rCuGOE#B4-(caW80Xru^|4E9rtnfSQjtQ5QieSJPD?!Qs1JV z!S25ofVbygH61WPLej@Id~`5y@-z}RBjfT0tQ`g7_yn=9$`#H#oNm~2)j_d@U*u!D zQe)9Q4E`6`FQ&UylpsGcVS@@SsaYK);K5#6RQp3@5&}#q&}FQvR6oDqFwgB!oph_Y z2pb7eX0nd0I$sfO2wyJQ&IsDx%`t?3<{ZX>&|u1_p$tKMRhJ@CTT@YuqvwB9TAbQg zh%7;U2OZp6h`z?i=uqZ?$edcb70yy)#xPk34cTt)X2L;TK4)&=8QG}H^rDS z7gjlI=0PR1FH%4@L=!yR1f9^zR!4`RN0Daa%pNs;pxd;G+`pi|iJKW1&4b~^8C4WD zQa?HJ7>UB<_##wFVG6-%nD2Iqb-fRT655u%KHb`{u>B#g`kNcg<97uNGkdb{n;||lO#M1J!`1E`B3|;lO?fzFb z!Qj%Sm|o)Gn5TzBCwfZbBe=z@@oB*tqkyWhgRxUB>{>n6&i;Sl%N+%?UqD-a6CMXx zj*_#0e9J4%QSLPU7IfJY?HkZ^RGXvn#)JPphK^u$IdWa_D z9{+wroW^OHE@3*`1p-6QOE8Na;o+>g=%;W$m>-7CzT$pzr--pJH0oyC1Y-$5crKEE7(C}K zRLue%nTSQ4X&q-Oj9)LZ*w)inAb+1|IQkA7rwT>D4ir2=>uk&}*#@hsd`(yS3akp! zC711!m$|;1+|Bx$LX6q>FP&%-Uh`R5T23}=;P!CC{B*F#=jXFfCodaB2OTEEP_EI> zj0mdbJ5mdSL?HPs5qtLDKM!sZb}wr-zN;Xe0MLJ9WIF~chhrRKoVKQq)fBpTReFzq zesk$DYNZH{5sSN9s7HdZlJa`mmtwu8d4gQF8*F3)b1+(%K-j~1L`KPzH2^kiw#ytE z!Jlkz^!(J=5?|QwCpGR<)GU3nSaE+a>cuq0@QrAXOoe2+qi-(tdqG`AkAQDjG6o9C z<|9ifBVihrj}iGx>h01#sX)WSptK*f&*7cYpOc^WpTu70mswNX#=63?OSRR;_d&Ch z8@;*?=dH@lXi?@&)Q~;gL|A2HJpuyUY#^lDC@66Thi=EHJ|k6c(5y}ofh<0crC=Ua zVCsD;u7Nn#Mr;iWYeGI!^5G0vVW>~0xA~tRl(DVZx_|K*tRzGb?&VoI0h+;VvjmgV*Y#>P?8rrznR#M{`Z0 z-4Mvc&GcYDEI}$6otJtdcSjrMP*wd=S-wszL=TVQZ`&MLr_qRZp->&mH7joS8z!S* zv26?cpz$$BYSh`lwm(Es;<-eoxUj~HzPDs0hT*+KO=I@*~Lue+f>v75x0PoTxZlF%gn3 zuDB6k*Y4_V*Irz;-z%Z9)lhx3_oi|Cc13|vQC)cf5$+W5D2~4ByvCYu)i7-7>Qe8s z<}IHi$Pd_7%Cl;>5*eaB@<}05;?cnBs&DEfe^AfORmu=b4;Rla?V%x~Gel-=PNJF9 zF4S964#K->QlWBzI?qExy>^~ChoEV&KT4AK8|!)Ir9nN*T-)u|Q}tiqP)fY56lwe4 zfzfXgFLTLDI^k6Mvb4%b6&Fm|c6x&I%*?T$$4R#_FcPD|-oa!w2c7F?_|bMhFCCh2)-jE1 zIc*rU1VXa1z(2!5ZuTzn3D1}LKMn7TfhBIt zf>UFfsD$aIYfn7Nx!l-35KTJ~>ErY;Cn)6RQ#7KUaAqvob+B=F^4BLzbB=i`&UJNb zJLNsvNb5Jiv!Kdh6^UEA!yjN{Xs&$p6=5T`>+yZDv?(QLr~)3~mqVWPTtD6L_DWY@ zn5Dql3~q6`k(QPjh+0COHkyND^40dcT%jp z%bu4*a>m{6Qi)~ZP7#7BA8*Nh&@E(!*s7bCoua+N8V!*?_BpB(`m7yoWY3zi}41P->O`*ah zhkW=Ku#>v2XUYMvmdxosjtYAVKrvat6W^Vz*sqDK-xVQRSvffsZmdDSX{TaI>DmEU zG5RNclwG3dR+@|yyR(#hjrQ^V3=A#|FkT{J{E zM%d7NJ@Stu!xNv47v!I3pC7_DePG)}$^Ey%@QW6Z6Y`$uVL3I=-Alvp5!$q;5A1w{{q* z1X46P1&)b88RUuomDL6G+7miP0tu&8TqJ|gI9bp-jrpf$nub$pAUI>mWLF1vkc=a_P$f7+;4W1 z@Fj^QOkzb0Z*Zz9Z2-7D6Z$dL@Nu&GABii?FlW5n;9)TTdvfd0|4@@z%sAXG=47+s zG9L~33I)MXd~MidYFX*zN048DBWm!Flt*--q3Q_T84K#zgcQ5N;pq-Jq4lZ5x%GJJ z9rmp|l2JXq8))kqy|0U1{h_j3fx(1-%>Ld`GUWy~!E^XS#kXUPY#j+_6vNho0hWCR z5d#7fm`{!r`*VGG45fVbGFFKv?Yz35(5C{Fqoc!dK6r z%sZ~A;M|ol7P9>`tFmvyniCUdoDGQEr3Ejr?t25oyf4zQ=lI+1!*}}(z)LXZPsr4yCOV%Z*t)AH1RM` zj4I2OzMaisugh&8O5N7Fp`#F|{uQQ-oU1M^uj1qUq0FN=%dwRMqR6j6536Naxt?&c z(PFws>Vk>5s)K536LO*qinvP-f{Znt`JV;EK1>z>)-Pa!KTR@HuwmwPK*SCtDKg<= z2eKf9q`sM){Tq()$RnoUnplltxM_jVspSn%8k*G=TR0>nPo}DL(ES5yY}_ajlxWrW zb@%$$KRmzfdE0wA&BZH2cY&3TOSxw`h`lRUFMTiu2&V|oefgZ>`Y$h(;1 zp}BKWv#eI^o)ns|;8?Xgx<)<36Uo)|C8APS$}zGB=}zDB?f+u0Z#%C|cIh{3=%M<& zTaQZT;w-5x;sVi$JK1v;r*x0+muPKCCr(054xSzr`;SLt(gXS@>OgPkHtS0@{^Y}4ioudQHVkgd zLQh2Psc~d)O`I^>t6_c04CM)UdlkB^6+z^JHNB=ay9%~OYrC=juH_&8rWt0V_*Oj{ zW_YTPNhs1-#%}JEO}JHS&X@7dqWwd|P1LR&NLsFPVR}fD?5n$^ju3}@o4B`g9Bv-s z5s~@^81cDI5C^M7DIf=!(}!B%KCt<+gg@Uakb1VAcJ^~AYlJmYD}o*z48JmwC~}gP z;!~42EurB^<`Ul@#uOeH?P;d@Y@5RY+L$*=hgkEJ4DviUElhWedHQiV%iB)m$`)nH$-g7wiI$W7M#aG4YSxZ_MEJOU1K)? zBv5-$KoEV9lxo(YYiqlAcelr;XMR+bzZtRGE*_3yLY4AiTL@Rin5T&5Aa5~Co(}{EBIsYFI4p<1=zyaL?4Oq`kC8tN`I{yO|SzD3~F9e79dP{ zk=4gk1!OHxuh8nqZ12B9`XQ+WZuhnzsX!iU0yT4x7 z2IftF)I57mfv%dRngJI`f*t!63bsheezMr;nwaDL0)BZYzfWtEyU@S3lB z@1jsMGx49sC6joa6CyQxOB{s%C`U%AGVFf<5S(Q#ceVJuK75B#*W!oi|C_QkpKC$3 zk}HclzWu+Zt_7;8D_!f@mbqG_Ep@a$$atNRDpjnCR20&x$W(+BDMWcBql}FhfjS`X zaN1f#K$^A|qf$sMg;XINK``Z&qaxq~j1YMRArc+|LV&y?A*KVTC4?&1dwcUkk}%ffkX zLxS^0W%f$A^3z+5S%1Vp@z-9;BUB=?fo5lpzSPrub)R)c^^ck*Yu?4~ITtJVHwSqa z(YuA)z&{D~+p*E17c*k)U0`9ID@WeympNnt@jA_g1C@kDb;sMGZh~&wnJ;IKoSexf zZhLAW*li-Jh3EOX365#4Maw(R6cANd^hvA_oZh!PvH%1e&chb3a9OM4EW!_MN&atX z*_*3pmVGSi?JV|98@zXE-KB{zWdGR`w(D-BiLv)#$W5=!v5)nr+AxF5(H5QLe%$%c zx;#rxgYR`LsvkSa8Vti8cpe0nNW)!(Poxf4pEw?vRGG2r=g%W)1`QyO1|Q^GH)RU% z0H+;d-o+fqKNl&~Du)*Tf#9(2O&(mj^~$)JUL3-rBIOk36PGAi#u6CvY zCKD=GD|``%hs5#imyEI79*ld(Bj1$d=eIn?$EPNO`>i##YZ?bau0OWVUHYN&({XNy zexS5&=6GMy`^Yu<#*&AzyLU!Knd7F~BdDtBnaNbd7EBB~AH>uI91Xvo{%(mWFOYXk z^GTPxR7PF9!Q$OEprcBVSu|DnM1wRp<)Gq@-~7OL|Apv7qDL149#Jt%Inc=FzUj@8 zV;i3pvSUJ7@*8>OgeTA0RD11prPMHl2sLTJ8;H;Fl~$(8FB_KLNk?RgD_}%RmpkaW zR(I2HVfyR~v_R!SrW)@DxYQ}e8$5?&;_oe&PZq82icJXEGbx*53KD;GDH;?JqCXWK z&u`sw&1)-v*XaY!Ne8wzoKoaYtg>`J>WOY1Dhp}q4ml*=u1%hEi=LinQ*So+qA#my z3z6)T<~X%=*S=bymW@$Iz)^%N`zPH%ZqgAw?#7^8X+5Au(ix8*EmTG$=#!)PQ7fg3 zjkEna__y0MlT8Vzl}Veem2Lzo>anVs$jbH*8v1+qozlg4*3P^D11mDoeEE8~Z~yCp zOZ?ayYI&#X;M8K`Dz=wv5W#I@bbQu*CqA~Qi(Qp}T7toh^e;aZv6njp5OGgUAZ*UAr9 zqo3`qIM`qiD~9!oInH7$OclpAN;lIU!|xcz@FWj#EiqddB}5XUwfb6$?Jum`W-Vr> zJb_q{eoWWeR1hAwXjm(`ROAfqD_cl?SW`hC4~DiKrndDQ(5iC!P3Bh_tEu{C_6lh` z$gLg*+7pG|;AhS2IFqhEn_6va{gy?8Jm_Hw?E&ubELYwSis}r*K0{+J~vsO;LB1Rg5M#;n||)s1@}$SSwX)XmH&* z8<$ZZLP=!)1Li`-<4q-Of?B7(@19MpM(=Wc-nYu5bbc*^f30okdanYlP2ZziUkQzd z=x<>%^U_R+*OX+g^gT$%o+JhHqbsN+M5uO@2<-_S#u!L@oFPVMa@6zIbpsg0!vI~3 zq^1J=B3Vm*o`T0YXSM6zZamxa&+!h(MkExi-*gJOml56O$j#p@zLK{wu{sWJMTH0K z7A9m=TV0X!Tzl{*mBcJha?S{uBI{f=>|h)jfZZlWmO!$|fy(o45JMs4H40kfTVGPR_zOnT51#5j6sb6K9QPgVv z**fNVB%>r1np9;UHtt@gSt*qK`yVYlCzV^yH_R#S#!m6M?+@kvz{_O>vMORCE!+_; zCo|$v;Q^n=f@N!J)OB-oPJTw}K+?_=M}Z`%P4q9sfBt{hZ^bJT`o`uvrWXo(lv1}0 znV-;Rm=Cr1>d4C%j-fQ8f5l_TeDJj+MIAER%F=~?r}BR7@!l?nzG+k5R@sywG%85$ zH9V~uUPP>DWu^5@CGvH_2WFqMHKb>$9V4IJd$zKWpQiXefO`V_*+ti$8rc5|;l^ofb!Yzm(cOU6202mpT&130U( zM6l-z-;n?Z#b$3m5H-$A=4Ng!(1eNWN*GHpIE8_`dsL~K9G%zSGmAUim8u3}!ZOkG zl$w9>OCP!0g|x_sMD^kBGc=LNM!8QAlIp?t>wcR{tc{QVN4ab@7OP2p?Q6_vL1}u` zZ;TD*Z%d1hTn~IRpq)J!?fS2Vnc|*D>PVk;tpV&yIdWULR*h+D(!>d(BdV=rhurb*c|T=Dw?^N7%V-g>6k>ar$O6C}9}mzl+p5XJlxd*I}& zTQHu6-8X2SsxS1686Dmsv2QCG$Tso)J=NH6gIvQN3W2?LrR_7m4n0NQ$l7rLDM(sP`;%I${JuXDL#LEKWv}~ z25O`!AbN+V=<&&-w)ONVg{?IodD{CB@w5|r>~5eGC;u{_43SHegsHdfX%@TdsNl)j zkZ0149^`XJhA|8?q3d<)Yg62X-X@uXz8MQFF2!1EZF3p4?(&ci4kc3Uo8GI*;-*w6 z`QLlM1U&cvP2nIzvj1zZd`p2^MpvXlE>1ZNQk(eES-h3DIyr0|m9G-V`L>dEyjzg@ zHq(1E{)$ps9cq#jUapuQSp(KJSbpQ^#HWKhx?=j$zwZewd{uiA+lIu*&$w{zeXA5( zk^a|NREv4nv^RH+m6`8GM^goG-4l=Kxye-sE^>i>voJM4+wPu6v`Q!P+n8PG-b?A2 zcAle(u$qLZfv?A#3|Bt|1BpKnK{zQ0&hpuQMg!&SSQ?&L1~+bKoLK*GTtWQ8c;NI6 zoczo@)jK*^Po^-2q`N>#VwuVi0QtRfx-&Z>{d8p&__!oi^r>lUU2t@MnyRGwH~wvV38N7@%1w(<1+H2 zci$Q-0EQ)enz6o|(Lal7gq~yrcE@ev`k|m8Cb^63ll>v{y;eO3*Qiq~mzK=o1}JB# zMW49&WGJ&t#pnnoobGP;VE}g=kQkBlR9o+SR|_QMLfBfsWviA0iM@FDsH+`Do&1o@F0K2E^YXHhG3rPm$oh1=x zFf~ANwK4O}&CfvJpL}aM+NV_fEfMuv*{!isA8o{ntJ{fNYXT!}3B*H9>}|c*7GfsX zj)FB#E+@8}3|;AMoQ;g2E%B65E`hw2}>)!iG8L#N}XCK9KdBIJo*BYwp zO{m!cA8yYOq<_9%dB=W4LfvgWj3uPnOXaMcWee8}L^DuL=%8PSo%W7rZ^-&}V!y{* z^<#PYT29)a8T)RE))pa8VXH}(Fq}vjrwnZfZDrr;?Mh z`z3hWtjurURfTM9a9Pr2$H?0)?YUjxlJ69sEOP5I<@pE8N%w1`DG6*ZBYM<2-;lMJ^Xf<>CbN;BZ@jjx z309H>&7c5CWM6%&i8FzC`?2?E@Fyqsa@5$bfP95@U5=gCqDQqiO&?k;8zbb*~)upR9`6Bm2IRJ^frTD!C`A zzGyuC>~;6WcR}^e0Z_N7aVP#;I=i&c!TgE!OPN=R@Z5Wb8AwYMeSrbP=~rKofIcJu|XnWAYv3LGsqGfL&`+M=Aizo|w_oYPP zf9A|@U;DqieEa`?s)W=@q*bVne1$&wlN}3Uv0Uw>qho=Oa(3hRTKxwkoeo!62P;@I z3AwoLB|~d?{Zo%v@LT`tczWWcsaDRPh0yeuUOfzW(RI>EkFPH_Cu%T3cuL`)Qow}* aj|t1E^gVekq0j;agReOM89woQ-~Rxbej!i* literal 0 HcmV?d00001 From 226819c8d171d3fd6f4c5d87649b460d84de6fa9 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Wed, 26 Nov 2014 12:19:19 -0800 Subject: [PATCH 318/408] add details on protected branches Add some details from https://about.gitlab.com/2014/11/26/keeping-your-code-protected/: Who can: * Force push to non-protected branches * Force push to protected branches * Remove protected branches --- doc/permissions/permissions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index d561868c8bb..e21384d21dc 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -19,6 +19,7 @@ If a user is a GitLab administrator they receive all permissions. | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ | +| Force push to non-protected branches | | | ✓ | ✓ | ✓ | | Remove non-protected branches | | | ✓ | ✓ | ✓ | | Add tags | | | ✓ | ✓ | ✓ | | Write a wiki | | | ✓ | ✓ | ✓ | @@ -35,6 +36,8 @@ If a user is a GitLab administrator they receive all permissions. | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | +| Force push to protected branches | | | | | | +| Remove protected branches | | | | | | ## Group From c85d4af88921aba31afd39c3403fe2d41381c2ca Mon Sep 17 00:00:00 2001 From: Marc Radulescu Date: Thu, 27 Nov 2014 10:24:19 +0100 Subject: [PATCH 319/408] remove unnecessarry image --- doc/development/architecture.md | 2 +- doc/development/cubby_holes.jpg | Bin 132815 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 doc/development/cubby_holes.jpg diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 68c813d4339..209182e7742 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -23,7 +23,7 @@ Users come to NginX and request actions to be done by workers in the office; - The goods in the warehouse (metadata, issues, merge requests etc); - The users coming to the front desk (permissions) -**Redis** is a [communication board with “cubby holes”](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/cubby_holes.jpg) that can contain tasks for office workers; +**Redis** is a communication board with “cubby holes” that can contain tasks for office workers; **Sidekiq** is a worker that primarily handles sending out emails. It takes tasks from the Redis communication board; diff --git a/doc/development/cubby_holes.jpg b/doc/development/cubby_holes.jpg deleted file mode 100644 index afbb58bb950f85f6cb9ad722426158e880ae0a70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132815 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 aj|t1E^gVekq0j;agReOM89woQ-~Rxbej!i* From 0dcc1e88a4a9a1fe4745421474fcb3e93bfb87ef Mon Sep 17 00:00:00 2001 From: Vincent Robert Date: Thu, 27 Nov 2014 15:48:19 +0100 Subject: [PATCH 320/408] Add Dockerfile to build an Omnibus GitLab image --- docker/Dockerfile | 36 ++++++++++++++++++++++++++++++++++++ docker/README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ docker/gitlab.rb | 31 +++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100644 docker/gitlab.rb diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000000..b1720e15114 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,36 @@ +# Data: docker run --name gitlab_data genezys/gitlab:7.5.1 /bin/true +# Run: docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data genezys/gitlab:7.5.1 + +FROM ubuntu:14.04 +MAINTAINER Vincent Robert + +# Install required packages +RUN apt-get update -q \ + && DEBIAN_FRONTEND=noninteractive apt-get install -qy \ + openssh-server \ + wget \ + && apt-get clean + +# Download & Install GitLab +RUN TMP_FILE=$(mktemp); \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.1-omnibus.5.2.0.ci-1_amd64.deb \ + && dpkg -i $TMP_FILE \ + && rm -f $TMP_FILE + +# Manage SSHD through runit +RUN mkdir -p /opt/gitlab/sv/sshd/supervise \ + && mkfifo /opt/gitlab/sv/sshd/supervise/ok \ + && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \ + && chmod a+x /opt/gitlab/sv/sshd/run \ + && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \ + && mkdir -p /var/run/sshd + +# Expose web & ssh +EXPOSE 80 22 + +# Volume & configuration +VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"] +ADD gitlab.rb /etc/gitlab/ + +# Default is to run runit & reconfigure +CMD gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000000..ca56a9b35a4 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,42 @@ +What is GitLab? +=============== + +GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations. + + + +![GitLab Logo](https://gitlab.com/uploads/appearance/logo/1/brand_logo-c37eb221b456bb4b472cc1084480991f.png) + + +How to use this image. +====================== + +I recommend creating a data volume container first, this will simplify migrations and backups: + + docker run --name gitlab_data genezys/gitlab:7.5.1 /bin/true + +This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it: + +- `/var/opt/gitlab` for application data +- `/var/log/gitlab` for logs +- `/etc/gitlab` for configuration + +Then run GitLab: + + docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data genezys/gitlab:7.5.1 + +You can then go to `http://localhost:8080/` (or most likely `http://192.168.59.103:8080/` if you use boot2docker). Next time, you can just use `docker start gitlab` and `docker stop gitlab`. + + +How to configure GitLab. +======================== + +This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. + +To access GitLab configuration, you can start a new container using the shared data volume container: + + docker run -ti --rm --volumes-from gitlab_data ubuntu vi /etc/gitlab/gitlab.rb + +**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab. + +You can find all available options in [GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). diff --git a/docker/gitlab.rb b/docker/gitlab.rb new file mode 100644 index 00000000000..da909db01f8 --- /dev/null +++ b/docker/gitlab.rb @@ -0,0 +1,31 @@ +# External URL should be your Docker instance. +# By default, this example is the "standard" boot2docker IP. +# Always use port 80 here to force the internal nginx to bind port 80, +# even if you intend to use another port in Docker. +external_url "http://192.168.59.103/" + +# Some configuration of GitLab +# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration +gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' +gitlab_rails['gitlab_support_email'] = 'support@example.com' +gitlab_rails['time_zone'] = 'Europe/Paris' + +# SMTP settings +# You must use an external server, the Docker container does not install an SMTP server +gitlab_rails['smtp_enable'] = true +gitlab_rails['smtp_address'] = "smtp.example.com" +gitlab_rails['smtp_port'] = 587 +gitlab_rails['smtp_user_name'] = "user" +gitlab_rails['smtp_password'] = "password" +gitlab_rails['smtp_domain'] = "example.com" +gitlab_rails['smtp_authentication'] = "plain" +gitlab_rails['smtp_enable_starttls_auto'] = true + +# Enable LDAP authentication +# gitlab_rails['ldap_enabled'] = true +# gitlab_rails['ldap_host'] = 'ldap.example.com' +# gitlab_rails['ldap_port'] = 389 +# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain' +# gitlab_rails['ldap_allow_username_or_email_login'] = false +# gitlab_rails['ldap_uid'] = 'uid' +# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com' From 385105382b62f10ce84730ccb4f15aecea189470 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Fri, 28 Nov 2014 07:55:59 +0100 Subject: [PATCH 321/408] Make docker image file user agnostic, to prevent confusion over official images. --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index b1720e15114..38a48867ca9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ -# Data: docker run --name gitlab_data genezys/gitlab:7.5.1 /bin/true -# Run: docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data genezys/gitlab:7.5.1 +# Data: docker run --name gitlab_data USER/IMAGE:TAG /bin/true +# Run: docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data USER/IMAGE:TAG FROM ubuntu:14.04 MAINTAINER Vincent Robert From e08255ceea2af40f66039c7768be8de5122649f1 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Fri, 28 Nov 2014 11:13:49 +0100 Subject: [PATCH 322/408] Make the docker commands so that people can build their own images. --- docker/Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 38a48867ca9..70e8ad93423 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,9 @@ -# Data: docker run --name gitlab_data USER/IMAGE:TAG /bin/true -# Run: docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data USER/IMAGE:TAG +# At this moment GitLab doesn't have official Docker images. +# Build your own based on the Omnibus packages with the following commands. +# The first commands assumes you're in the GitLab repo root directory. +# Build: sudo docker build --tag gitlab_image docker/. +# Data: sudo docker run --name gitlab_data gitlab /bin/true +# Run: sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image FROM ubuntu:14.04 MAINTAINER Vincent Robert From 64ab6c9ed54d1c0a86f4c3bb6b87fcac882da0c0 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 28 Nov 2014 15:01:41 +0100 Subject: [PATCH 323/408] Add 'MemoryKiller' Sidekiq middleware When enabled, this middleware allows Sidekiq to detect that its RSS has exceeded a maximum value, triggering a graceful shutdown. This middleware should be combined with external process supervision that will restart Sidekiq after the graceful shutdown, such as Runit. --- CHANGELOG | 2 +- config/initializers/4_sidekiq.rb | 1 + .../sidekiq_middleware/memory_killer.rb | 45 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 lib/gitlab/sidekiq_middleware/memory_killer.rb diff --git a/CHANGELOG b/CHANGELOG index 417bd3c2b4d..85178d0dfd7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,7 +4,7 @@ v 7.6.0 - Add CRON=1 backup setting for quiet backups - - - - + - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) - - - diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb index 228b14cb526..b8a7fd624a5 100644 --- a/config/initializers/4_sidekiq.rb +++ b/config/initializers/4_sidekiq.rb @@ -15,6 +15,7 @@ Sidekiq.configure_server do |config| config.server_middleware do |chain| chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger + chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MAX_RSS'] end end diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb new file mode 100644 index 00000000000..3ef46627916 --- /dev/null +++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb @@ -0,0 +1,45 @@ +module Gitlab + module SidekiqMiddleware + class MemoryKiller + # Wait 30 seconds for running jobs to finish during graceful shutdown + GRACEFUL_SHUTDOWN_WAIT = 30 + + def call(worker, job, queue) + yield + current_rss = get_rss + return unless max_rss > 0 && current_rss > max_rss + + Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\ + "#{max_rss}" + Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}" + Process.kill('SIGUSR1', Process.pid) + + Sidekiq.logger.warn "spawning thread that will send SIGTERM to PID "\ + "#{Process.pid} in #{graceful_shutdown_wait} seconds" + Thread.new do + sleep(graceful_shutdown_wait) + Process.kill('SIGTERM', Process.pid) + end + end + + private + + def get_rss + output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{Process.pid})) + return 0 unless status.zero? + + output.to_i + end + + def max_rss + @max_rss ||= ENV['SIDEKIQ_MAX_RSS'].to_s.to_i + end + + def graceful_shutdown_wait + @graceful_shutdown_wait ||= ( + ENV['SIDEKIQ_GRACEFUL_SHUTDOWN_WAIT'] || GRACEFUL_SHUTDOWN_WAIT + ).to_i + end + end + end +end From d336127a20ce22a9512123595a887c4207b748e9 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 28 Nov 2014 15:19:03 +0100 Subject: [PATCH 324/408] Add comments to the MemoryKiller middleware --- lib/gitlab/sidekiq_middleware/memory_killer.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb index 3ef46627916..0fb09d3f228 100644 --- a/lib/gitlab/sidekiq_middleware/memory_killer.rb +++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb @@ -12,10 +12,13 @@ module Gitlab Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\ "#{max_rss}" Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}" + # SIGUSR1 tells Sidekiq to stop accepting new jobs Process.kill('SIGUSR1', Process.pid) Sidekiq.logger.warn "spawning thread that will send SIGTERM to PID "\ "#{Process.pid} in #{graceful_shutdown_wait} seconds" + # Send the final shutdown signal to Sidekiq from a separate thread so + # that the current job can finish Thread.new do sleep(graceful_shutdown_wait) Process.kill('SIGTERM', Process.pid) From 824ad40699bf4ee22f0dbe53b5ae344c916c0d9f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 29 Nov 2014 12:01:32 +0200 Subject: [PATCH 325/408] Make clear that the upgrader script does not update gitlab-shell. --- doc/update/upgrader.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md index 44e18a9ed42..0a9f242d9ab 100644 --- a/doc/update/upgrader.md +++ b/doc/update/upgrader.md @@ -10,6 +10,8 @@ If you have local changes to your GitLab repository the script will stash them a **GitLab Upgrader is available only for GitLab version 6.4.2 or higher.** +**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.** + ## 0. Backup cd /home/git/gitlab From 674cbe939cb65e67479e0d73f4004e52c4546791 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 29 Nov 2014 21:34:18 +0200 Subject: [PATCH 326/408] Dont allow project creation without repository Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects_controller.rb | 7 ++-- app/models/project.rb | 21 ++++++++++ app/services/projects/create_service.rb | 54 +++++++++++++------------ app/views/projects/create.js.haml | 13 ------ app/views/projects/new.html.haml | 2 +- 5 files changed, 54 insertions(+), 43 deletions(-) delete mode 100644 app/views/projects/create.js.haml diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b3181fa310e..ead0127b515 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -19,10 +19,11 @@ class ProjectsController < ApplicationController def create @project = ::Projects::CreateService.new(current_user, project_params).execute - flash[:notice] = 'Project was successfully created.' if @project.saved? - respond_to do |format| - format.js + if @project.saved? + redirect_to project_path(@project), notice: 'Project was successfully created.' + else + render 'new' end end diff --git a/app/models/project.rb b/app/models/project.rb index d2576bb85d0..d7570684ac8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -586,4 +586,25 @@ class Project < ActiveRecord::Base def origin_merge_requests merge_requests.where(source_project_id: self.id) end + + def create_repository + if gitlab_shell.add_repository(path_with_namespace) + true + else + errors.add(:base, "Failed to create repository") + false + end + end + + def repository_exists? + !!repository.exists? + end + + def create_wiki + ProjectWiki.new(self, self.owner).wiki + true + rescue ProjectWiki::CouldNotCreateWikiError => ex + errors.add(:base, "Failed create wiki") + false + end end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 12386792aab..3672b623806 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -37,37 +37,24 @@ module Projects @project.creator = current_user - if @project.save - log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") - system_hook_service.execute_hooks_for(@project, :create) + Project.transaction do + @project.save - unless @project.group - @project.team << [current_user, :master] - end - - @project.update_column(:last_activity_at, @project.created_at) - - if @project.import? - @project.import_start - else - GitlabShellWorker.perform_async( - :add_repository, - @project.path_with_namespace - ) - end - - if @project.wiki_enabled? - begin - # force the creation of a wiki, - ProjectWiki.new(@project, @project.owner).wiki - rescue ProjectWiki::CouldNotCreateWikiError => ex - # Prevent project observer crash - # if failed to create wiki - nil + unless @project.import? + unless @project.create_repository + raise 'Failed to create repository' end end end + if @project.persisted? + if @project.wiki_enabled? + @project.create_wiki + end + + after_create_actions + end + @project rescue => ex @project.errors.add(:base, "Can't save project. Please try again later") @@ -84,5 +71,20 @@ module Projects namespace = Namespace.find_by(id: namespace_id) current_user.can?(:create_projects, namespace) end + + def after_create_actions + log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") + system_hook_service.execute_hooks_for(@project, :create) + + unless @project.group + @project.team << [current_user, :master] + end + + @project.update_column(:last_activity_at, @project.created_at) + + if @project.import? + @project.import_start + end + end end end diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml deleted file mode 100644 index 89710d3a09a..00000000000 --- a/app/views/projects/create.js.haml +++ /dev/null @@ -1,13 +0,0 @@ -- if @project.saved? - - if @project.import? - :plain - location.href = "#{import_project_path(@project)}"; - - else - :plain - location.href = "#{project_path(@project)}"; -- else - :plain - $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); - $('.project-submit').enable(); - $('.save-project-loader').hide(); - $('.project-edit-container').show(); diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index f5cd0f21e01..e77ef84f51c 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -3,7 +3,7 @@ = render 'projects/errors' .project-edit-content - = form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f| + = form_for @project, html: { class: 'new_project form-horizontal' } do |f| .form-group.project-name-holder = f.label :name, class: 'control-label' do %strong Project name From 880478b21e7c9b0068b3e14b8f7fb58ada2c232e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 29 Nov 2014 21:59:28 +0200 Subject: [PATCH 327/408] Proper wiki restore. Fixes #845 --- CHANGELOG | 2 +- lib/backup/repository.rb | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 85178d0dfd7..6a0768b516b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,7 @@ v 7.6.0 - Fork repository to groups - New rugged version - Add CRON=1 backup setting for quiet backups - - + - Fix failing wiki restore - - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) - diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index f39fba23cf5..6b04b23cf46 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -79,16 +79,20 @@ module Backup wiki = ProjectWiki.new(project) + $progress.print " * #{wiki.path_with_namespace} ... " + if File.exists?(path_to_bundle(wiki)) - $progress.print " * #{wiki.path_with_namespace} ... " cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}) - if system(*cmd, silent) - $progress.puts " [DONE]".green - else - puts " [FAILED]".red - puts "failed: #{cmd.join(' ')}" - abort 'Restore failed' - end + else + cmd = %W(git init --bare #{path_to_repo(wiki)}) + end + + if system(*cmd, silent) + $progress.puts " [DONE]".green + else + puts " [FAILED]".red + puts "failed: #{cmd.join(' ')}" + abort 'Restore failed' end end From a8df4ee9e2b7881cea46ebc6b3e7889d13e3b5e5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 30 Nov 2014 00:49:51 +0200 Subject: [PATCH 328/408] Separate web page for projects without repository Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/no_repo.html.haml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/views/projects/no_repo.html.haml diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml new file mode 100644 index 00000000000..dd576243510 --- /dev/null +++ b/app/views/projects/no_repo.html.haml @@ -0,0 +1,22 @@ +%h2 + %i.fa.fa-warning + No repository + +%p.slead + The repository for this project does not exist. + %br + This means you can not push code until you create an empty repository or import existing one. +%hr + +.no-repo-actions + = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do + Create empty bare repository + + %strong.prepend-left-10.append-right-10 or + + = link_to new_project_import_path(@project), class: 'btn' do + Import repository + +- if can? current_user, :remove_project, @project + .prepend-top-20 + = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" From 9d937293136afd7994218b8dc72bb0956fb19eeb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 30 Nov 2014 00:50:25 +0200 Subject: [PATCH 329/408] Move projects import to separate resource. Add bare repo creation to repository controller Signed-off-by: Dmitriy Zaporozhets --- .../projects/imports_controller.rb | 49 +++++++++++++++++++ .../projects/repositories_controller.rb | 9 +++- app/controllers/projects_controller.rb | 39 +++++---------- app/models/project.rb | 2 +- app/views/projects/import.html.haml | 31 ------------ app/views/projects/imports/new.html.haml | 21 ++++++++ app/views/projects/imports/show.html.haml | 9 ++++ config/routes.rb | 5 +- 8 files changed, 101 insertions(+), 64 deletions(-) create mode 100644 app/controllers/projects/imports_controller.rb delete mode 100644 app/views/projects/import.html.haml create mode 100644 app/views/projects/imports/new.html.haml create mode 100644 app/views/projects/imports/show.html.haml diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb new file mode 100644 index 00000000000..b8350642804 --- /dev/null +++ b/app/controllers/projects/imports_controller.rb @@ -0,0 +1,49 @@ +class Projects::ImportsController < Projects::ApplicationController + # Authorize + before_filter :authorize_admin_project! + before_filter :require_no_repo + before_filter :redirect_if_progress, except: :show + + def new + end + + def create + @project.import_url = params[:project][:import_url] + + if @project.save + @project.reload + + if @project.import_failed? + @project.import_retry + else + @project.import_start + end + end + + redirect_to project_import_path(@project) + end + + def show + unless @project.import_in_progress? + if @project.import_finished? + redirect_to(@project) and return + else + redirect_to new_project_import_path(@project) and return + end + end + end + + private + + def require_no_repo + if @project.repository_exists? + redirect_to(@project) and return + end + end + + def redirect_if_progress + if @project.import_in_progress? + redirect_to project_import_path(@project) and return + end + end +end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index bcd14a1c847..3a90c1c806d 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -1,7 +1,14 @@ class Projects::RepositoriesController < Projects::ApplicationController # Authorize before_filter :authorize_download_code! - before_filter :require_non_empty_project + before_filter :require_non_empty_project, except: :create + before_filter :authorize_admin_project!, only: :create + + def create + @project.create_repository + + redirect_to @project + end def archive unless can?(current_user, :download_code, @project) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ead0127b515..fbd9e5f2a5b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -4,7 +4,7 @@ class ProjectsController < ApplicationController before_filter :repository, except: [:new, :create] # Authorize - before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import] + before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] layout 'navless', only: [:new, :create, :fork] before_filter :set_title, only: [:new, :create] @@ -48,7 +48,7 @@ class ProjectsController < ApplicationController def show if @project.import_in_progress? - redirect_to import_project_path(@project) + redirect_to project_import_path(@project) return end @@ -61,39 +61,22 @@ class ProjectsController < ApplicationController respond_to do |format| format.html do - if @project.empty_repo? - render "projects/empty", layout: user_layout + if @project.repository_exists? + if @project.empty_repo? + render "projects/empty", layout: user_layout + else + @last_push = current_user.recent_push(@project.id) if current_user + render :show, layout: user_layout + end else - @last_push = current_user.recent_push(@project.id) if current_user - render :show, layout: user_layout + render "projects/no_repo", layout: user_layout end end + format.json { pager_json("events/_events", @events.count) } end end - def import - if @project.import_finished? - redirect_to @project - return - end - end - - def retry_import - unless @project.import_failed? - redirect_to import_project_path(@project) - end - - @project.import_url = project_params[:import_url] - - if @project.save - @project.reload - @project.import_retry - end - - redirect_to import_project_path(@project) - end - def destroy return access_denied! unless can?(current_user, :remove_project, @project) diff --git a/app/models/project.rb b/app/models/project.rb index d7570684ac8..daf4bdd0aad 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -136,7 +136,7 @@ class Project < ActiveRecord::Base state_machine :import_status, initial: :none do event :import_start do - transition :none => :started + transition [:none, :finished] => :started end event :import_finish do diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml deleted file mode 100644 index 4513c89e784..00000000000 --- a/app/views/projects/import.html.haml +++ /dev/null @@ -1,31 +0,0 @@ -- if @project.import_in_progress? - .save-project-loader - .center - %h2 - %i.fa.fa-spinner.fa-spin - Import in progress. - %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)} - %p Please wait while we import the repository for you. Refresh at will. - :javascript - new ProjectImport(); - -- elsif @project.import_failed? - .save-project-loader - .center - %h2 - Import failed. Retry? - %hr - - if can?(current_user, :admin_project, @project) - = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f| - .form-group.import-url-data - = f.label :import_url, class: 'control-label' do - %span Import existing git repo - .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' - .bs-callout.bs-callout-info - This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. - %br - The import will time out after 4 minutes. For big repositories, use a clone/push combination. - For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} - .form-actions - = f.submit 'Retry import', class: "btn btn-create", tabindex: 4 diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml new file mode 100644 index 00000000000..6c3083e49f5 --- /dev/null +++ b/app/views/projects/imports/new.html.haml @@ -0,0 +1,21 @@ +%h3.page-title + - if @project.import_failed? + Import failed. Retry? + - else + Import repository + +%hr + += form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f| + .form-group.import-url-data + = f.label :import_url, class: 'control-label' do + %span Import existing git repo + .col-sm-10 + = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' + .bs-callout.bs-callout-info + This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. + %br + The import will time out after 4 minutes. For big repositories, use a clone/push combination. + For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} + .form-actions + = f.submit 'Start import', class: "btn btn-create", tabindex: 4 diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml new file mode 100644 index 00000000000..2d1fdafed24 --- /dev/null +++ b/app/views/projects/imports/show.html.haml @@ -0,0 +1,9 @@ +.save-project-loader + .center + %h2 + %i.fa.fa-spinner.fa-spin + Import in progress. + %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)} + %p Please wait while we import the repository for you. Refresh at will. + :javascript + new ProjectImport(); diff --git a/config/routes.rb b/config/routes.rb index 470fe7f4dc1..723104daf13 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -186,8 +186,6 @@ Gitlab::Application.routes.draw do post :upload_image post :toggle_star get :autocomplete_sources - get :import - put :retry_import end scope module: :projects do @@ -232,8 +230,9 @@ Gitlab::Application.routes.draw do end resource :fork, only: [:new, :create] + resource :import, only: [:new, :create, :show] - resource :repository, only: [:show] do + resource :repository, only: [:show, :create] do member do get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex } end From f7d8467af94d6d4783419c8536275810de2bc19e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 30 Nov 2014 00:52:04 +0200 Subject: [PATCH 330/408] Update CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 85178d0dfd7..fd902050913 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,8 +7,8 @@ v 7.6.0 - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) - - - - - - + - Create project with repository in synchrony + - Added ability to create empty repo or import existing one if project does not have repository - - - From 5c1496a4d803de6efe5c81b5d0a3bba7599bf81f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 30 Nov 2014 10:50:15 +0200 Subject: [PATCH 331/408] Improve project factories Signed-off-by: Dmitriy Zaporozhets --- features/steps/shared/project.rb | 2 +- spec/factories/projects.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index bd7e6e1d8b3..0bd5653538c 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -131,7 +131,7 @@ module SharedProject end step 'public empty project "Empty Public Project"' do - create :empty_project, :public, name: "Empty Public Project" + create :project_empty_repo, :public, name: "Empty Public Project" end step 'project "Community" has comments' do diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 23314b3b1a4..60eb73e4a95 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -27,6 +27,10 @@ # FactoryGirl.define do + # Project without repository + # + # Project does not have bare repository. + # Use this factory if you dont need repository in tests factory :empty_project, class: 'Project' do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } @@ -47,6 +51,20 @@ FactoryGirl.define do end end + # Project with empty repository + # + # This is a case when you just created a project + # but not pushed any code there yet + factory :project_empty_repo, parent: :empty_project do + after :create do |project| + project.create_repository + end + end + + # Project with test repository + # + # Test repository source can be found at + # https://gitlab.com/gitlab-org/gitlab-test factory :project, parent: :empty_project do path { 'gitlabhq' } From 31f7560332ba710d962a4055dbd8c6c02d5c06cb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 30 Nov 2014 15:25:17 +0200 Subject: [PATCH 332/408] Update CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 18c559ecc17..c9ac79566bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,7 +11,7 @@ v 7.6.0 - Added ability to create empty repo or import existing one if project does not have repository - - - - + - Reactivate highlight.js language autodetection - - From 191aa9712eeb8fe39e8947dc681cefe4221044ec Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 30 Nov 2014 18:24:05 +0200 Subject: [PATCH 333/408] Properly fix wiki restore. ProjectWiki.new() creates a new wiki git repository, so any tries to bare clone a bundle fail. With this patch we remove the newly created wiki.git before restoring from the backup bundle. --- lib/backup/repository.rb | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 6b04b23cf46..e18bc804437 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -59,7 +59,7 @@ module Backup FileUtils.mkdir_p(repos_path) Project.find_each(batch_size: 1000) do |project| - $progress.print "#{project.path_with_namespace} ... " + $progress.print " * #{project.path_with_namespace} ... " project.namespace.ensure_dir_exist if project.namespace @@ -79,20 +79,22 @@ module Backup wiki = ProjectWiki.new(project) - $progress.print " * #{wiki.path_with_namespace} ... " - if File.exists?(path_to_bundle(wiki)) - cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}) - else - cmd = %W(git init --bare #{path_to_repo(wiki)}) - end + $progress.print " * #{wiki.path_with_namespace} ... " - if system(*cmd, silent) - $progress.puts " [DONE]".green - else - puts " [FAILED]".red - puts "failed: #{cmd.join(' ')}" - abort 'Restore failed' + # If a wiki bundle exists, first remove the empty repo + # that was initialized with ProjectWiki.new() and then + # try to restore with 'git clone --bare'. + FileUtils.rm_rf(path_to_repo(wiki)) + cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}) + + if system(*cmd, silent) + $progress.puts " [DONE]".green + else + puts " [FAILED]".red + puts "failed: #{cmd.join(' ')}" + abort 'Restore failed' + end end end From ba955fe17403e005e9f551e3b31e3d539681f4d1 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 1 Dec 2014 11:41:30 +0100 Subject: [PATCH 334/408] Change gitlab to gitlab_image on data run and add tail, thanks Vincent Robert. --- docker/Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 70e8ad93423..dddab4f74b5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,10 @@ # At this moment GitLab doesn't have official Docker images. # Build your own based on the Omnibus packages with the following commands. # The first commands assumes you're in the GitLab repo root directory. -# Build: sudo docker build --tag gitlab_image docker/. -# Data: sudo docker run --name gitlab_data gitlab /bin/true -# Run: sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image +# sudo docker build --tag gitlab_image docker/ +# sudo docker run --name gitlab_data gitlab_image /bin/true +# sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image +# sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log FROM ubuntu:14.04 MAINTAINER Vincent Robert From 25a566da0aae470d7819820500e9343c7f462dfc Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 1 Dec 2014 15:11:26 +0100 Subject: [PATCH 335/408] Remove unused password argument from notification We were still passing a 'password' argument around, but it is not used anywhere because we send a password reset link in the welcome email nowadays. --- app/mailers/emails/profile.rb | 3 +-- app/services/notification_service.rb | 2 +- spec/mailers/notify_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index f8a7d133d1d..6d7f8eb4b02 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -1,8 +1,7 @@ module Emails module Profile - def new_user_email(user_id, password, token = nil) + def new_user_email(user_id, token = nil) @user = User.find(user_id) - @password = password @target_url = user_url(@user) @token = token mail(to: @user.email, subject: subject("Account was created for you")) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index c9a1574b84e..2b6217e2e29 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -107,7 +107,7 @@ class NotificationService # Notify new user with email after creation def new_user(user, token = nil) # Don't email omniauth created users - mailer.new_user_email(user.id, user.password, token) unless user.extern_uid? + mailer.new_user_email(user.id, token) unless user.extern_uid? end # Notify users on new note in system diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index e06e8826e5c..a0c37587b23 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -46,7 +46,7 @@ describe Notify do token = 'kETLwRaayvigPq_x3SNM' - subject { Notify.new_user_email(new_user.id, new_user.password, token) } + subject { Notify.new_user_email(new_user.id, token) } it_behaves_like 'an email sent from GitLab' @@ -83,7 +83,7 @@ describe Notify do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") } - subject { Notify.new_user_email(new_user.id, new_user.password) } + subject { Notify.new_user_email(new_user.id) } it_behaves_like 'an email sent from GitLab' From 06b7907c2afe0cb0fa25f4cdef0ff470710de2f9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Dec 2014 16:25:10 +0200 Subject: [PATCH 336/408] Fix deploy keys permission check in internal api Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/git_access.rb | 28 ++++++++++++++++++---------- spec/lib/gitlab/git_access_spec.rb | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 3452240dad8..5f8cb19efdf 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -8,15 +8,7 @@ module Gitlab def check(actor, cmd, project, changes = nil) case cmd when *DOWNLOAD_COMMANDS - if actor.is_a? User - download_access_check(actor, project) - elsif actor.is_a? DeployKey - actor.projects.include?(project) - elsif actor.is_a? Key - download_access_check(actor.user, project) - else - raise 'Wrong actor' - end + download_access_check(actor, project) when *PUSH_COMMANDS if actor.is_a? User push_access_check(actor, project, changes) @@ -32,7 +24,23 @@ module Gitlab end end - def download_access_check(user, project) + def download_access_check(actor, project) + if actor.is_a?(User) + user_download_access_check(actor, project) + elsif actor.is_a?(DeployKey) + if actor.projects.include?(project) + build_status_object(true) + else + build_status_object(false, "Deploy key not allowed to access this project") + end + elsif actor.is_a? Key + user_download_access_check(actor.user, project) + else + raise 'Wrong actor' + end + end + + def user_download_access_check(user, project) if user && user_allowed?(user) && user.can?(:download_code, project) build_status_object(true) else diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 1addba55787..66e87e57cbc 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -46,6 +46,25 @@ describe Gitlab::GitAccess do it { subject.allowed?.should be_false } end end + + describe 'deploy key permissions' do + let(:key) { create(:deploy_key) } + + context 'pull code' do + context 'allowed' do + before { key.projects << project } + subject { access.download_access_check(key, project) } + + it { subject.allowed?.should be_true } + end + + context 'denied' do + subject { access.download_access_check(key, project) } + + it { subject.allowed?.should be_false } + end + end + end end describe 'push_access_check' do From 612b8806ddc7881421e26a9dbfe465d6445fb3d6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Dec 2014 16:55:33 +0200 Subject: [PATCH 337/408] Fix internal API for missing project or key Signed-off-by: Dmitriy Zaporozhets --- lib/api/internal.rb | 13 +++++++++---- spec/requests/api/internal_spec.rb | 24 +++++++++++++++++++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 1648834f034..180e50611cf 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -33,15 +33,20 @@ module API end project = Project.find_with_namespace(project_path) - return false unless project + + unless project + return Gitlab::GitAccessStatus.new(false, 'No such project') + end actor = if params[:key_id] - Key.find(params[:key_id]) + Key.find_by(id: params[:key_id]) elsif params[:user_id] - User.find(params[:user_id]) + User.find_by(id: params[:user_id]) end - return false unless actor + unless actor + return Gitlab::GitAccessStatus.new(false, 'No such user or key') + end access.check( actor, diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 53b7808d4c3..4faa1f9b964 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -26,7 +26,7 @@ describe API::API, api: true do end end - describe "GET /internal/allowed" do + describe "POST /internal/allowed" do context "access granted" do before do project.team << [user, :developer] @@ -140,7 +140,7 @@ describe API::API, api: true do archive(key, project) response.status.should == 200 - response.body.should == 'true' + JSON.parse(response.body)["status"].should be_true end end @@ -149,10 +149,28 @@ describe API::API, api: true do archive(key, project) response.status.should == 200 - response.body.should == 'false' + JSON.parse(response.body)["status"].should be_false end end end + + context 'project does not exist' do + it do + pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists')) + + response.status.should == 200 + JSON.parse(response.body)["status"].should be_false + end + end + + context 'user does not exist' do + it do + pull(OpenStruct.new(id: 0), project) + + response.status.should == 200 + JSON.parse(response.body)["status"].should be_false + end + end end def pull(key, project) From f53e0fff47eda03296dee95dbd44b6f5a78c6269 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Dec 2014 17:40:45 +0200 Subject: [PATCH 338/408] Show username in comment header for easier mention Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/notes/_note.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index a25c5e207fb..b2abdf0035d 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -18,6 +18,8 @@ %i.fa.fa-trash-o.cred Remove = link_to_member(@project, note.author, avatar: false) + %span.author-username + = '@' + note.author.username %span.note-last-update = note_timestamp(note) From 9a8ffadc39bf3d9742d4c623d261bee4c6d9e5bf Mon Sep 17 00:00:00 2001 From: Mark Riedesel Date: Thu, 13 Nov 2014 12:40:55 -0600 Subject: [PATCH 339/408] Improve Monokai highlight style to match original The current monokai style in highlightjs is not very true to the original and lacks colors for certain syntactic items. This change's goal is to bring the highlightjs monokai style in line with the original design from http://www.monokai.nl/blog/2006/07/15/textmate-color-theme/ --- CHANGELOG | 1 + app/assets/stylesheets/highlight/monokai.scss | 31 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c9ac79566bd..c8fae4588f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 7.6.0 - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) - - + - Monokai highlighting style now more faithful to original design (Mark Riedesel) - Create project with repository in synchrony - Added ability to create empty repo or import existing one if project does not have repository - diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 36bc5df2f44..dffa2dc9ed2 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -29,28 +29,30 @@ .hljs-tag, .hljs-tag .hljs-title, - .hljs-keyword, - .hljs-literal, .hljs-strong, .hljs-change, .hljs-winutils, .hljs-flow, .lisp .hljs-title, .clojure .hljs-built_in, + .hljs-keyword, .nginx .hljs-title, .tex .hljs-special { color: #F92672; } .hljs { - color: #DDD; + color: #F8F8F2; } - .hljs .hljs-constant, - .asciidoc .hljs-code { + .asciidoc .hljs-code, + .markdown .hljs-code, + .hljs-literal, + .hljs-function .hljs-keyword { color: #66D9EF; } + .hljs-code, .hljs-class .hljs-title, .hljs-header { @@ -62,18 +64,27 @@ .hljs-symbol, .hljs-symbol .hljs-string, .hljs-value, + .hljs-constant, + .hljs-number, .hljs-regexp { - color: #BF79DB; + color: #AE81FF; + } + + .hljs-string { + color: #E6DB74; + } + + .hljs-params { + color: #fd971f; } .hljs-link_url, .hljs-tag .hljs-value, - .hljs-string, .hljs-bullet, .hljs-subst, .hljs-title, .hljs-emphasis, - .haskell .hljs-type, + .hljs-type, .hljs-preprocessor, .hljs-pragma, .ruby .hljs-class .hljs-parent, @@ -99,12 +110,12 @@ } .hljs-comment, - .java .hljs-annotation, + .hljs-annotation, .smartquote, .hljs-blockquote, .hljs-horizontal_rule, - .python .hljs-decorator, .hljs-template_comment, + .hljs-decorator, .hljs-pi, .hljs-doctype, .hljs-deletion, From 86c55106a0fb00533299efb96ba72ac91efc4276 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 1 Dec 2014 17:45:25 +0100 Subject: [PATCH 340/408] Change twitter handle from gitlabhq -> gitlab --- app/views/shared/_promo.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml index 3400c345c4c..3596aabe309 100644 --- a/app/views/shared/_promo.html.haml +++ b/app/views/shared/_promo.html.haml @@ -1,5 +1,5 @@ .gitlab-promo = link_to 'Homepage', promo_url = link_to "Blog", promo_url + '/blog/' - = link_to "@gitlabhq", "https://twitter.com/gitlabhq" + = link_to "@gitlab", "https://twitter.com/gitlab" = link_to "Requests", "http://feedback.gitlab.com/" From 64919745544cd09cdb510bf15e9522280d61fdde Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 1 Dec 2014 18:58:37 +0100 Subject: [PATCH 341/408] Disable Sidekiq arguments logging by default --- CHANGELOG | 3 +++ config/initializers/4_sidekiq.rb | 2 +- doc/development/README.md | 1 + doc/sidekiq_debugging.md | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 doc/sidekiq_debugging.md diff --git a/CHANGELOG b/CHANGELOG index c9ac79566bd..c4839bc1136 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,9 @@ v 7.6.0 - - +v 7.5.2 + - Don't log Sidekiq arguments by default + v 7.5.0 - API: Add support for Hipchat (Kevin Houdebert) - Add time zone configuration in gitlab.yml (Sullivan Senechal) diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb index b8a7fd624a5..75c543c0f47 100644 --- a/config/initializers/4_sidekiq.rb +++ b/config/initializers/4_sidekiq.rb @@ -14,7 +14,7 @@ Sidekiq.configure_server do |config| } config.server_middleware do |chain| - chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger + chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MAX_RSS'] end end diff --git a/doc/development/README.md b/doc/development/README.md index 20db6662aca..c31e5d7ae97 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -4,3 +4,4 @@ - [Shell commands](shell_commands.md) in the GitLab codebase - [Rake tasks](rake_tasks.md) for development - [CI setup](ci_setup.md) for testing GitLab +- [Sidekiq debugging](sidekiq_debugging.md) diff --git a/doc/sidekiq_debugging.md b/doc/sidekiq_debugging.md new file mode 100644 index 00000000000..cea11e5f126 --- /dev/null +++ b/doc/sidekiq_debugging.md @@ -0,0 +1,14 @@ +# Sidekiq debugging + +## Log arguments to Sidekiq jobs + +If you want to see what arguments are being passed to Sidekiq jobs you can set +the SIDEKIQ_LOG_ARGUMENTS environment variable. + +``` +SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start +``` + +It is not recommend to enable this setting in production because some Sidekiq +jobs (such as sending a password reset email) take secret arguments (for +example the password reset token). From 5b92ddb80994ff6ab992dcdb39e3a27c687f0dc3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 08:30:56 +0200 Subject: [PATCH 342/408] Mention mobile UI improvements in CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c9ac79566bd..b2b9b1f227e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,7 +12,7 @@ v 7.6.0 - - - Reactivate highlight.js language autodetection - - + - Mobile UI improvements - v 7.5.0 From 4cbe72d76722ce6c3d327ec62f54478a6e955d32 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 11:31:57 +0200 Subject: [PATCH 343/408] UI improvements mostly for mobile screens Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/timeline.scss | 17 +++++++++++++++++ app/assets/stylesheets/sections/issues.scss | 10 ++++++++++ app/assets/stylesheets/sections/notes.scss | 7 +++++-- app/views/projects/_issues_nav.html.haml | 8 ++++---- app/views/projects/issues/_issue.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 4 ++-- .../projects/merge_requests/index.html.haml | 2 -- app/views/projects/notes/_form.html.haml | 2 +- features/project/active_tab.feature | 2 +- features/steps/project/active_tab.rb | 4 ++-- features/steps/project/issues/milestones.rb | 4 ++-- 11 files changed, 45 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss index f29cf25fa4c..57e9e8ae5c5 100644 --- a/app/assets/stylesheets/generic/timeline.scss +++ b/app/assets/stylesheets/generic/timeline.scss @@ -75,3 +75,20 @@ } } } + +@media (max-width: $screen-xs-max) { + .timeline { + &:before { + background: none; + } + .timeline-entry .timeline-entry-inner { + .timeline-icon { + display: none; + } + + .timeline-content { + margin-left: 0; + } + } + } +} diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index ebf8a6125c7..9a5400fffbc 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -151,4 +151,14 @@ form.edit-issue { } } } + + .issue { + &:hover .issue-actions { + display: none !important; + } + + .issue-updated-at { + display: none; + } + } } diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 7eb42fddade..783f6ae02d3 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -36,13 +36,16 @@ ul.notes { font-size: 13px; } .author { - color: #555; + color: #333; font-weight: bold; font-size: 14px; &:hover { - color: $link_hover_color; + color: $link_color; } } + .author-username { + font-size: 14px; + } } .discussion { diff --git a/app/views/projects/_issues_nav.html.haml b/app/views/projects/_issues_nav.html.haml index 1e14a2deb8c..5b5d8eb9492 100644 --- a/app/views/projects/_issues_nav.html.haml +++ b/app/views/projects/_issues_nav.html.haml @@ -2,7 +2,7 @@ - if project_nav_tab? :issues = nav_link(controller: :issues) do = link_to project_issues_path(@project), class: "tab" do - Browse Issues + Issues - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do = link_to project_merge_requests_path(@project), class: "tab" do @@ -19,7 +19,7 @@ - if current_controller?(:issues) - if current_user - %li + %li.hidden-xs = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do %i.fa.fa-rss @@ -45,8 +45,8 @@ .pull-right %button.btn.btn-default.sidebar-expand-button %i.icon.fa.fa-list - + - if can? current_user, :write_merge_request, @project - = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do + = link_to new_project_merge_request_path(@project), class: "btn btn-new pull-left", title: "New Merge Request" do %i.fa.fa-plus New Merge Request diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 7525812696f..85a3d2b6c01 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -28,7 +28,7 @@ %span.task-status = issue.task_status - .pull-right + .pull-right.issue-updated-at %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} .issue-labels diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 1ee2e1bdae8..0a719fc642e 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -7,7 +7,7 @@ %i.fa.fa-check MERGED - else - %span.pull-right + %span.pull-right.hidden-xs - if merge_request.for_fork? %span.light #{merge_request.source_project_namespace}: @@ -31,7 +31,7 @@ %span.task-status = merge_request.task_status - .pull-right + .pull-right.hidden-xs %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} .merge-request-labels diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index cd1e48ca976..a6d90a68b11 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,8 +1,6 @@ = render "projects/issues_nav" .row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.fa.fa-list.fa-2x .col-md-3.responsive-side = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project), labels: true, redirect: 'merge_requests', entity: 'merge_request' diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index c68b3817e79..5bc0e60bbe8 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -29,7 +29,7 @@ = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel - .note-form-option + .note-form-option.hidden-xs %a.choose-btn.btn.js-choose-note-attachment-button %i.fa.fa-paperclip %span Choose File ... diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index 8d3e0bd967f..ed548177837 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -110,7 +110,7 @@ Feature: Project Active Tab Scenario: On Project Issues/Browse Given I visit my project's issues page - Then the active sub tab should be Browse Issues + Then the active sub tab should be Issues And no other sub tabs should be active And the active main tab should be Issues diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 83796b0ba88..bb42d15eae5 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -89,8 +89,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps click_link('Labels') end - step 'the active sub tab should be Browse Issues' do - ensure_active_sub_tab('Browse Issues') + step 'the active sub tab should be Issues' do + ensure_active_sub_tab('Issues') end step 'the active sub tab should be Milestones' do diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb index 89d7af3c9ee..cce87a6d981 100644 --- a/features/steps/project/issues/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps milestone = @project.milestones.find_by(title: "v2.2") page.should have_content(milestone.title[0..10]) page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") + page.should have_content("Issues") end step 'I click link "v2.2"' do @@ -28,7 +28,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps milestone = @project.milestones.find_by(title: "v2.3") page.should have_content(milestone.title[0..10]) page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") + page.should have_content("Issues") end step 'project "Shop" has milestone "v2.2"' do From 8f60144782ba564c63d757c11a72440202f2490c Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 10:32:24 +0100 Subject: [PATCH 344/408] Change avatar file size to 200kb. --- CHANGELOG | 2 +- app/models/user.rb | 2 +- app/views/profiles/show.html.haml | 2 +- app/views/shared/_choose_group_avatar_button.html.haml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 417bd3c2b4d..7f0c5f8436d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,7 +8,7 @@ v 7.6.0 - - - - - + - Change maximum avatar file size from 100KB to 200KB - - - diff --git a/app/models/user.rb b/app/models/user.rb index fc191a78f53..1cddd85ada0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -124,7 +124,7 @@ class User < ActiveRecord::Base validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? } validate :unique_email, if: ->(user) { user.email_changed? } - validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + validates :avatar, file_size: { maximum: 200.kilobytes.to_i } before_validation :generate_password, on: :create before_validation :sanitize_attrs diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index d6b52f86154..640104fdad1 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -83,7 +83,7 @@   %span.file_name.js-avatar-filename File name... = f.file_field :avatar, class: "js-user-avatar-input hidden" - .light The maximum file size allowed is 100KB. + .light The maximum file size allowed is 200KB. - if @user.avatar? %hr = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml index f32c2d388a7..299c0bd42a2 100644 --- a/app/views/shared/_choose_group_avatar_button.html.haml +++ b/app/views/shared/_choose_group_avatar_button.html.haml @@ -4,4 +4,4 @@   %span.file_name.js-avatar-filename File name... = f.file_field :avatar, class: 'js-group-avatar-input hidden' -.light The maximum file size allowed is 100KB. +.light The maximum file size allowed is 200KB. From 6eeaef6dc4cc0cda9c80bf46f38eae9f640debec Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 11:47:12 +0200 Subject: [PATCH 345/408] Smaller tabs for mobile view Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/common.scss | 4 ---- app/assets/stylesheets/generic/mobile.scss | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 app/assets/stylesheets/generic/mobile.scss diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index cd2f4e45e3c..2fc738c18d8 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -330,10 +330,6 @@ table { } } -@media (max-width: $screen-xs-max) { - .container .content { margin-top: 20px; } -} - .wiki .highlight, .note-body .highlight { margin-bottom: 9px; } diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss new file mode 100644 index 00000000000..c164b07b104 --- /dev/null +++ b/app/assets/stylesheets/generic/mobile.scss @@ -0,0 +1,17 @@ +/** Common mobile (screen XS) styles **/ +@media (max-width: $screen-xs-max) { + .container .content { + margin-top: 20px; + } + + .nav.nav-tabs > li > a { + padding: 10px; + font-size: 12px; + margin-right: 3px; + + .badge { + display: none; + } + } +} + From bff0034584f47b64cad2813dc6db9bb571faecce Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 2 Dec 2014 14:39:58 +0100 Subject: [PATCH 346/408] Revert "Remove the lowest memory requirement of 512MB." This reverts commit 5cf6d5949d6c776e24d3bd5c0b417000f1efc57a. --- doc/install/requirements.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index fd59ac8a073..ed194253148 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -50,6 +50,11 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ### Memory +- 512MB is the absolute minimum but we do not recommend this amount of memory. +You will either need to configure 512MB or 1.5GB of swap space. +With 512MB of swap space you must configure only one unicorn worker. +With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). +If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users @@ -85,7 +90,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o ## Supported web browsers - Chrome (Latest stable version) -- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) +- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Safari 7+ (known problem: required fields in html5 do not work) - Opera (Latest released version) - IE 10+ From 79912d6870ec019fa0100c1ced1c5a2c1f9c3227 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 2 Dec 2014 14:46:16 +0100 Subject: [PATCH 347/408] System with 512MB of RAM can be used but swap size must be increased. --- doc/install/requirements.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index ed194253148..2b60c3560a5 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -51,10 +51,11 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ### Memory - 512MB is the absolute minimum but we do not recommend this amount of memory. -You will either need to configure 512MB or 1.5GB of swap space. -With 512MB of swap space you must configure only one unicorn worker. +You will need to configure minimum 1.5GB of swap space. +With 1.5GB of swap space you must configure only one unicorn worker. With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow. +Consider installing GitLab on Ubuntu as installation on CentOS could be unsuccessful with this amount of memory. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users From 0ec4685837e2ca83d1bdebd72f99ddb725dc609c Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 2 Dec 2014 14:48:42 +0100 Subject: [PATCH 348/408] Update libv8 from 3.16.14.3 to 3.16.14.7 This selfish change fixes 'bundle install' on my (OS X) development machine. --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index b6c1dcfa331..7871f49d0bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM addressable (~> 2.3) letter_opener (1.1.2) launchy (~> 2.2) - libv8 (3.16.14.3) + libv8 (3.16.14.7) listen (2.3.1) celluloid (>= 0.15.2) rb-fsevent (>= 0.9.3) From 0afa07f7f07c68c74de20f5a353fba60c1752826 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 15:19:43 +0100 Subject: [PATCH 349/408] Add troubleshooting section to the docker documentation. --- docker/Dockerfile | 6 +++--- docker/README.md | 8 ++++++-- docker/troubleshooting.md | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 docker/troubleshooting.md diff --git a/docker/Dockerfile b/docker/Dockerfile index dddab4f74b5..6a0b7b7976d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,6 @@ # sudo docker build --tag gitlab_image docker/ # sudo docker run --name gitlab_data gitlab_image /bin/true # sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image -# sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log FROM ubuntu:14.04 MAINTAINER Vincent Robert @@ -12,8 +11,9 @@ MAINTAINER Vincent Robert # Install required packages RUN apt-get update -q \ && DEBIAN_FRONTEND=noninteractive apt-get install -qy \ - openssh-server \ - wget \ + openssh-server \ + wget \ + vim \ && apt-get clean # Download & Install GitLab diff --git a/docker/README.md b/docker/README.md index ca56a9b35a4..b528b22336f 100644 --- a/docker/README.md +++ b/docker/README.md @@ -8,7 +8,7 @@ GitLab offers git repository management, code reviews, issue tracking, activity ![GitLab Logo](https://gitlab.com/uploads/appearance/logo/1/brand_logo-c37eb221b456bb4b472cc1084480991f.png) -How to use this image. +How to use this image ====================== I recommend creating a data volume container first, this will simplify migrations and backups: @@ -28,7 +28,7 @@ Then run GitLab: You can then go to `http://localhost:8080/` (or most likely `http://192.168.59.103:8080/` if you use boot2docker). Next time, you can just use `docker start gitlab` and `docker stop gitlab`. -How to configure GitLab. +How to configure GitLab ======================== This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. @@ -40,3 +40,7 @@ To access GitLab configuration, you can start a new container using the shared d **Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab. You can find all available options in [GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). + +Troubleshooting +========================= +Please see the [troubleshooting](troubleshooting.md) file in this directory. diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md new file mode 100644 index 00000000000..4916d742736 --- /dev/null +++ b/docker/troubleshooting.md @@ -0,0 +1,23 @@ +# Troubleshooting + +This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245 +But it might contain useful commands for other cases as well. + +The configuration to add the postgres log in vim is: +postgresql['log_directory'] = '/var/log/gitlab/postgresql.log' + +# Commands + +sudo docker rm -f gitlab +sudo docker rm -f gitlab_data + +sudo docker build --tag gitlab_image docker/ +sudo docker run --name gitlab_data gitlab_image /bin/true + +sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get install -y vim; sudo vi /etc/gitlab/gitlab.rb + +sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image + +sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log + +sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/log/gitlab/postgresql.log From 9211b541d3eaa60401f4ab6a5d264f9179ad4160 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 16:22:23 +0200 Subject: [PATCH 350/408] Improve MR code reloading when push code Every time you pushed to master it updates merge requests that has master as target branch. So if you have 50 open merge requests point to master it will reload all of them every time you push a single commit to master. The funny thing is that after reloading diff of most merge requests looks the same. After this patch we update diff only if we push commit to master that includes in MR commits list. For example we have next repository: feature: A - B - C master: A We create merge requests #1 with code from feature to master. MR #1: B - C If we push to master commit D - MR will not be reloaded. So picture will look next: feature: A - B - C master: A - D MR #1: B - C And if we push to master commit B - MR will be reloaded. So picture will look next: feature: A - B - C master: A - B MR #1: C Signed-off-by: Dmitriy Zaporozhets --- app/services/merge_requests/refresh_service.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 74448998ddd..1a1704aea77 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -43,8 +43,22 @@ module MergeRequests merge_requests = filter_merge_requests(merge_requests) merge_requests.each do |merge_request| - merge_request.reload_code - merge_request.mark_as_unchecked + + if merge_request.source_branch == @branch_name + merge_request.reload_code + merge_request.mark_as_unchecked + else + mr_commit_ids = merge_request.commits.map(&:id) + push_commit_ids = @commits.map(&:id) + matches = mr_commit_ids & push_commit_ids + + if matches.any? + merge_request.reload_code + merge_request.mark_as_unchecked + else + merge_request.mark_as_unchecked + end + end end end From 40e80dbeb5d767a8a1f3142963f670fbad3ecf4c Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 16:18:32 +0100 Subject: [PATCH 351/408] Remove vim since it is of no use to running GitLab. --- docker/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6a0b7b7976d..292a7238d61 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,7 +13,6 @@ RUN apt-get update -q \ && DEBIAN_FRONTEND=noninteractive apt-get install -qy \ openssh-server \ wget \ - vim \ && apt-get clean # Download & Install GitLab From 8dd3a16227149405f2e38663c16099b53db1745d Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 16:24:55 +0100 Subject: [PATCH 352/408] Change vim command. --- docker/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md index 4916d742736..1d80473c7c2 100644 --- a/docker/troubleshooting.md +++ b/docker/troubleshooting.md @@ -14,7 +14,7 @@ sudo docker rm -f gitlab_data sudo docker build --tag gitlab_image docker/ sudo docker run --name gitlab_data gitlab_image /bin/true -sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get install -y vim; sudo vi /etc/gitlab/gitlab.rb +sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image From a33cb855302f189f3510fc2fbe73851d9d7a7b74 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 16:28:12 +0100 Subject: [PATCH 353/408] Add interactive commands. --- docker/troubleshooting.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md index 1d80473c7c2..415c8f785c0 100644 --- a/docker/troubleshooting.md +++ b/docker/troubleshooting.md @@ -21,3 +21,5 @@ sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --vol sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/log/gitlab/postgresql.log + +sudo docker run -ti --rm --volumes-from gitlab_data ubuntu /bin/sh From 835cbc06d8d9c773a3b405eca81650378a8ccdcd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 17:42:56 +0200 Subject: [PATCH 354/408] Reload mr code on force push too Signed-off-by: Dmitriy Zaporozhets --- app/services/merge_requests/refresh_service.rb | 7 ++++++- lib/gitlab/force_push_check.rb | 15 +++++++++++++++ lib/gitlab/git_access.rb | 9 +-------- 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 lib/gitlab/force_push_check.rb diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 1a1704aea77..baf0936cc3d 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -3,6 +3,7 @@ module MergeRequests def execute(oldrev, newrev, ref) return true unless ref =~ /heads/ + @oldrev, @newrev = oldrev, newrev @branch_name = ref.gsub("refs/heads/", "") @fork_merge_requests = @project.fork_merge_requests.opened @commits = @project.repository.commits_between(oldrev, newrev) @@ -35,6 +36,10 @@ module MergeRequests end end + def force_push? + Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev) + end + # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too def reload_merge_requests @@ -44,7 +49,7 @@ module MergeRequests merge_requests.each do |merge_request| - if merge_request.source_branch == @branch_name + if merge_request.source_branch == @branch_name || force_push? merge_request.reload_code merge_request.mark_as_unchecked else diff --git a/lib/gitlab/force_push_check.rb b/lib/gitlab/force_push_check.rb new file mode 100644 index 00000000000..6a52cdba608 --- /dev/null +++ b/lib/gitlab/force_push_check.rb @@ -0,0 +1,15 @@ +module Gitlab + class ForcePushCheck + def self.force_push?(project, oldrev, newrev) + return false if project.empty_repo? + + if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA + missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read + missed_refs.split("\n").size > 0 + else + false + end + end + end +end + diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 5f8cb19efdf..8b4729896b5 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -94,14 +94,7 @@ module Gitlab end def forced_push?(project, oldrev, newrev) - return false if project.empty_repo? - - if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA - missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read - missed_refs.split("\n").size > 0 - else - false - end + Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev) end private From 6f9d9ea09ead63bbed43b94f57edf5e05ade661c Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 16:57:52 +0100 Subject: [PATCH 355/408] Postgres log location is a directory. --- docker/troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md index 415c8f785c0..e2717a13b4a 100644 --- a/docker/troubleshooting.md +++ b/docker/troubleshooting.md @@ -4,7 +4,7 @@ This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2 But it might contain useful commands for other cases as well. The configuration to add the postgres log in vim is: -postgresql['log_directory'] = '/var/log/gitlab/postgresql.log' +postgresql['log_directory'] = '/var/log/gitlab/postgresql' # Commands @@ -20,6 +20,6 @@ sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --vol sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log -sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/log/gitlab/postgresql.log +sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current sudo docker run -ti --rm --volumes-from gitlab_data ubuntu /bin/sh From 3b643bc87ba126e00550e6a067e4327020452a1b Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 17:16:46 +0100 Subject: [PATCH 356/408] Move build to first step and add interactive commands. --- docker/troubleshooting.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md index e2717a13b4a..deab144841c 100644 --- a/docker/troubleshooting.md +++ b/docker/troubleshooting.md @@ -8,10 +8,12 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql' # Commands +```bash +sudo docker build --tag gitlab_image docker/ + sudo docker rm -f gitlab sudo docker rm -f gitlab_data -sudo docker build --tag gitlab_image docker/ sudo docker run --name gitlab_data gitlab_image /bin/true sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb @@ -23,3 +25,27 @@ sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitla sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current sudo docker run -ti --rm --volumes-from gitlab_data ubuntu /bin/sh +``` + +# Interactively + +```bash +# First start a GitLab container without starting GitLab +# This is almost the same as starting the GitLab container except: +# - we run interactively (-t -i) +# - we define TERM=linux because it allows to use arrow keys in vi (!!!) +# - we choose another startup command (bash) +sudo docker run -ti -e TERM=linux --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash + +# Configure GitLab to redirect PostgreSQL logs +echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb + +# You can now start GitLab manually from Bash (in the background) +gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start & + +# And tail the logs (PostgreSQL log may not exist immediately) +tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current + +# And get the memory +cat /proc/meminfo +``` From ed7760b1d7b58d07793437db78f960ed7c4ae182 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 2 Dec 2014 19:25:04 +0100 Subject: [PATCH 357/408] Add command to limit Postgres memory allocation, thanks Jacob. --- docker/troubleshooting.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md index deab144841c..442cc69ec58 100644 --- a/docker/troubleshooting.md +++ b/docker/troubleshooting.md @@ -40,7 +40,11 @@ sudo docker run -ti -e TERM=linux --name gitlab --publish 8080:80 --publish 2222 # Configure GitLab to redirect PostgreSQL logs echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb +# Prevent Postgres from allocating 25% of total memory +echo "postgresql['shared_buffers'] = '100MB'" >> /etc/gitlab/gitlab.rb + # You can now start GitLab manually from Bash (in the background) +# Maybe the command below is still missing something to run in the background gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start & # And tail the logs (PostgreSQL log may not exist immediately) From 12f8699296aed86b01cc455dfaa05305966fb5a8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 22:30:06 +0200 Subject: [PATCH 358/408] Fix safari 8 ui issue Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/_issues_nav.html.haml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/views/projects/_issues_nav.html.haml b/app/views/projects/_issues_nav.html.haml index 5b5d8eb9492..18628eb6207 100644 --- a/app/views/projects/_issues_nav.html.haml +++ b/app/views/projects/_issues_nav.html.haml @@ -27,14 +27,17 @@ .pull-right %button.btn.btn-default.sidebar-expand-button %i.icon.fa.fa-list - = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do - .append-right-10.hidden-xs.hidden-sm - = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } - = hidden_field_tag :state, params['state'] - = hidden_field_tag :scope, params['scope'] - = hidden_field_tag :assignee_id, params['assignee_id'] - = hidden_field_tag :milestone_id, params['milestone_id'] - = hidden_field_tag :label_id, params['label_id'] + + .pull-left + = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do + .append-right-10.hidden-xs.hidden-sm + = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } + = hidden_field_tag :state, params['state'] + = hidden_field_tag :scope, params['scope'] + = hidden_field_tag :assignee_id, params['assignee_id'] + = hidden_field_tag :milestone_id, params['milestone_id'] + = hidden_field_tag :label_id, params['label_id'] + - if can? current_user, :write_issue, @project = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do %i.fa.fa-plus From 5f15ed04fc58cfe8b7d54b3490248430575b16d9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 22:38:45 +0200 Subject: [PATCH 359/408] Respect current controller scope when using search from project area Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/_search.html.haml | 11 ++++++++++- app/views/search/_project_filter.html.haml | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 2460a6a014d..04f79846858 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -4,7 +4,16 @@ = hidden_field_tag :group_id, @group.try(:id) - if @project && @project.persisted? = hidden_field_tag :project_id, @project.id - = hidden_field_tag :search_code, true + + - if current_controller?(:issues) + = hidden_field_tag :scope, 'issues' + - elsif current_controller?(:merge_requests) + = hidden_field_tag :scope, 'merge_requests' + - elsif current_controller?(:wikis) + = hidden_field_tag :scope, 'wiki_blobs' + - else + = hidden_field_tag :search_code, true + - if @snippet || @snippets = hidden_field_tag :snippets, true = hidden_field_tag :repository_ref, @ref diff --git a/app/views/search/_project_filter.html.haml b/app/views/search/_project_filter.html.haml index c201b3d6c47..ad933502a28 100644 --- a/app/views/search/_project_filter.html.haml +++ b/app/views/search/_project_filter.html.haml @@ -25,6 +25,7 @@ = @search_results.notes_count %li{class: ("active" if @scope == 'wiki_blobs')} = link_to search_filter_path(scope: 'wiki_blobs') do + %i.fa.fa-book Wiki .pull-right = @search_results.wiki_blobs_count From c5bdc7e13ac4f953f83f226b0f2f5b45905d2bce Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Dec 2014 22:47:19 +0200 Subject: [PATCH 360/408] New label/milestone link from issue form opens in new window Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/_issuable_form.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 6cdfab933b4..dd40a719561 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -49,7 +49,7 @@ - else %span.light No open milestones available.   - = link_to 'Create new milestone', new_project_milestone_path(issuable.project) + = link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank .form-group = f.label :label_ids, class: 'control-label' do %i.icon-tag @@ -61,7 +61,7 @@ - else %span.light No labels yet.   - = link_to 'Create new label', new_project_label_path(issuable.project) + = link_to 'Create new label', new_project_label_path(issuable.project), target: :blank .form-actions - if issuable.new_record? From ba51a1dd328c453927d390147f5fa4be3e1f6fe7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 20 Oct 2014 12:56:01 +0300 Subject: [PATCH 361/408] Render cross reference in issue title Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/issue_box.scss | 5 +++++ app/helpers/gitlab_markdown_helper.rb | 12 ++++++++++++ app/views/projects/issues/show.html.haml | 3 +++ 3 files changed, 20 insertions(+) diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 94149594e24..79fbad4b946 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -113,6 +113,11 @@ padding: 10px 15px; } + .cross-project-ref { + float: left; + padding: 10px 15px; + } + .creator { float: right; padding: 10px 15px; diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 7d3cb749829..800cacdc2c2 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -254,4 +254,16 @@ module GitlabMarkdownHelper truncated end end + + def cross_project_reference(project, entity) + path = project.path_with_namespace + + if entity.kind_of?(Issue) + [path, entity.iid].join('#') + elsif entity.kind_of?(MergeRequest) + [path, entity.iid].join('!') + else + raise 'Not supported type' + end + end end diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index aad58e48f6c..685d9f96d51 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -38,6 +38,9 @@ - else Open + .cross-project-ref + = cross_project_reference(@project, @issue) + .creator Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)} From ed1c22568a9363161d20a1c38147d0ef0f019c2b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Dec 2014 11:40:04 +0200 Subject: [PATCH 362/408] Add cross-project reference tooltip for merge request Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/issues/show.html.haml | 1 + app/views/projects/merge_requests/show/_mr_box.html.haml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 685d9f96d51..01a1fabda26 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -39,6 +39,7 @@ Open .cross-project-ref + %i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'} = cross_project_reference(@project, @issue) .creator diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index 7e5a4eda508..866b236d827 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -8,6 +8,10 @@ - else Open + .cross-project-ref + %i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'} + = cross_project_reference(@project, @merge_request) + .creator Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)} From 9e4d39c0513fc91fc2c844d482e82a8e9df8927d Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Wed, 3 Dec 2014 12:41:47 +0100 Subject: [PATCH 363/408] Move commands to the readme, rename gitlab to gitlab_app, add PostgreSQL tweaks to gitlab.rb. --- docker/Dockerfile | 7 ------- docker/README.md | 30 +++++++++++++++++++++++------- docker/gitlab.rb | 6 ++++++ docker/troubleshooting.md | 18 +++++++++++++----- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 292a7238d61..3ffedd16e81 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,10 +1,3 @@ -# At this moment GitLab doesn't have official Docker images. -# Build your own based on the Omnibus packages with the following commands. -# The first commands assumes you're in the GitLab repo root directory. -# sudo docker build --tag gitlab_image docker/ -# sudo docker run --name gitlab_data gitlab_image /bin/true -# sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image - FROM ubuntu:14.04 MAINTAINER Vincent Robert diff --git a/docker/README.md b/docker/README.md index b528b22336f..a2a194bd42c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -11,21 +11,37 @@ GitLab offers git repository management, code reviews, issue tracking, activity How to use this image ====================== -I recommend creating a data volume container first, this will simplify migrations and backups: +At this moment GitLab doesn't have official Docker images. +Build your own based on the Omnibus packages with the following command (it assumes you're in the GitLab repo root directory): - docker run --name gitlab_data genezys/gitlab:7.5.1 /bin/true +```bash +sudo docker build --tag gitlab_image docker/ +``` -This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it: +We assume using a data volume container, this will simplify migrations and backups. +This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it. + +The directories on data container are: - `/var/opt/gitlab` for application data - `/var/log/gitlab` for logs - `/etc/gitlab` for configuration -Then run GitLab: +Create the data container with: - docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data genezys/gitlab:7.5.1 +```bash +sudo docker run --name gitlab_data gitlab_image /bin/true +``` -You can then go to `http://localhost:8080/` (or most likely `http://192.168.59.103:8080/` if you use boot2docker). Next time, you can just use `docker start gitlab` and `docker stop gitlab`. +After creating this run GitLab: + +```bash +sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image +``` + +It might take a while before the docker container is responding to queries. + +You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker). Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`. How to configure GitLab @@ -39,7 +55,7 @@ To access GitLab configuration, you can start a new container using the shared d **Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab. -You can find all available options in [GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). +You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). Troubleshooting ========================= diff --git a/docker/gitlab.rb b/docker/gitlab.rb index da909db01f8..7fddf309c01 100644 --- a/docker/gitlab.rb +++ b/docker/gitlab.rb @@ -4,6 +4,12 @@ # even if you intend to use another port in Docker. external_url "http://192.168.59.103/" +# Prevent Postgres from trying to allocate 25% of total memory +postgresql['shared_buffers'] = '1MB' + +# Configure GitLab to redirect PostgreSQL logs to the data volume +postgresql['log_directory'] = '/var/log/gitlab/postgresql' + # Some configuration of GitLab # You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md index 442cc69ec58..b1b70de5997 100644 --- a/docker/troubleshooting.md +++ b/docker/troubleshooting.md @@ -11,20 +11,22 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql' ```bash sudo docker build --tag gitlab_image docker/ -sudo docker rm -f gitlab +sudo docker rm -f gitlab_app sudo docker rm -f gitlab_data sudo docker run --name gitlab_data gitlab_image /bin/true sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb -sudo docker run --detach --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image +sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current -sudo docker run -ti --rm --volumes-from gitlab_data ubuntu /bin/sh +sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers + +sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb ``` # Interactively @@ -35,21 +37,27 @@ sudo docker run -ti --rm --volumes-from gitlab_data ubuntu /bin/sh # - we run interactively (-t -i) # - we define TERM=linux because it allows to use arrow keys in vi (!!!) # - we choose another startup command (bash) -sudo docker run -ti -e TERM=linux --name gitlab --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash +sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash # Configure GitLab to redirect PostgreSQL logs echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb # Prevent Postgres from allocating 25% of total memory -echo "postgresql['shared_buffers'] = '100MB'" >> /etc/gitlab/gitlab.rb +echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb # You can now start GitLab manually from Bash (in the background) # Maybe the command below is still missing something to run in the background gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start & +# Inspect PostgreSQL config +cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers + # And tail the logs (PostgreSQL log may not exist immediately) tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current # And get the memory cat /proc/meminfo +head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall +free -m + ``` From 3838b168b33163d4cbe64b7ff6e6b408bc8d857f Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Wed, 3 Dec 2014 12:43:26 +0100 Subject: [PATCH 364/408] Add password hint. --- docker/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index a2a194bd42c..a489203d017 100644 --- a/docker/README.md +++ b/docker/README.md @@ -41,7 +41,9 @@ sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 - It might take a while before the docker container is responding to queries. -You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker). Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`. +You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker). +You can login with username `root` and password `5iveL!fe`. +Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`. How to configure GitLab From c0a0d46c97d5d1c4fae54b01af98520749959592 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Wed, 3 Dec 2014 12:51:17 +0100 Subject: [PATCH 365/408] Add docker container to changelog. --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ba3cdfa0e52..ae8de1df277 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,7 +17,7 @@ v 7.6.0 - Change maximum avatar file size from 100KB to 200KB - - - - + - In the docker directory is a container template based on the Omnibus packages. - - From 2a0ee91f7ca3008d6bbdd10a7351485d1fa0468f Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Wed, 3 Dec 2014 14:07:18 +0100 Subject: [PATCH 366/408] Remove docker file maintainer at his request. https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245#note_647506 --- docker/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3ffedd16e81..d0b5338773e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,4 @@ FROM ubuntu:14.04 -MAINTAINER Vincent Robert # Install required packages RUN apt-get update -q \ From 106de470c95267dbfef7078839477eb844a11689 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Dec 2014 14:21:00 +0100 Subject: [PATCH 367/408] Use clickable checkboxes in issue template --- doc/release/monthly.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 64a8bc98344..4c1dd5af46b 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -20,36 +20,36 @@ Replace the dates with actual dates based on the number of workdays before the r ``` Xth: -* Update the changelog (#LINK) -* Triage the omnibus-gitlab milestone +- [ ] Update the changelog (#LINK) +- [ ] Triage the omnibus-gitlab milestone Xth: -* Merge CE in to EE (#LINK) -* Close the omnibus-gitlab milestone +- [ ] Merge CE in to EE (#LINK) +- [ ] Close the omnibus-gitlab milestone Xth: -* Create x.x.0.rc1 (#LINK) -* Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) +- [ ] Create x.x.0.rc1 (#LINK) +- [ ] Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) Xth: -* Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) -* Regression issue and tweet about rc1 (#LINK) -* Start blog post (#LINK) +- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) +- [ ] Regression issue and tweet about rc1 (#LINK) +- [ ] Start blog post (#LINK) Xth: -* Do QA and fix anything coming out of it (#LINK) +- [ ] Do QA and fix anything coming out of it (#LINK) 22nd: -* Release CE and EE (#LINK) +- [ ] Release CE and EE (#LINK) Xth: -* * Deploy to GitLab.com (#LINK) +- [ ] Deploy to GitLab.com (#LINK) ``` From 4ce27042f9db09f80cca071a5d0571e7205441f3 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Dec 2014 14:41:39 +0100 Subject: [PATCH 368/408] The second gitlab.com deploy should be easy --- doc/release/monthly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 4c1dd5af46b..383064b5e6b 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -288,7 +288,7 @@ Proposed tweet for CE "GitLab X.X is released! It brings *** " # **1 workday after release - Update GitLab.com** - Build a package for gitlab.com based on the official release instead of RC1 -- Deploy the package +- Deploy the package (should not need downtime because of the small difference with RC1) # **25th - Release GitLab CI** From a8ce1d88b5f9d85dc78267363bbe5de7f81b1807 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Dec 2014 15:40:12 +0100 Subject: [PATCH 369/408] Release CI at the same time as CE and EE --- doc/release/monthly.md | 50 ++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 383064b5e6b..9b05fea8c8a 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -20,7 +20,9 @@ Replace the dates with actual dates based on the number of workdays before the r ``` Xth: -- [ ] Update the changelog (#LINK) +- [ ] Update the CE changelog (#LINK) +- [ ] Update the EE changelog (#LINK) +- [ ] Update the CI changelog (#LINK) - [ ] Triage the omnibus-gitlab milestone Xth: @@ -31,12 +33,14 @@ Xth: Xth: - [ ] Create x.x.0.rc1 (#LINK) +- [ ] Create x.x.0-ee.rc1 (#LINK) +- [ ] Create CI y.y.0.rc1 (#LINK) - [ ] Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) Xth: - [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) -- [ ] Regression issue and tweet about rc1 (#LINK) +- [ ] Regression issues (CE, CI) and tweet about rc1 (#LINK) - [ ] Start blog post (#LINK) Xth: @@ -45,7 +49,7 @@ Xth: 22nd: -- [ ] Release CE and EE (#LINK) +- [ ] Release CE, EE and CI (#LINK) Xth: @@ -57,6 +61,8 @@ Xth: Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing. +There are three changelogs that need to be updated: CE, EE and CI. + ### **5. Take weekend and vacations into account** Ensure that there is enough time to incorporate the findings of the release candidate, etc. @@ -81,6 +87,7 @@ The RC1 release comes with the task to update the installation and upgrade docs. 1. Create: CE update guide from previous version. Like `7.3-to-7.4.md` 1. Create: CE to EE update guide in EE repository for latest version. 1. Update: `6.x-or-7.x-to-7.x.md` to latest version. +1. Create: CI update guide from previous version It's best to copy paste the previous guide and make changes where necessary. The typical steps are listed below with any points you should specifically look at. @@ -173,6 +180,24 @@ Now developers can use master for merging new features. So you should use stable branch for future code chages related to release. +### 5. Release GitLab CI RC1 + +Add to your local `gitlab-ci/.git/config`: + +``` +[remote "public"] + url = none + pushurl = git@dev.gitlab.org:gitlab/gitlab-ci.git + pushurl = git@gitlab.com:gitlab-org/gitlab-ci.git + pushurl = git@github.com:gitlabhq/gitlab-ci.git +``` + +* Create a stable branch `x-y-stable` +* Bump VERSION to `x.y.0.rc1` +* `git tag -a v$(cat VERSION) -m "Version $(cat VERSION)" +* `git push public x-y-stable v$(cat VERSION)` + + # **4 workdays before release - Release RC1** ### **1. Determine QA person @@ -191,6 +216,7 @@ It is important to do this as soon as possible, so we can catch any errors befor - Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. - Check the changelog of CE and EE for important changes. +- Also check the CI changelog - Create a WIP MR for the blog post - Ask Dmitriy to add screenshots to the WIP MR. - Decide with team who will be the MVP user. @@ -258,6 +284,11 @@ Bump version, create release tag and push to remotes: bundle exec rake release["x.x.0"] ``` +Also perform these steps for GitLab CI: + +- bump version in the stable branch +- create annotated tag +- push the stable branch and the annotated tag to the public repositories ### **2. Update installation.md** @@ -289,16 +320,3 @@ Proposed tweet for CE "GitLab X.X is released! It brings *** " - Build a package for gitlab.com based on the official release instead of RC1 - Deploy the package (should not need downtime because of the small difference with RC1) - -# **25th - Release GitLab CI** - -- Create the update guid `doc/x.x-to-x.x.md`. -- Update CHANGELOG -- Bump version -- Create annotated tags `git tag -a vx.x.0 -m 'Version x.x.0' xxxxx` -- Create stable branch `x-x-stable` -- Create GitHub release post -- Post to blog about release -- Post to twitter - - From 279952bb788b8e1601aafc66306d173956165740 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Dec 2014 17:29:52 +0200 Subject: [PATCH 370/408] Show issue/mr id in the list below title Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/issues/_issue.html.haml | 2 +- app/views/projects/merge_requests/_merge_request.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 85a3d2b6c01..dc6510be858 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -4,7 +4,6 @@ = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) .issue-title - %span.light= "##{issue.iid}" %span.str-truncated = link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title" - if issue.closed? @@ -12,6 +11,7 @@ CLOSED .issue-info + %span.light= "##{issue.iid}" - if issue.assignee assigned to #{link_to_member(@project, issue.assignee)} - if issue.votes_count > 0 diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 0a719fc642e..dedb060a231 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,6 +1,5 @@ %li{ class: mr_css_classes(merge_request) } .merge-request-title - %span.light= "##{merge_request.iid}" = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title" - if merge_request.merged? %small.pull-right @@ -15,6 +14,7 @@ %i.fa.fa-angle-right.light = merge_request.target_branch .merge-request-info + %span.light= "##{merge_request.iid}" - if merge_request.author authored by #{link_to_member(merge_request.source_project, merge_request.author)} - if merge_request.votes_count > 0 From cdabe7302571a21fd377505f144c053b59adb738 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Dec 2014 16:30:57 +0100 Subject: [PATCH 371/408] Fix EE RC1 tag name --- doc/release/monthly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 9b05fea8c8a..a95ba2e107b 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -33,7 +33,7 @@ Xth: Xth: - [ ] Create x.x.0.rc1 (#LINK) -- [ ] Create x.x.0-ee.rc1 (#LINK) +- [ ] Create x.x.0.rc1-ee (#LINK) - [ ] Create CI y.y.0.rc1 (#LINK) - [ ] Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package) From 1aca3718807019315fa2e31c1da58183a9e25f5e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Dec 2014 16:50:03 +0100 Subject: [PATCH 372/408] Add changes suggested by Sytse --- doc/release/monthly.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index a95ba2e107b..0700f24ab76 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -217,6 +217,7 @@ It is important to do this as soon as possible, so we can catch any errors befor - Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. - Check the changelog of CE and EE for important changes. - Also check the CI changelog +- Add a proposed tweet text to the blog post WIP MR description. - Create a WIP MR for the blog post - Ask Dmitriy to add screenshots to the WIP MR. - Decide with team who will be the MVP user. @@ -264,7 +265,7 @@ Create an issue with description of a problem, if it is quick fix fix it yoursel **NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted, create an issue about it in order to discuss the next steps after the release. -# **22nd - Release CE and EE** +# **22nd - Release CE, EE and CI** **Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`** From 372cb87f05e73dadb1304a9b8412e32624258e5b Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 4 Dec 2014 09:47:27 +0100 Subject: [PATCH 373/408] Reword the 512 memmory advise. --- doc/install/requirements.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 2b60c3560a5..660c1adb802 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -50,12 +50,12 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ### Memory -- 512MB is the absolute minimum but we do not recommend this amount of memory. -You will need to configure minimum 1.5GB of swap space. -With 1.5GB of swap space you must configure only one unicorn worker. -With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow. -Consider installing GitLab on Ubuntu as installation on CentOS could be unsuccessful with this amount of memory. +- 512MB is the absolute minimum but we strongly **advise against** this amount of memory. +You will need to configure a minimum of 1.5GB of swap space to make the Omnibus package reconfigure run succeed. +If you use a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker. +With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). +If you use a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow. +Consider installing GitLab on Ubuntu instead of CentOS because sometimes CentOS gives errors during installation and usage with this amount of memory. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users From d7aff11876f517d9e9cb4eb2237ceed4211d7013 Mon Sep 17 00:00:00 2001 From: Vincent Robert Date: Thu, 4 Dec 2014 10:30:10 +0100 Subject: [PATCH 374/408] Move to 7.5.2 --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d0b5338773e..93c564fc03b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -9,7 +9,7 @@ RUN apt-get update -q \ # Download & Install GitLab RUN TMP_FILE=$(mktemp); \ - wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.1-omnibus.5.2.0.ci-1_amd64.deb \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.2-omnibus.5.2.1.ci-1_amd64.deb \ && dpkg -i $TMP_FILE \ && rm -f $TMP_FILE From 58b58fe44b0924ce24f2f3e5d63b0f99fcf22f9f Mon Sep 17 00:00:00 2001 From: Vincent Robert Date: Thu, 4 Dec 2014 10:38:04 +0100 Subject: [PATCH 375/408] gitlab-ctl can now be followed with docker logs --- docker/Dockerfile | 2 +- docker/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 93c564fc03b..e9b7883e983 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -29,4 +29,4 @@ VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"] ADD gitlab.rb /etc/gitlab/ # Default is to run runit & reconfigure -CMD gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start +CMD gitlab-ctl reconfigure & /opt/gitlab/embedded/bin/runsvdir-start diff --git a/docker/README.md b/docker/README.md index a489203d017..e66278632f7 100644 --- a/docker/README.md +++ b/docker/README.md @@ -39,7 +39,7 @@ After creating this run GitLab: sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image ``` -It might take a while before the docker container is responding to queries. +It might take a while before the docker container is responding to queries. You can follow the configuration process with `docker logs -f gitlab`. You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker). You can login with username `root` and password `5iveL!fe`. From 176105eca628b297fbfc20b29146f2a8d5ddd74d Mon Sep 17 00:00:00 2001 From: Vincent Robert Date: Thu, 4 Dec 2014 10:38:35 +0100 Subject: [PATCH 376/408] Reword configuration to recommend an interactive command line --- docker/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index e66278632f7..1fbf703e25c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -51,14 +51,18 @@ How to configure GitLab This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. -To access GitLab configuration, you can start a new container using the shared data volume container: +To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor: - docker run -ti --rm --volumes-from gitlab_data ubuntu vi /etc/gitlab/gitlab.rb +```bash +docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu +vi /etc/gitlab/gitlab.rb +``` **Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab. You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). + Troubleshooting ========================= Please see the [troubleshooting](troubleshooting.md) file in this directory. From 14a1c1b4e6393dab2bd4c691a7241810980c0623 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 4 Dec 2014 11:03:40 +0100 Subject: [PATCH 377/408] Add some comments about updating the Omnibus package download location for the docker image. --- docker/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index d0b5338773e..7d538cc5e93 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,6 +8,8 @@ RUN apt-get update -q \ && apt-get clean # Download & Install GitLab +# If the Omnibus package version below is outdates please contribute a merge request to update it. +# If you run GitLab Enterprise Edition point it to a location where you have downloaded it. RUN TMP_FILE=$(mktemp); \ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.1-omnibus.5.2.0.ci-1_amd64.deb \ && dpkg -i $TMP_FILE \ From d80a59c7b1d3459579c3da95b64a5f235021aa59 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Dec 2014 12:10:38 +0200 Subject: [PATCH 378/408] Create helper for sort drowdown option names Signed-off-by: Dmitriy Zaporozhets --- app/helpers/sorting_helper.rb | 17 +++++++++++++++++ app/views/admin/projects/index.html.haml | 8 ++++---- app/views/admin/users/index.html.haml | 5 +++-- app/views/dashboard/projects.html.haml | 9 +++++---- app/views/explore/groups/index.html.haml | 8 ++++---- app/views/explore/projects/index.html.haml | 8 ++++---- app/views/projects/branches/index.html.haml | 4 ++-- app/views/shared/_sort_dropdown.html.haml | 8 ++++---- 8 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 app/helpers/sorting_helper.rb diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb new file mode 100644 index 00000000000..59e58e2f3d3 --- /dev/null +++ b/app/helpers/sorting_helper.rb @@ -0,0 +1,17 @@ +module SortingHelper + def sort_title_oldest_updated + 'Oldest updated' + end + + def sort_title_recently_updated + 'Recently updated' + end + + def sort_title_oldest_created + 'Recently updated' + end + + def sort_title_recently_created + 'Recently updated' + end +end diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 2cd6b12be7f..aa59f38d213 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -56,13 +56,13 @@ = link_to admin_projects_path(sort: nil) do Name = link_to admin_projects_path(sort: 'newest') do - Newest + = sort_title_recently_created = link_to admin_projects_path(sort: 'oldest') do - Oldest + = sort_title_oldest_created = link_to admin_projects_path(sort: 'recently_updated') do - Recently updated + = sort_title_recently_updated = link_to admin_projects_path(sort: 'last_updated') do - Last updated + = sort_title_oldest_updated = link_to admin_projects_path(sort: 'largest_repository') do Largest repository = link_to 'New Project', new_project_path, class: "btn btn-new" diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 92c619738a2..8e1ecb41a85 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -49,9 +49,10 @@ = link_to admin_users_path(sort: 'oldest_sign_in') do Oldest sign in = link_to admin_users_path(sort: 'recently_created') do - Recently created + = sort_title_recently_created = link_to admin_users_path(sort: 'late_created') do - Late created + = sort_title_oldest_created + = link_to 'New User', new_admin_user_path, class: "btn btn-new" %ul.well-list - @users.each do |user| diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index f124c688be1..5b7835b097b 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -14,13 +14,14 @@ = link_to projects_dashboard_filter_path(sort: nil) do Name = link_to projects_dashboard_filter_path(sort: 'newest') do - Newest + = sort_title_recently_created = link_to projects_dashboard_filter_path(sort: 'oldest') do - Oldest + = sort_title_oldest_created = link_to projects_dashboard_filter_path(sort: 'recently_updated') do - Recently updated + = sort_title_recently_updated = link_to projects_dashboard_filter_path(sort: 'last_updated') do - Last updated + = sort_title_oldest_updated + %p.light All projects you have access to are listed here. Public projects are not included here unless you are a member %hr diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 709d062df83..9b1d7d0416d 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -20,13 +20,13 @@ = link_to explore_groups_path(sort: nil) do Name = link_to explore_groups_path(sort: 'newest') do - Newest + = sort_title_recently_created = link_to explore_groups_path(sort: 'oldest') do - Oldest + = sort_title_oldest_created = link_to explore_groups_path(sort: 'recently_updated') do - Recently updated + = sort_title_recently_updated = link_to explore_groups_path(sort: 'last_updated') do - Last updated + = sort_title_oldest_updated %hr diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index f797c4e3830..02586077d8c 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -20,13 +20,13 @@ = link_to explore_projects_path(sort: nil) do Name = link_to explore_projects_path(sort: 'newest') do - Newest + = sort_title_recently_created = link_to explore_projects_path(sort: 'oldest') do - Oldest + = sort_title_oldest_created = link_to explore_projects_path(sort: 'recently_updated') do - Recently updated + = sort_title_recently_updated = link_to explore_projects_path(sort: 'last_updated') do - Last updated + = sort_title_oldest_updated %hr .public-projects diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 9f2b1b59292..d2aefd815a1 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -20,9 +20,9 @@ = link_to project_branches_path(sort: nil) do Name = link_to project_branches_path(sort: 'recently_updated') do - Recently updated + = sort_title_recently_updated = link_to project_branches_path(sort: 'last_updated') do - Last updated + = sort_title_oldest_updated %hr - unless @branches.empty? %ul.bordered-list.top-list.all-branches diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 7b37b39780e..54f59245690 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -9,13 +9,13 @@ %ul.dropdown-menu %li = link_to project_filter_path(sort: 'newest') do - Newest + = sort_title_recently_created = link_to project_filter_path(sort: 'oldest') do - Oldest + = sort_title_oldest_created = link_to project_filter_path(sort: 'recently_updated') do - Recently updated + = sort_title_recently_updated = link_to project_filter_path(sort: 'last_updated') do - Last updated + = sort_title_oldest_updated = link_to project_filter_path(sort: 'milestone_due_soon') do Milestone due soon = link_to project_filter_path(sort: 'milestone_due_later') do From 6670c99487891afd4a577d0a9ed54fe455bb0324 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Dec 2014 12:12:51 +0200 Subject: [PATCH 379/408] Set proper filter words Signed-off-by: Dmitriy Zaporozhets --- app/helpers/sorting_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 59e58e2f3d3..492e065b713 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -8,10 +8,10 @@ module SortingHelper end def sort_title_oldest_created - 'Recently updated' + 'Oldest created' end def sort_title_recently_created - 'Recently updated' + 'Recently created' end end From 1a80d13a3990937580c97e2b0ba8fb98f69bc055 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 25 Nov 2014 18:15:30 +0200 Subject: [PATCH 380/408] Multi-provider auth. LDAP is not reworked --- .../omniauth_callbacks_controller.rb | 7 +- app/helpers/profile_helper.rb | 2 +- app/models/identity.rb | 7 ++ app/models/user.rb | 3 +- .../20141121161704_add_identity_table.rb | 21 ++++ db/schema.rb | 107 +++++++++++++----- lib/gitlab/ldap/user.rb | 5 +- lib/gitlab/oauth/user.rb | 21 ++-- 8 files changed, 123 insertions(+), 50 deletions(-) create mode 100644 app/models/identity.rb create mode 100644 db/migrate/20141121161704_add_identity_table.rb diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index bd4b310fcbf..58d0506c07d 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -42,10 +42,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def handle_omniauth if current_user - # Change a logged-in user's authentication method: - current_user.extern_uid = oauth['uid'] - current_user.provider = oauth['provider'] - current_user.save + # Add new authentication method + current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider']) redirect_to profile_path else @user = Gitlab::OAuth::User.new(oauth) @@ -53,6 +51,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Only allow properly saved users to login. if @user.persisted? && @user.valid? + # binding.pry sign_in_and_redirect(@user.gl_user) else error_message = diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb index 0b375558305..816074e0247 100644 --- a/app/helpers/profile_helper.rb +++ b/app/helpers/profile_helper.rb @@ -1,6 +1,6 @@ module ProfileHelper def oauth_active_class(provider) - if current_user.provider == provider.to_s + if current_user.identities.exists?(provider: provider.to_s) 'active' end end diff --git a/app/models/identity.rb b/app/models/identity.rb new file mode 100644 index 00000000000..e6af93bcc50 --- /dev/null +++ b/app/models/identity.rb @@ -0,0 +1,7 @@ +class Identity < ActiveRecord::Base + belongs_to :user + + validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} + + scope :ldap, -> { where('provider LIKE ?', 'ldap%') } +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 1cddd85ada0..0cf0946593c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -79,6 +79,7 @@ class User < ActiveRecord::Base # Profile has_many :keys, dependent: :destroy has_many :emails, dependent: :destroy + has_many :identities, dependent: :destroy # Groups has_many :members, dependent: :destroy @@ -113,7 +114,6 @@ class User < ActiveRecord::Base validates :name, presence: true validates :email, presence: true, email: {strict_mode: true}, uniqueness: true validates :bio, length: { maximum: 255 }, allow_blank: true - validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} validates :username, presence: true, uniqueness: { case_sensitive: false }, exclusion: { in: Gitlab::Blacklist.path }, @@ -178,7 +178,6 @@ class User < ActiveRecord::Base scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } - scope :ldap, -> { where('provider LIKE ?', 'ldap%') } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } # diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb new file mode 100644 index 00000000000..7d019c65ee1 --- /dev/null +++ b/db/migrate/20141121161704_add_identity_table.rb @@ -0,0 +1,21 @@ +class AddIdentityTable < ActiveRecord::Migration + def up + create_table :identities do |t| + t.string :extern_uid + t.string :provider + t.references :user + end + + add_index :identities, :user_id + + User.where("provider is not NULL").find_each do |user| + execute "INSERT INTO identities(provider, extern_uid, user_id) VALUES('#{user.provider}', '#{user.extern_uid}', '#{user.id}')" + end + + #TODO remove user's columns extern_uid and provider + end + + def down +#TODO + end +end diff --git a/db/schema.rb b/db/schema.rb index 68d1080b6ee..34f991e5cf2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,11 +11,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20141121133009) do +ActiveRecord::Schema.define(version: 20141121161704) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "appearances", force: true do |t| + t.string "title" + t.text "description" + t.string "logo" + t.integer "updated_by" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "broadcast_messages", force: true do |t| t.text "message", null: false t.datetime "starts_at" @@ -74,6 +83,29 @@ ActiveRecord::Schema.define(version: 20141121133009) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree + create_table "git_hooks", force: true do |t| + t.string "force_push_regex" + t.string "delete_branch_regex" + t.string "commit_message_regex" + t.boolean "deny_delete_tag" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "username_regex" + t.string "email_regex" + t.string "author_email_regex" + t.boolean "member_check", default: false, null: false + t.string "file_name_regex" + end + + create_table "identities", force: true do |t| + t.string "extern_uid" + t.string "provider" + t.integer "user_id" + end + + add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree + create_table "issues", force: true do |t| t.string "title" t.integer "assignee_id" @@ -130,6 +162,15 @@ ActiveRecord::Schema.define(version: 20141121133009) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree + create_table "ldap_group_links", force: true do |t| + t.string "cn", null: false + t.integer "group_access", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "provider" + end + create_table "members", force: true do |t| t.integer "access_level", null: false t.integer "source_id", null: false @@ -209,6 +250,8 @@ ActiveRecord::Schema.define(version: 20141121133009) do t.string "type" t.string "description", default: "", null: false t.string "avatar" + t.string "ldap_cn" + t.integer "ldap_access" end add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree @@ -240,6 +283,14 @@ ActiveRecord::Schema.define(version: 20141121133009) do add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree + create_table "project_group_links", force: true do |t| + t.integer "project_id", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "group_access", default: 30, null: false + end + create_table "projects", force: true do |t| t.string "name" t.string "path" @@ -247,21 +298,22 @@ ActiveRecord::Schema.define(version: 20141121133009) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.string "issues_tracker", default: "gitlab", null: false + t.string "issues_tracker", default: "gitlab", null: false t.string "issues_tracker_id" - t.boolean "snippets_enabled", default: true, null: false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.text "merge_requests_template" end add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree @@ -327,12 +379,12 @@ ActiveRecord::Schema.define(version: 20141121133009) do end create_table "users", force: true do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -340,24 +392,24 @@ ActiveRecord::Schema.define(version: 20141121133009) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "extern_uid" t.string "provider" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.string "avatar" @@ -365,9 +417,10 @@ ActiveRecord::Schema.define(version: 20141121133009) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false t.datetime "last_credential_check_at" + t.datetime "admin_email_unsubscribed_at" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 3176e9790a7..827a33b5217 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -12,9 +12,10 @@ module Gitlab class << self def find_by_uid_and_provider(uid, provider) # LDAP distinguished name is case-insensitive - ::User. + identity = ::Identity. where(provider: [provider, :ldap]). where('lower(extern_uid) = ?', uid.downcase).last + identity && identity.user end end @@ -34,7 +35,7 @@ module Gitlab end def find_by_email - model.find_by(email: auth_hash.email) + User.find_by(email: auth_hash.email) end def update_user_attributes diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 47f62153a50..7c1970eb8e5 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -27,11 +27,9 @@ module Gitlab def save unauthorized_to_create unless gl_user + gl_user.save! if needs_blocking? - gl_user.save! gl_user.block - else - gl_user.save! end log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" @@ -70,24 +68,23 @@ module Gitlab end def find_by_uid_and_provider - model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last + identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid) + identity && identity.user end def build_new_user - model.new(user_attributes).tap do |user| - user.skip_confirmation! - end + user = User.new(user_attributes) + user.skip_confirmation! + user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) end def user_attributes { - extern_uid: auth_hash.uid, - provider: auth_hash.provider, name: auth_hash.name, username: auth_hash.username, email: auth_hash.email, password: auth_hash.password, - password_confirmation: auth_hash.password, + password_confirmation: auth_hash.password } end @@ -95,10 +92,6 @@ module Gitlab Gitlab::AppLogger end - def model - ::User - end - def raise_unauthorized_to_create raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") end From 3a5ed5260b24051939575d1934ce9b8392cac09f Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 27 Nov 2014 13:34:39 +0200 Subject: [PATCH 381/408] Supporting for multiple omniauth provider for the same user --- .../omniauth_callbacks_controller.rb | 5 +- app/helpers/profile_helper.rb | 4 +- app/models/identity.rb | 2 - app/models/user.rb | 6 +- app/services/notification_service.rb | 2 +- app/views/admin/users/show.html.haml | 2 +- .../sessions/_oauth_providers.html.haml | 2 +- .../20141121161704_add_identity_table.rb | 17 ++- db/schema.rb | 102 +++++------------- features/steps/profile/profile.rb | 2 +- lib/api/entities.rb | 8 +- lib/api/users.rb | 12 ++- lib/gitlab/ldap/access.rb | 8 +- lib/gitlab/ldap/user.rb | 10 +- lib/gitlab/oauth/user.rb | 13 ++- spec/factories.rb | 22 +++- spec/lib/gitlab/ldap/user_spec.rb | 8 +- spec/lib/gitlab/oauth/user_spec.rb | 7 +- spec/models/user_spec.rb | 16 ++- spec/requests/api/users_spec.rb | 2 +- 20 files changed, 123 insertions(+), 127 deletions(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 58d0506c07d..3e984e5007a 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -51,7 +51,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Only allow properly saved users to login. if @user.persisted? && @user.valid? - # binding.pry sign_in_and_redirect(@user.gl_user) else error_message = @@ -66,8 +65,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return end end - rescue StandardError - flash[:notice] = "There's no such user!" + rescue ForbiddenAction => e + flash[:notice] = e.message redirect_to new_user_session_path end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb index 816074e0247..6480fd3886f 100644 --- a/app/helpers/profile_helper.rb +++ b/app/helpers/profile_helper.rb @@ -10,10 +10,10 @@ module ProfileHelper end def show_profile_social_tab? - enabled_social_providers.any? && !current_user.ldap_user? + enabled_social_providers.any? end def show_profile_remove_tab? - gitlab_config.signup_enabled && !current_user.ldap_user? + gitlab_config.signup_enabled end end diff --git a/app/models/identity.rb b/app/models/identity.rb index e6af93bcc50..5fb1850c30b 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -2,6 +2,4 @@ class Identity < ActiveRecord::Base belongs_to :user validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} - - scope :ldap, -> { where('provider LIKE ?', 'ldap%') } end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 0cf0946593c..7faeef1b5b0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -406,7 +406,11 @@ class User < ActiveRecord::Base end def ldap_user? - extern_uid && provider.start_with?('ldap') + identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) + end + + def ldap_identity + @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"]) end def accessible_deploy_keys diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 2b6217e2e29..d1aadd741e1 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -107,7 +107,7 @@ class NotificationService # Notify new user with email after creation def new_user(user, token = nil) # Don't email omniauth created users - mailer.new_user_email(user.id, token) unless user.extern_uid? + mailer.new_user_email(user.id, token) unless user.identities.any? end # Notify users on new note in system diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 211d77d5185..29717aedd80 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -95,7 +95,7 @@ %li %span.light LDAP uid: %strong - = @user.extern_uid + = @user.ldap_identity.extern_uid - if @user.created_by %li diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml index 15048a78063..d053c51d7ec 100644 --- a/app/views/devise/sessions/_oauth_providers.html.haml +++ b/app/views/devise/sessions/_oauth_providers.html.haml @@ -1,4 +1,4 @@ -- providers = (enabled_oauth_providers - [:ldap]) +- providers = enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')} - if providers.present? .bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'} %span Sign in with:   diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb index 7d019c65ee1..243958039af 100644 --- a/db/migrate/20141121161704_add_identity_table.rb +++ b/db/migrate/20141121161704_add_identity_table.rb @@ -8,14 +8,25 @@ class AddIdentityTable < ActiveRecord::Migration add_index :identities, :user_id - User.where("provider is not NULL").find_each do |user| + User.where("provider IS NOT NULL").find_each do |user| execute "INSERT INTO identities(provider, extern_uid, user_id) VALUES('#{user.provider}', '#{user.extern_uid}', '#{user.id}')" end - #TODO remove user's columns extern_uid and provider + remove_column :users, :extern_uid + remove_column :users, :provider end def down -#TODO + add_column :users, :extern_uid, :string + add_column :users, :provider, :string + + User.where("id IN(SELECT user_id FROM identities)").find_each do |user| + identity = user.identities.last + user.extern_uid = identity.extern_uid + user.provider = identity.provider + user.save + end + + drop_table :identities end end diff --git a/db/schema.rb b/db/schema.rb index 34f991e5cf2..ec211901e42 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -16,15 +16,6 @@ ActiveRecord::Schema.define(version: 20141121161704) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - create_table "appearances", force: true do |t| - t.string "title" - t.text "description" - t.string "logo" - t.integer "updated_by" - t.datetime "created_at" - t.datetime "updated_at" - end - create_table "broadcast_messages", force: true do |t| t.text "message", null: false t.datetime "starts_at" @@ -83,21 +74,6 @@ ActiveRecord::Schema.define(version: 20141121161704) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree - create_table "git_hooks", force: true do |t| - t.string "force_push_regex" - t.string "delete_branch_regex" - t.string "commit_message_regex" - t.boolean "deny_delete_tag" - t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.string "username_regex" - t.string "email_regex" - t.string "author_email_regex" - t.boolean "member_check", default: false, null: false - t.string "file_name_regex" - end - create_table "identities", force: true do |t| t.string "extern_uid" t.string "provider" @@ -162,15 +138,6 @@ ActiveRecord::Schema.define(version: 20141121161704) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree - create_table "ldap_group_links", force: true do |t| - t.string "cn", null: false - t.integer "group_access", null: false - t.integer "group_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.string "provider" - end - create_table "members", force: true do |t| t.integer "access_level", null: false t.integer "source_id", null: false @@ -250,8 +217,6 @@ ActiveRecord::Schema.define(version: 20141121161704) do t.string "type" t.string "description", default: "", null: false t.string "avatar" - t.string "ldap_cn" - t.integer "ldap_access" end add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree @@ -283,14 +248,6 @@ ActiveRecord::Schema.define(version: 20141121161704) do add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree - create_table "project_group_links", force: true do |t| - t.integer "project_id", null: false - t.integer "group_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "group_access", default: 30, null: false - end - create_table "projects", force: true do |t| t.string "name" t.string "path" @@ -298,22 +255,21 @@ ActiveRecord::Schema.define(version: 20141121161704) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.string "issues_tracker", default: "gitlab", null: false + t.string "issues_tracker", default: "gitlab", null: false t.string "issues_tracker_id" - t.boolean "snippets_enabled", default: true, null: false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.text "merge_requests_template" + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false end add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree @@ -379,12 +335,12 @@ ActiveRecord::Schema.define(version: 20141121161704) do end create_table "users", force: true do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -392,35 +348,32 @@ ActiveRecord::Schema.define(version: 20141121161704) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "extern_uid" - t.string "provider" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" + t.datetime "last_credential_check_at" t.string "avatar" t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.datetime "last_credential_check_at" - t.datetime "admin_email_unsubscribed_at" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree @@ -428,7 +381,6 @@ ActiveRecord::Schema.define(version: 20141121161704) do add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree - add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 6d747b65bae..38aaadcd28d 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -170,7 +170,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step "I am not an ldap user" do - current_user.update_attributes(extern_uid: nil, provider: '') + current_user.identities.delete current_user.ldap_user?.should be_false end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 42e4442365d..2fea151aeb3 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -14,10 +14,14 @@ module API expose :bio, :skype, :linkedin, :twitter, :website_url end + class Identity < Grape::Entity + expose :provider, :extern_uid + end + class UserFull < User expose :email - expose :theme_id, :color_scheme_id, :extern_uid, :provider, \ - :projects_limit + expose :theme_id, :color_scheme_id, :projects_limit + expose :identities, using: Entities::Identity expose :can_create_group?, as: :can_create_group expose :can_create_project?, as: :can_create_project end diff --git a/lib/api/users.rb b/lib/api/users.rb index d07815a8a97..37b36ddcf94 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -59,10 +59,16 @@ module API post do authenticated_as_admin! required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin] user = User.build_user(attrs) admin = attrs.delete(:admin) user.admin = admin unless admin.nil? + + identity_attrs = attributes_for_keys [:provider, :extern_uid] + if identity_attrs.any? + user.identities.build(identity_attrs) + end + if user.save present user, with: Entities::UserFull else @@ -89,8 +95,6 @@ module API # twitter - Twitter account # website_url - Website url # projects_limit - Limit projects each user can create - # extern_uid - External authentication provider UID - # provider - External provider # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false @@ -99,7 +103,7 @@ module API put ":id" do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin] user = User.find(params[:id]) not_found!('User') unless user diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index eb2c4e48ff2..0c85acf7e69 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -8,7 +8,7 @@ module Gitlab attr_reader :adapter, :provider, :user def self.open(user, &block) - Gitlab::LDAP::Adapter.open(user.provider) do |adapter| + Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter| block.call(self.new(user, adapter)) end end @@ -28,13 +28,13 @@ module Gitlab def initialize(user, adapter=nil) @adapter = adapter @user = user - @provider = user.provider + @provider = user.ldap_identity.provider end def allowed? - if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) + if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) return true unless ldap_config.active_directory - !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter) + !Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) else false end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 827a33b5217..3ef494ba137 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -35,15 +35,13 @@ module Gitlab end def find_by_email - User.find_by(email: auth_hash.email) + ::User.find_by(email: auth_hash.email) end def update_user_attributes - gl_user.attributes = { - extern_uid: auth_hash.uid, - provider: auth_hash.provider, - email: auth_hash.email - } + gl_user.email = auth_hash.email + gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid) + gl_user end def changed? diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 7c1970eb8e5..6861427864e 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -5,6 +5,8 @@ # module Gitlab module OAuth + class ForbiddenAction < StandardError; end + class User attr_accessor :auth_hash, :gl_user @@ -27,9 +29,11 @@ module Gitlab def save unauthorized_to_create unless gl_user - gl_user.save! if needs_blocking? + gl_user.save! gl_user.block + else + gl_user.save! end log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" @@ -73,9 +77,10 @@ module Gitlab end def build_new_user - user = User.new(user_attributes) + user = ::User.new(user_attributes) user.skip_confirmation! user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) + user end def user_attributes @@ -92,8 +97,8 @@ module Gitlab Gitlab::AppLogger end - def raise_unauthorized_to_create - raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") + def unauthorized_to_create + raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") end end end diff --git a/spec/factories.rb b/spec/factories.rb index 15899d8c3c4..58060131638 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -18,15 +18,24 @@ FactoryGirl.define do password "12345678" password_confirmation { password } confirmed_at { Time.now } - confirmation_token { nil } + confirmation_token { nil } trait :admin do admin true end - trait :ldap do - provider 'ldapmain' - extern_uid 'my-ldap-id' + factory :omniauth_user do + ignore do + extern_uid '123456' + provider 'ldapmain' + end + + after(:create) do |user, evaluator| + user.identities << create(:identity, + provider: evaluator.provider, + extern_uid: evaluator.extern_uid + ) + end end factory :admin, traits: [:admin] @@ -182,4 +191,9 @@ FactoryGirl.define do deploy_key project end + + factory :identity do + provider 'ldapmain' + extern_uid 'my-ldap-id' + end end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 726c9764e3d..294ee6cbae0 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -15,18 +15,18 @@ describe Gitlab::LDAP::User do describe :find_or_create do it "finds the user if already existing" do - existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain') + existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') expect{ gl_user.save }.to_not change{ User.count } end it "connects to existing non-ldap user if the email matches" do - existing_user = create(:user, email: 'john@example.com') + existing_user = create(:omniauth_user, email: 'john@example.com') expect{ gl_user.save }.to_not change{ User.count } existing_user.reload - expect(existing_user.extern_uid).to eql 'my-uid' - expect(existing_user.provider).to eql 'ldapmain' + expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.provider).to eql 'ldapmain' end it "creates a new user if not found" do diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb index 8a83a1b2588..88307515789 100644 --- a/spec/lib/gitlab/oauth/user_spec.rb +++ b/spec/lib/gitlab/oauth/user_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do end describe :persisted? do - let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') } + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } it "finds an existing user based on uid and provider (facebook)" do auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider') @@ -39,8 +39,9 @@ describe Gitlab::OAuth::User do oauth_user.save expect(gl_user).to be_valid - expect(gl_user.extern_uid).to eql uid - expect(gl_user.provider).to eql 'twitter' + identity = gl_user.identities.first + expect(identity.extern_uid).to eql uid + expect(identity.provider).to eql 'twitter' end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6d865cfc691..8be7f733a5b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -62,6 +62,7 @@ describe User do it { should have_many(:assigned_issues).dependent(:destroy) } it { should have_many(:merge_requests).dependent(:destroy) } it { should have_many(:assigned_merge_requests).dependent(:destroy) } + it { should have_many(:identities).dependent(:destroy) } end describe "Mass assignment" do @@ -361,24 +362,29 @@ describe User do end describe :ldap_user? do - let(:user) { build(:user, :ldap) } - it "is true if provider name starts with ldap" do - user.provider = 'ldapmain' + user = create(:omniauth_user, provider: 'ldapmain') expect( user.ldap_user? ).to be_true end it "is false for other providers" do - user.provider = 'other-provider' + user = create(:omniauth_user, provider: 'other-provider') expect( user.ldap_user? ).to be_false end it "is false if no extern_uid is provided" do - user.extern_uid = nil + user = create(:omniauth_user, extern_uid: nil) expect( user.ldap_user? ).to be_false end end + describe :ldap_identity do + it "returns ldap identity" do + user = create :omniauth_user + user.ldap_identity.provider.should_not be_empty + end + end + describe '#full_website_url' do let(:user) { create(:user) } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 113a39b870e..1ecc79ea7ef 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -33,7 +33,7 @@ describe API::API, api: true do response.status.should == 200 json_response.should be_an Array json_response.first.keys.should include 'email' - json_response.first.keys.should include 'extern_uid' + json_response.first.keys.should include 'identities' json_response.first.keys.should include 'can_create_project' end end From b56b96d438c92981cd5c0f5e2f4b23d3799a3bd3 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 4 Dec 2014 12:55:37 +0200 Subject: [PATCH 382/408] added helper --- app/helpers/oauth_helper.rb | 4 ++++ .../devise/sessions/_oauth_providers.html.haml | 2 +- spec/helpers/oauth_helper_spec.rb | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 spec/helpers/oauth_helper_spec.rb diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index 7024483b8b3..df18db71c84 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -16,4 +16,8 @@ module OauthHelper [:twitter, :github, :google_oauth2].include?(name.to_sym) end end + + def additional_providers + enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')} + end end diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml index d053c51d7ec..8d6aaefb9ff 100644 --- a/app/views/devise/sessions/_oauth_providers.html.haml +++ b/app/views/devise/sessions/_oauth_providers.html.haml @@ -1,4 +1,4 @@ -- providers = enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')} +- providers = additional_providers - if providers.present? .bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'} %span Sign in with:   diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb new file mode 100644 index 00000000000..846e65b54e9 --- /dev/null +++ b/spec/helpers/oauth_helper_spec.rb @@ -0,0 +1,17 @@ +require "spec_helper" + +describe OauthHelper do + describe "additional_providers" do + it 'returns appropriate values' do + [ + [[:twitter, :github], [:twitter, :github]], + [[:ldap_main], []], + [[:twitter, :ldap_main], [:twitter]], + [[], []], + ].each do |couple| + allow(helper).to receive(:enabled_oauth_providers) { couple.first } + additional_providers.should include(*couple.last) + end + end + end +end \ No newline at end of file From a3e9046ad522d4d234c9b4e644a1175f9ed8213d Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 4 Dec 2014 12:29:30 +0100 Subject: [PATCH 383/408] Fix spelling error in dockerfile, thanks Vincent for noting it. --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a7b44d823ea..aea59916c7a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update -q \ && apt-get clean # Download & Install GitLab -# If the Omnibus package version below is outdates please contribute a merge request to update it. +# If the Omnibus package version below is outdated please contribute a merge request to update it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it. RUN TMP_FILE=$(mktemp); \ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.2-omnibus.5.2.1.ci-1_amd64.deb \ From db2edff937cbc309c10bb1a987356a58f8a9c8fa Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Dec 2014 15:07:01 +0200 Subject: [PATCH 384/408] Handle web hook exception Write to log if web hook cant be executed. This prevents 500 error when test web hook with invalid URL and prevent exceptions and retries in sidekiq Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/hooks_controller.rb | 1 + app/models/hooks/web_hook.rb | 8 +++++++- app/services/test_hook_service.rb | 3 --- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index cab8fd76e6c..2d6c3111192 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController def test if !@project.empty_repo? status = TestHookService.new.execute(hook, current_user) + if status flash[:notice] = 'Hook successfully executed.' else diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 23fa01e0b70..8479d4aecf6 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -32,7 +32,10 @@ class WebHook < ActiveRecord::Base def execute(data) parsed_url = URI.parse(url) if parsed_url.userinfo.blank? - WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) + WebHook.post(url, + body: data.to_json, + headers: { "Content-Type" => "application/json" }, + verify: false) else post_url = url.gsub("#{parsed_url.userinfo}@", "") auth = { @@ -45,6 +48,9 @@ class WebHook < ActiveRecord::Base verify: false, basic_auth: auth) end + rescue SocketError, Errno::ECONNREFUSED => e + logger.error("WebHook Error => #{e}") + false end def async_execute(data) diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb index b6b1ef29b51..17d86a7a274 100644 --- a/app/services/test_hook_service.rb +++ b/app/services/test_hook_service.rb @@ -2,8 +2,5 @@ class TestHookService def execute(hook, current_user) data = GitPushService.new.sample_data(hook.project, current_user) hook.execute(data) - true - rescue SocketError - false end end From f9a730ebb48ffe21c7b80ed4d188e47ec1baa497 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 4 Dec 2014 13:43:08 +0200 Subject: [PATCH 385/408] fix specs --- spec/helpers/oauth_helper_spec.rb | 23 ++++++++++++--------- spec/lib/gitlab/ldap/access_spec.rb | 2 +- spec/lib/gitlab/ldap/authentication_spec.rb | 2 +- spec/lib/gitlab/ldap/user_spec.rb | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb index 846e65b54e9..453699136e9 100644 --- a/spec/helpers/oauth_helper_spec.rb +++ b/spec/helpers/oauth_helper_spec.rb @@ -2,16 +2,19 @@ require "spec_helper" describe OauthHelper do describe "additional_providers" do - it 'returns appropriate values' do - [ - [[:twitter, :github], [:twitter, :github]], - [[:ldap_main], []], - [[:twitter, :ldap_main], [:twitter]], - [[], []], - ].each do |couple| - allow(helper).to receive(:enabled_oauth_providers) { couple.first } - additional_providers.should include(*couple.last) - end + it 'returns all enabled providers' do + allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] } + helper.additional_providers.should include(*[:twitter, :github]) + end + + it 'does not return ldap provider' do + allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] } + helper.additional_providers.should include(:twitter) + end + + it 'returns empty array' do + allow(helper).to receive(:enabled_oauth_providers) { [] } + helper.additional_providers.should == [] end end end \ No newline at end of file diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index f4d5a927396..4573b8696c4 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::LDAP::Access do let(:access) { Gitlab::LDAP::Access.new user } - let(:user) { create(:user, :ldap) } + let(:user) { create(:omniauth_user) } describe :allowed? do subject { access.allowed? } diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb index 0eb7c443b8b..11fdf108756 100644 --- a/spec/lib/gitlab/ldap/authentication_spec.rb +++ b/spec/lib/gitlab/ldap/authentication_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::LDAP::Authentication do let(:klass) { Gitlab::LDAP::Authentication } - let(:user) { create(:user, :ldap, extern_uid: dn) } + let(:user) { create(:omniauth_user, extern_uid: dn) } let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } let(:login) { 'john' } let(:password) { 'password' } diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 294ee6cbae0..f73884e6441 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::LDAP::User do end it "connects to existing non-ldap user if the email matches" do - existing_user = create(:omniauth_user, email: 'john@example.com') + existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter") expect{ gl_user.save }.to_not change{ User.count } existing_user.reload From cdc62cffcb86dfd939c119cba2acaf266af39f23 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Thu, 4 Dec 2014 15:22:10 +0100 Subject: [PATCH 386/408] Add rake task for google schema whitelisting. --- app/mailers/notify.rb | 8 ++ doc/integration/gitlab_buttons_in_gmail.md | 17 +++++ .../mail_google_schema_whitelisting.rake | 73 +++++++++++++++++++ .../gitlab/mail_google_schema_whitelisting.rb | 27 +++++++ 4 files changed, 125 insertions(+) create mode 100644 lib/tasks/gitlab/mail_google_schema_whitelisting.rake create mode 100644 spec/tasks/gitlab/mail_google_schema_whitelisting.rb diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0ee19836627..6d671e6e0bd 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -26,6 +26,14 @@ class Notify < ActionMailer::Base delay_for(2.seconds) end + def test_email(recepient_email, subject, body) + mail(to: recepient_email, + subject: subject, + body: body.html_safe, + content_type: 'text/html' + ) + end + private # The default email address to send emails from diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md index 5cfea5a90f8..0816509c557 100644 --- a/doc/integration/gitlab_buttons_in_gmail.md +++ b/doc/integration/gitlab_buttons_in_gmail.md @@ -9,3 +9,20 @@ If correctly setup, emails that require an action will be marked in Gmail. To get this functioning, you need to be registered with Google. [See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google) +To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server. + +To check what would be sent to the google email address, run the rake task: + +```bash +bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production +``` + +**This will not send the email but give you the output of how the mail will look.** + +Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate". + +If you receive "No errors detected" message from the tester you can send the email using: + +```bash +bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true +`` diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake new file mode 100644 index 00000000000..f40bba24da8 --- /dev/null +++ b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake @@ -0,0 +1,73 @@ +require "#{Rails.root}/app/helpers/emails_helper" +require 'action_view/helpers' +extend ActionView::Helpers + +include ActionView::Context +include EmailsHelper + +namespace :gitlab do + desc "Email google whitelisting email with example email for actions in inbox" + task mail_google_schema_whitelisting: :environment do + subject = "Rails | Implemented feature" + url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}" + schema = email_action(url) + body = email_template(schema, url) + mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe) + if send_now + mail.deliver + else + puts "WOULD SEND:" + end + puts mail + end + + def email_template(schema, url) + " + + + + GitLab + + + + + +
    +
    +

    I like it :+1:

    +
    +
    + +
    + + " + end + + def send_now + if ENV['SEND'] == "true" + true + else + false + end + end +end diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb new file mode 100644 index 00000000000..45aaf0fc90b --- /dev/null +++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb @@ -0,0 +1,27 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:mail_google_schema_whitelisting rake task' do + before :all do + Rake.application.rake_require "tasks/gitlab/task_helpers" + Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting" + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + describe 'call' do + before do + # avoid writing task output to spec progress + $stdout.stub :write + end + + let :run_rake_task do + Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable + Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting" + end + + it 'should run the task without errors' do + expect { run_rake_task }.to_not raise_error + end + end +end From 704b7237e6c4daa3642c01f8803072fdc3a45eaf Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 4 Dec 2014 16:54:08 +0100 Subject: [PATCH 387/408] Fix notifications for developers that don't read the documentation. --- doc/development/rake_tasks.md | 5 ++++- lib/tasks/seed.rake | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 lib/tasks/seed.rake diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 6d9ac161e91..ffa61e66134 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -1,6 +1,6 @@ # Rake tasks for developers -## Setup db with developer seeds: +## Setup db with developer seeds Note that if your db user does not have advanced privileges you must create the db manually before running this command. @@ -8,6 +8,9 @@ Note that if your db user does not have advanced privileges you must create the bundle exec rake setup ``` +The `setup` task is a alias for `gitlab:setup`. +This tasks calls `db:setup` to create the database, with `add_limits_mysql` it adds limits to the database schema in case of a MySQL database and fianlly it runs `db:seed_fu` to seed the database. + ## Run tests This runs all test suites present in GitLab. diff --git a/lib/tasks/seed.rake b/lib/tasks/seed.rake new file mode 100644 index 00000000000..c54a1e694a5 --- /dev/null +++ b/lib/tasks/seed.rake @@ -0,0 +1,8 @@ +namespace :db do + namespace :seed do + desc "Seed is replaced with seed_fu" + task :dump => :environment do + raise "Please run db:seed_fu instead of db:seed." + end + end +end From 18164afbccf90074abf55e2bcdb91204b213ede1 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 28 Nov 2014 19:06:21 +0100 Subject: [PATCH 388/408] Update Sidekiq to 2.17.8 --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ae8de1df277..2c6808f46b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 7.6.0 - - - In the docker directory is a container template based on the Omnibus packages. + - Update Sidekiq to version 2.17.8 - - diff --git a/Gemfile b/Gemfile index 613ef11cf4d..b4ca5969277 100644 --- a/Gemfile +++ b/Gemfile @@ -112,7 +112,7 @@ gem "acts-as-taggable-on" # Background jobs gem 'slim' gem 'sinatra', require: nil -gem 'sidekiq', '2.17.0' +gem 'sidekiq', '2.17.8' # HTTP requests gem "httparty" diff --git a/Gemfile.lock b/Gemfile.lock index 7871f49d0bf..4bcb1eb0de5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,7 +78,7 @@ GEM coffee-script-source (1.6.3) colored (1.2) colorize (0.5.8) - connection_pool (1.2.0) + connection_pool (2.1.0) coveralls (0.7.0) multi_json (~> 1.3) rest-client @@ -402,7 +402,7 @@ GEM rdoc (3.12.2) json (~> 1.4) redcarpet (3.1.2) - redis (3.0.6) + redis (3.1.0) redis-actionpack (4.0.0) actionpack (~> 4) redis-rack (~> 1.5.0) @@ -410,8 +410,8 @@ GEM redis-activesupport (4.0.0) activesupport (~> 4) redis-store (~> 1.1.0) - redis-namespace (1.4.1) - redis (~> 3.0.4) + redis-namespace (1.5.1) + redis (~> 3.0, >= 3.0.4) redis-rack (1.5.0) rack (~> 1.5) redis-store (~> 1.1.0) @@ -470,12 +470,12 @@ GEM sexp_processor (4.4.0) shoulda-matchers (2.1.0) activesupport (>= 3.0.0) - sidekiq (2.17.0) - celluloid (>= 0.15.2) - connection_pool (>= 1.0.0) + sidekiq (2.17.8) + celluloid (= 0.15.2) + connection_pool (~> 2.0) json - redis (>= 3.0.4) - redis-namespace (>= 1.3.1) + redis (~> 3.1) + redis-namespace (~> 1.3) simple_oauth (0.1.9) simplecov (0.9.0) docile (~> 1.1.0) @@ -684,7 +684,7 @@ DEPENDENCIES semantic-ui-sass (~> 0.16.1.0) settingslogic shoulda-matchers (~> 2.1.0) - sidekiq (= 2.17.0) + sidekiq (= 2.17.8) simplecov sinatra six From 288df41c0a3b950f3474d9ae00c644d4780de321 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 4 Dec 2014 18:14:54 +0100 Subject: [PATCH 389/408] Seed is not seed dump. --- lib/tasks/seed.rake | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/tasks/seed.rake b/lib/tasks/seed.rake index c54a1e694a5..7c006c16639 100644 --- a/lib/tasks/seed.rake +++ b/lib/tasks/seed.rake @@ -1,8 +1,6 @@ namespace :db do - namespace :seed do - desc "Seed is replaced with seed_fu" - task :dump => :environment do - raise "Please run db:seed_fu instead of db:seed." - end + desc "Seed is replaced with seed_fu" + task :seed => :environment do + raise "Please run db:seed_fu instead of db:seed." end end From a46fe875c6aea206e575e2b083bd31ed36ee1b1e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Dec 2014 21:49:19 +0200 Subject: [PATCH 390/408] Feature: atom feed for user activity Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 +- app/controllers/users_controller.rb | 7 ++++- app/views/users/show.atom.builder | 29 +++++++++++++++++++ app/views/users/show.html.haml | 10 ++++++- config/routes.rb | 3 +- spec/features/atom/users_spec.rb | 43 +++++++++++++++++++++++++++++ 6 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 app/views/users/show.atom.builder create mode 100644 spec/features/atom/users_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 2c6808f46b9..6c28a573703 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,7 +20,7 @@ v 7.6.0 - In the docker directory is a container template based on the Omnibus packages. - Update Sidekiq to version 2.17.8 - - - + - Atom feed for user activity v 7.5.2 - Don't log Sidekiq arguments by default diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0b442f5383a..67af1801bda 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -20,9 +20,14 @@ class UsersController < ApplicationController # Get user activity feed for projects common for both users @events = @user.recent_events. - where(project_id: authorized_projects_ids).limit(20) + where(project_id: authorized_projects_ids).limit(30) @title = @user.name + + respond_to do |format| + format.html + format.atom { render layout: false } + end end def determine_layout diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder new file mode 100644 index 00000000000..0d61a9e8097 --- /dev/null +++ b/app/views/users/show.atom.builder @@ -0,0 +1,29 @@ +xml.instruct! +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do + xml.title "Activity feed for #{@user.name}" + xml.link :href => user_url(@user, :atom), :rel => "self", :type => "application/atom+xml" + xml.link :href => user_url(@user), :rel => "alternate", :type => "text/html" + xml.id projects_url + xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + + @events.each do |event| + if event.proper? + xml.entry do + event_link = event_feed_url(event) + event_title = event_feed_title(event) + event_summary = event_feed_summary(event) + + xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" + xml.link :href => event_link + xml.title truncate(event_title, :length => 80) + xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) + xml.author do |author| + xml.name event.author_name + xml.email event.author_email + end + xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? } + end + end + end +end diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index cb49c030af2..54f2666ce5d 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -18,7 +18,15 @@ %h4 Groups: = render 'groups', groups: @groups %hr - %h4 User Activity: + %h4 + User Activity: + + - if current_user + %span.rss-icon.pull-right + = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do + %strong + %i.fa.fa-rss + = render @events .col-md-4 = render 'profile', user: @user diff --git a/config/routes.rb b/config/routes.rb index 723104daf13..f2984069b71 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -137,7 +137,8 @@ Gitlab::Application.routes.draw do end end - match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get + match "/u/:username" => "users#show", as: :user, + constraints: {username: /(?:[^.]|\.(?!atom$))+/, format: /atom/}, via: :get # # Dashboard Area diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb new file mode 100644 index 00000000000..746b6fc1ac9 --- /dev/null +++ b/spec/features/atom/users_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe "User Feed", feature: true do + describe "GET /" do + let!(:user) { create(:user) } + + context "user atom feed via private token" do + it "should render user atom feed" do + visit user_path(user, :atom, private_token: user.private_token) + body.should have_selector("feed title") + end + end + + context 'feed content' do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project, author: user, description: '') } + let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) } + + before do + project.team << [user, :master] + issue_event(issue, user) + note_event(note, user) + visit user_path(user, :atom, private_token: user.private_token) + end + + it "should have issue opened event" do + body.should have_content("#{user.name} opened issue ##{issue.iid}") + end + + it "should have issue comment event" do + body.should have_content("#{user.name} commented on issue ##{issue.iid}") + end + end + end + + def issue_event(issue, user) + EventCreateService.new.open_issue(issue, user) + end + + def note_event(note, user) + EventCreateService.new.leave_note(note, user) + end +end From d31d711a70779132ea43387f93eb4ccc1e472761 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Dec 2014 22:14:20 +0200 Subject: [PATCH 391/408] DRY and refactor atom code Signed-off-by: Dmitriy Zaporozhets --- app/helpers/events_helper.rb | 22 +++++++++++++++++ app/helpers/issues_helper.rb | 15 ++++++++++++ app/views/dashboard/issues.atom.builder | 21 ++++------------ app/views/dashboard/show.atom.builder | 25 ++++---------------- app/views/groups/issues.atom.builder | 13 +--------- app/views/groups/show.atom.builder | 24 ++++--------------- app/views/projects/issues/index.atom.builder | 13 +--------- app/views/users/show.atom.builder | 25 ++++---------------- 8 files changed, 56 insertions(+), 102 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 71f97fbb8c8..a3136926b38 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -145,4 +145,26 @@ module EventsHelper rescue "--broken encoding" end + + def event_to_atom(xml, event) + if event.proper? + xml.entry do + event_link = event_feed_url(event) + event_title = event_feed_title(event) + event_summary = event_feed_summary(event) + + xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" + xml.link href: event_link + xml.title truncate(event_title, length: 80) + xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") + xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email) + xml.author do |author| + xml.name event.author_name + xml.email event.author_email + end + + xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? } + end + end + end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index d513e0ba58e..a5b393c1e3c 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -113,4 +113,19 @@ module IssuesHelper 'issue-box-open' end end + + def issue_to_atom(xml, issue) + xml.entry do + xml.id project_issue_url(issue.project, issue) + xml.link href: project_issue_url(issue.project, issue) + xml.title truncate(issue.title, length: 80) + xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") + xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email) + xml.author do |author| + xml.name issue.author_name + xml.email issue.author_email + end + xml.summary issue.title + end + end end diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index f5413557783..66381310221 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,24 +1,13 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.title "#{current_user.name} issues" - xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" - xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" - xml.id issues_dashboard_url(:private_token => current_user.private_token) + xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html" + xml.id issues_dashboard_url(private_token: current_user.private_token) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(issue.project, issue) - xml.link :href => project_issue_url(issue.project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) - xml.author do |author| - xml.name issue.author_name - xml.email issue.author_email - end - xml.summary issue.title - end + issue_to_atom(xml, issue) end end diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index f4cf24ccd99..70ac66f8016 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -1,29 +1,12 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" - xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html" + xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml" + xml.link href: dashboard_url, rel: "alternate", type: "text/html" xml.id projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.proper? - xml.entry do - event_link = event_feed_url(event) - event_title = event_feed_title(event) - event_summary = event_feed_summary(event) - - xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" - xml.link :href => event_link - xml.title truncate(event_title, :length => 80) - xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) - xml.author do |author| - xml.name event.author_name - xml.email event.author_email - end - xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? } - end - end + event_to_atom(xml, event) end end diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder index f2005193f83..240001967f3 100644 --- a/app/views/groups/issues.atom.builder +++ b/app/views/groups/issues.atom.builder @@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(issue.project, issue) - xml.link :href => project_issue_url(issue.project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) - xml.author do |author| - xml.name issue.author_name - xml.email issue.author_email - end - xml.summary issue.title - end + issue_to_atom(xml, issue) end end diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index e07bb7d2fb7..e765ea8338d 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -1,28 +1,12 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.title "Group feed - #{@group.name}" - xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html" + xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml" + xml.link href: group_path(@group), rel: "alternate", type: "text/html" xml.id projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.proper? - xml.entry do - event_link = event_feed_url(event) - event_title = event_feed_title(event) - - xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" - xml.link :href => event_link - xml.title truncate(event_title, :length => 80) - xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) - xml.author do |author| - xml.name event.author_name - xml.email event.author_email - end - xml.summary event_title - end - end + event_to_atom(xml, event) end end diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 012ba235951..61e651da932 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -7,17 +7,6 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(@project, issue) - xml.link :href => project_issue_url(@project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) - xml.author do |author| - xml.name issue.author_name - xml.email issue.author_email - end - xml.summary issue.title - end + issue_to_atom(xml, issue) end end diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder index 0d61a9e8097..b7216a88765 100644 --- a/app/views/users/show.atom.builder +++ b/app/views/users/show.atom.builder @@ -1,29 +1,12 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.title "Activity feed for #{@user.name}" - xml.link :href => user_url(@user, :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => user_url(@user), :rel => "alternate", :type => "text/html" + xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" + xml.link href: user_url(@user), rel: "alternate", type: "text/html" xml.id projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.proper? - xml.entry do - event_link = event_feed_url(event) - event_title = event_feed_title(event) - event_summary = event_feed_summary(event) - - xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" - xml.link :href => event_link - xml.title truncate(event_title, :length => 80) - xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) - xml.author do |author| - xml.name event.author_name - xml.email event.author_email - end - xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? } - end - end + event_to_atom(xml, event) end end From 3dc25ba331c4f5c4708b0fcd8478d943d182d760 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 4 Dec 2014 21:22:21 +0100 Subject: [PATCH 392/408] Remove warning from db seed since it is called by db setup. --- doc/development/rake_tasks.md | 3 ++- lib/tasks/seed.rake | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 lib/tasks/seed.rake diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index ffa61e66134..53f8095cb13 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -9,7 +9,8 @@ bundle exec rake setup ``` The `setup` task is a alias for `gitlab:setup`. -This tasks calls `db:setup` to create the database, with `add_limits_mysql` it adds limits to the database schema in case of a MySQL database and fianlly it runs `db:seed_fu` to seed the database. +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 fianlly 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/seed.rake b/lib/tasks/seed.rake deleted file mode 100644 index 7c006c16639..00000000000 --- a/lib/tasks/seed.rake +++ /dev/null @@ -1,6 +0,0 @@ -namespace :db do - desc "Seed is replaced with seed_fu" - task :seed => :environment do - raise "Please run db:seed_fu instead of db:seed." - end -end From 90a308ea00ece729b15b430a397d22e3b3fe3102 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 5 Dec 2014 12:34:18 +0100 Subject: [PATCH 393/408] Update release docs to deploy to GitLab.com before publishing. --- doc/release/patch.md | 3 ++- doc/release/security.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/release/patch.md b/doc/release/patch.md index ce5c2170302..2bd34b7d822 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -18,13 +18,14 @@ Otherwise include it in the monthly release and note there was a regression fix 1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier 1. Fix the issue on a feature branch, do this on the private GitLab development server 1. If it is a security issue, then assign it to the release manager and apply a 'security' label +1. Build the package for GitLab.com and do a deploy 1. Consider creating and testing workarounds 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch 1. Make sure that the build has passed and all tests are passing 1. In a separate commit in the stable branch update the CHANGELOG 1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" -### Bump version +### Bump version Get release tools diff --git a/doc/release/security.md b/doc/release/security.md index c24a394ef4a..b67e0f37a04 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -17,6 +17,7 @@ Please report suspected security vulnerabilities in private to Date: Fri, 5 Dec 2014 13:40:11 +0100 Subject: [PATCH 394/408] Update favorites to the new link. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63fa5e3da86..f303e8e7383 100644 --- a/README.md +++ b/README.md @@ -131,4 +131,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ## Is it awesome? Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. -[These people](https://twitter.com/gitlabhq/favorites) seem to like it. +[These people](https://twitter.com/gitlab/favorites) seem to like it. From 1961b590700c3f1168322039b5992bc904fdeda1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 15:42:26 +0200 Subject: [PATCH 395/408] Add locked_at to merge request Signed-off-by: Dmitriy Zaporozhets --- db/migrate/20141205134006_add_locked_at_to_merge_request.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20141205134006_add_locked_at_to_merge_request.rb diff --git a/db/migrate/20141205134006_add_locked_at_to_merge_request.rb b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb new file mode 100644 index 00000000000..49651c44a82 --- /dev/null +++ b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb @@ -0,0 +1,5 @@ +class AddLockedAtToMergeRequest < ActiveRecord::Migration + def change + add_column :merge_requests, :locked_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index ec211901e42..b8335c5841b 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: 20141121161704) do +ActiveRecord::Schema.define(version: 20141205134006) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -181,6 +181,7 @@ ActiveRecord::Schema.define(version: 20141121161704) do t.integer "iid" t.text "description" t.integer "position", default: 0 + t.datetime "locked_at" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree From b23f71ec4d76bc1a6ed4b6c1add8dbc8fb32eb40 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 15:49:25 +0200 Subject: [PATCH 396/408] Set/unset merge request locked_at timestamp after transition Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7c525b02f48..e558c4164e9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -70,6 +70,16 @@ class MergeRequest < ActiveRecord::Base transition locked: :reopened end + after_transition any => :locked do |merge_request, transition| + merge_request.locked_at = Time.now + merge_request.save + end + + after_transition :locked => (any - :locked) do |merge_request, transition| + merge_request.locked_at = nil + merge_request.save + end + state :opened state :reopened state :closed From 6487419364fa9c179e24028d85b2be10d574067f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 16:02:08 +0200 Subject: [PATCH 397/408] Automatically close merge requests that were locker for 1 day Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/merge_requests_controller.rb | 5 +++++ app/models/merge_request.rb | 4 ++++ .../projects/merge_requests/show/_state_widget.html.haml | 6 ++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 20a733b10e1..bd43d15984d 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -225,6 +225,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController @allowed_to_merge = allowed_to_merge? @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge @source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name) + + if @merge_request.locked_long_ago? + @merge_request.unlock_mr + @merge_request.close + end end def allowed_to_merge? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e558c4164e9..2cc427d35c2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -346,4 +346,8 @@ class MergeRequest < ActiveRecord::Base source_project.repository.branch_names end end + + def locked_long_ago? + locked_at && locked_at < (Time.now - 1.day) + end end diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml index 87dad6140be..f909948995c 100644 --- a/app/views/projects/merge_requests/show/_state_widget.html.haml +++ b/app/views/projects/merge_requests/show/_state_widget.html.haml @@ -11,8 +11,10 @@ - if @merge_request.closed? %h4 - Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)} - #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} + Closed + - if @merge_request.closed_event + by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)} + #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} %p Changes were not merged into target branch - if @merge_request.merged? From 8f0b558aaadd7d665011642f64aa639d73bb4a76 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 5 Dec 2014 15:29:45 +0100 Subject: [PATCH 398/408] Add snapshot backup tips --- doc/raketasks/backup_restore.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 68e8a14f52f..79580029f80 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -208,3 +208,26 @@ Add the following lines at the bottom: The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors. This is recommended to reduce cron spam. + +## Alternative backup strategies + +If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow. +In this case you can consider using filesystem snapshots as part of your backup strategy. + +Example: Amazone EBS + +> A GitLab server using omnibus-gitlab hosted on Amazon AWS. +> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`. +> In this case you could make an application backup by taking an EBS snapshot. +> The backup includes all repositories, uploads and Postgres data. + +Example: LVM snapshots + Rsync + +> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`. +> Replicating the `/var/opt/gitlab` directory usign Rsync would not be reliable because too many files would change while Rsync is running. +> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`. +> Now we can have a longer running Rsync job which will create a consistent replica on the remote server. +> The replica includes all repositories, uploads and Postgres data. + +If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server. +It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use. From db585009be76ed7b00b4298f863333c92b94cf04 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 5 Dec 2014 15:33:22 +0100 Subject: [PATCH 399/408] Revert "Merge pull request #7349 from srna/patch-1" This reverts commit b37b71d887e8521b8992aa6e4f789a38b393e55a, reversing changes made to 42a1d8083c77d3803320bbbd0ac1559ff32d2519. --- app/uploaders/attachment_uploader.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index 29a55b36ca5..b122b6c8658 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -26,10 +26,6 @@ class AttachmentUploader < CarrierWave::Uploader::Base Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" end - def url - Gitlab.config.gitlab.relative_url_root + super unless super.nil? - end - def file_storage? self.class.storage == CarrierWave::Storage::File end From ed2eaf55c1c2402a0a630838901bdddbc11fda47 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 16:59:17 +0200 Subject: [PATCH 400/408] Move issues/mr filter to partial Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/_issuable_filter.html.haml | 49 +++++++++++++++++ app/views/projects/issues/_issues.html.haml | 50 +----------------- .../projects/merge_requests/index.html.haml | 52 +------------------ 3 files changed, 52 insertions(+), 99 deletions(-) create mode 100644 app/views/projects/_issuable_filter.html.haml diff --git a/app/views/projects/_issuable_filter.html.haml b/app/views/projects/_issuable_filter.html.haml new file mode 100644 index 00000000000..7e6a94a4700 --- /dev/null +++ b/app/views/projects/_issuable_filter.html.haml @@ -0,0 +1,49 @@ +.issues-filters + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-user + %span.light assignee: + - if @assignee.present? + %strong= @assignee.name + - elsif params[:assignee_id] == "0" + Unassigned + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(assignee_id: nil) do + Any + = link_to project_filter_path(assignee_id: 0) do + Unassigned + - @assignees.sort_by(&:name).each do |user| + %li + = link_to project_filter_path(assignee_id: user.id) do + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' + = user.name + + .dropdown.inline.prepend-left-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-clock-o + %span.light milestone: + - if @milestone.present? + %strong= @milestone.title + - elsif params[:milestone_id] == "0" + None (backlog) + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(milestone_id: nil) do + Any + = link_to project_filter_path(milestone_id: 0) do + None (backlog) + - project_active_milestones.each do |milestone| + %li + = link_to project_filter_path(milestone_id: milestone.id) do + %strong= milestone.title + %small.light= milestone.expires_at + + .pull-right + = render 'shared/sort_dropdown' diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 0bff8bdbead..15c84c7ced2 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,55 +1,7 @@ .append-bottom-10 .check-all-holder = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" - .issues-filters - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-user - %span.light assignee: - - if @assignee.present? - %strong= @assignee.name - - elsif params[:assignee_id] == "0" - Unassigned - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(assignee_id: nil) do - Any - = link_to project_filter_path(assignee_id: 0) do - Unassigned - - @assignees.sort_by(&:name).each do |user| - %li - = link_to project_filter_path(assignee_id: user.id) do - = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' - = user.name - - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-clock-o - %span.light milestone: - - if @milestone.present? - %strong= @milestone.title - - elsif params[:milestone_id] == "0" - None (backlog) - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(milestone_id: nil) do - Any - = link_to project_filter_path(milestone_id: 0) do - None (backlog) - - project_active_milestones.each do |milestone| - %li - = link_to project_filter_path(milestone_id: milestone.id) do - %strong= milestone.title - %small.light= milestone.expires_at - - .pull-right - = render 'shared/sort_dropdown' + = render 'projects/issuable_filter' .clearfix .issues_bulk_update.hide diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index a6d90a68b11..b93e0f9da3e 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -5,56 +5,8 @@ = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project), labels: true, redirect: 'merge_requests', entity: 'merge_request' .col-md-9 - .mr-filters.append-bottom-10 - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-user - %span.light assignee: - - if @assignee.present? - %strong= @assignee.name - - elsif params[:assignee_id] == "0" - Unassigned - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(assignee_id: nil) do - Any - = link_to project_filter_path(assignee_id: 0) do - Unassigned - - @assignees.sort_by(&:name).each do |user| - %li - = link_to project_filter_path(assignee_id: user.id) do - = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' - = user.name - - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-clock-o - %span.light milestone: - - if @milestone.present? - %strong= @milestone.title - - elsif params[:milestone_id] == "0" - None (backlog) - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(milestone_id: nil) do - Any - = link_to project_filter_path(milestone_id: 0) do - None (backlog) - - project_active_milestones.each do |milestone| - %li - = link_to project_filter_path(milestone_id: milestone.id) do - %strong= milestone.title - %small.light= milestone.expires_at - - .pull-right - = render 'shared/sort_dropdown' - + .append-bottom-10 + = render 'projects/issuable_filter' .panel.panel-default %ul.well-list.mr-list = render @merge_requests From 3f1fad5f090ba77f202ae9ad1adbad394deef4db Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 5 Dec 2014 15:59:55 +0100 Subject: [PATCH 401/408] Add an avatar_url spec when relative url is set. --- spec/helpers/application_helper_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 2db67cfdf95..07dd33b211b 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -66,6 +66,16 @@ describe ApplicationHelper do avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png") end + it "should return an url for the avatar with relative url" do + Gitlab.config.gitlab.stub(relative_url_root: "/gitlab") + Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url)) + + user = create(:user) + user.avatar = File.open(avatar_file_path) + user.save! + avatar_icon(user.email).to_s.should match("/gitlab//uploads/user/avatar/#{ user.id }/gitlab_logo.png") + end + it "should call gravatar_icon when no avatar is present" do user = create(:user, email: 'test@example.com') user.save! From e0f30c605bbf0a92f3ddeffdd80d765a5f041a06 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 17:13:07 +0200 Subject: [PATCH 402/408] Add author filter for issues & merge requests pages Signed-off-by: Dmitriy Zaporozhets --- .../projects/application_controller.rb | 27 +++++++++++++++++++ app/controllers/projects/issues_controller.rb | 16 ++--------- .../projects/merge_requests_controller.rb | 10 +------ app/finders/issuable_finder.rb | 9 +++++++ app/views/projects/_issuable_filter.html.haml | 23 ++++++++++++++++ 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 7e4580017dd..6b7fe06d59f 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -29,4 +29,31 @@ class Projects::ApplicationController < ApplicationController redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch" end end + + def set_filter_variables(collection) + params[:sort] ||= 'newest' + params[:scope] = 'all' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + + @sort = params[:sort].humanize + + assignee_id = params[:assignee_id] + author_id = params[:author_id] + milestone_id = params[:milestone_id] + + if assignee_id.present? && !assignee_id.to_i.zero? + @assignee = @project.team.find(assignee_id) + end + + if author_id.present? && !author_id.to_i.zero? + @author = @project.team.find(assignee_id) + end + + if milestone_id.present? && !milestone_id.to_i.zero? + @milestone = @project.milestones.find(milestone_id) + end + + @assignees = User.where(id: collection.pluck(:assignee_id)) + @authors = User.where(id: collection.pluck(:author_id)) + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c6d526f05c5..22235123826 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -18,18 +18,12 @@ class Projects::IssuesController < Projects::ApplicationController def index terms = params['issue_search'] + set_filter_variables(@project.issues) - @issues = issues_filtered + @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id)) @issues = @issues.full_search(terms) if terms.present? @issues = @issues.page(params[:page]).per(20) - assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] - @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? - @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? - sort_param = params[:sort] || 'newest' - @sort = sort_param.humanize unless sort_param.empty? - @assignees = User.where(id: @project.issues.pluck(:assignee_id)).active - respond_to do |format| format.html format.atom { render layout: false } @@ -127,12 +121,6 @@ class Projects::IssuesController < Projects::ApplicationController return render_404 unless @project.issues_enabled end - def issues_filtered - params[:scope] = 'all' if params[:scope].blank? - params[:state] = 'opened' if params[:state].blank? - @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id)) - end - # Since iids are implemented only in 6.1 # user may navigate to issue page using old global ids. # diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index bd43d15984d..4d6f41e9de5 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -17,18 +17,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] def index - params[:sort] ||= 'newest' - params[:scope] = 'all' if params[:scope].blank? - params[:state] = 'opened' if params[:state].blank? + set_filter_variables(@project.merge_requests) @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id)) @merge_requests = @merge_requests.page(params[:page]).per(20) - - @sort = params[:sort].humanize - assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] - @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? - @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? - @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id)) end def show diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index d0574240511..e1477510065 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -33,6 +33,7 @@ class IssuableFinder items = by_search(items) items = by_milestone(items) items = by_assignee(items) + items = by_author(items) items = by_label(items) items = sort(items) end @@ -125,6 +126,14 @@ class IssuableFinder items end + def by_author(items) + if params[:author_id].present? + items = items.where(author_id: (params[:author_id] == '0' ? nil : params[:author_id])) + end + + items + end + def by_label(items) if params[:label_name].present? label_names = params[:label_name].split(",") diff --git a/app/views/projects/_issuable_filter.html.haml b/app/views/projects/_issuable_filter.html.haml index 7e6a94a4700..b3e5efd938f 100644 --- a/app/views/projects/_issuable_filter.html.haml +++ b/app/views/projects/_issuable_filter.html.haml @@ -22,6 +22,29 @@ = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' = user.name + .dropdown.inline.prepend-left-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-user + %span.light author: + - if @author.present? + %strong= @author.name + - elsif params[:author_id] == "0" + Unassigned + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to project_filter_path(author_id: nil) do + Any + = link_to project_filter_path(author_id: 0) do + Unassigned + - @authors.sort_by(&:name).each do |user| + %li + = link_to project_filter_path(author_id: user.id) do + = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' + = user.name + .dropdown.inline.prepend-left-10 %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %i.fa.fa-clock-o From c8a96d8ab05333b75a2215a7330fc4296c480f40 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 17:25:22 +0200 Subject: [PATCH 403/408] More tests for issues finder Signed-off-by: Dmitriy Zaporozhets --- spec/finders/issues_finder_spec.rb | 85 +++++++++++++++++++----------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 7489e56f423..06e247aea61 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -5,9 +5,10 @@ describe IssuesFinder do let(:user2) { create :user } let(:project1) { create(:project) } let(:project2) { create(:project) } - let(:issue1) { create(:issue, assignee: user, project: project1) } - let(:issue2) { create(:issue, assignee: user, project: project2) } - let(:issue3) { create(:issue, assignee: user2, project: project2) } + let(:milestone) { create(:milestone, project: project1) } + let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) } + let(:issue2) { create(:issue, author: user, assignee: user, project: project2) } + let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) } before do project1.team << [user, :master] @@ -22,37 +23,59 @@ describe IssuesFinder do issue3 end - it 'should filter by all' do - params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(user, params) - issues.size.should == 3 + context 'scope: all' do + it 'should filter by all' do + params = { scope: "all", state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + issues.size.should == 3 + end + + it 'should filter by assignee id' do + params = { scope: "all", assignee_id: user.id, state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + issues.size.should == 2 + end + + it 'should filter by author id' do + params = { scope: "all", author_id: user2.id, state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + issues.should == [issue3] + end + + it 'should filter by milestone id' do + params = { scope: "all", milestone_id: milestone.id, state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + issues.should == [issue1] + end + + it 'should be empty for unauthorized user' do + params = { scope: "all", state: 'opened' } + issues = IssuesFinder.new.execute(nil, params) + issues.size.should be_zero + end + + it 'should not include unauthorized issues' do + params = { scope: "all", state: 'opened' } + issues = IssuesFinder.new.execute(user2, params) + issues.size.should == 2 + issues.should_not include(issue1) + issues.should include(issue2) + issues.should include(issue3) + end end - it 'should filter by assignee' do - params = { scope: "assigned-to-me", state: 'opened' } - issues = IssuesFinder.new.execute(user, params) - issues.size.should == 2 - end + context 'personal scope' do + it 'should filter by assignee' do + params = { scope: "assigned-to-me", state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + issues.size.should == 2 + end - it 'should filter by project' do - params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } - issues = IssuesFinder.new.execute(user, params) - issues.size.should == 1 - end - - it 'should be empty for unauthorized user' do - params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(nil, params) - issues.size.should be_zero - end - - it 'should not include unauthorized issues' do - params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(user2, params) - issues.size.should == 2 - issues.should_not include(issue1) - issues.should include(issue2) - issues.should include(issue3) + it 'should filter by project' do + params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } + issues = IssuesFinder.new.execute(user, params) + issues.size.should == 1 + end end end end From 7cefd9c6ef4fbbd8eb297ac03e2fa3e43c44f1a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 18:10:25 +0200 Subject: [PATCH 404/408] Prevent 500 on MR page if merge_event missing Signed-off-by: Dmitriy Zaporozhets --- .../projects/merge_requests/show/_state_widget.html.haml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml index f909948995c..a4f2a890969 100644 --- a/app/views/projects/merge_requests/show/_state_widget.html.haml +++ b/app/views/projects/merge_requests/show/_state_widget.html.haml @@ -19,8 +19,10 @@ - if @merge_request.merged? %h4 - Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)} - #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} + Merged + - if @merge_request.merge_event + by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)} + #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} = render "projects/merge_requests/show/remove_source_branch" - if @merge_request.locked? @@ -46,4 +48,3 @@ Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} = succeed '.' do != gfm(issues_sentence(@closes_issues)) - From 4491a3d12b414a52f32175e328df8dc48987d0fd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 18:17:51 +0200 Subject: [PATCH 405/408] Decline push if repository does not exist Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/git_access.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 8b4729896b5..875f8d8b3a3 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -49,8 +49,17 @@ module Gitlab end def push_access_check(user, project, changes) - return build_status_object(false, "You don't have access") unless user && user_allowed?(user) - return build_status_object(true) if changes.blank? + unless user && user_allowed?(user) + return build_status_object(false, "You don't have access") + end + + if changes.blank? + return build_status_object(true) + end + + unless project.repository.exists? + return build_status_object(false, "Repository does not exist") + end changes = changes.lines if changes.kind_of?(String) @@ -79,7 +88,7 @@ module Gitlab else :push_code_to_protected_branches end - elsif project.repository && project.repository.tag_names.include?(tag_name(ref)) + elsif project.repository.tag_names.include?(tag_name(ref)) # Prevent any changes to existing git tag unless user has permissions :admin_project else From 2a494d99faabb3b34bbb6e57a8d93bafd4f002be Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Dec 2014 18:30:50 +0200 Subject: [PATCH 406/408] Update CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 6c28a573703..0f9cb066b7a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,7 +19,7 @@ v 7.6.0 - - In the docker directory is a container template based on the Omnibus packages. - Update Sidekiq to version 2.17.8 - - + - Add author filter to project issues and merge requests pages - Atom feed for user activity v 7.5.2 From 04fa03a7d84581d4c5817920456d6b1dc4cb1a58 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 6 Dec 2014 18:22:19 +0200 Subject: [PATCH 407/408] Bolder event title and lighter color for event body Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/sections/events.scss | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 485a9c46610..a766d6e77ab 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -47,7 +47,7 @@ .event-title { @include str-truncated(72%); color: #333; - font-weight: normal; + font-weight: 500; font-size: 14px; .author_name { color: #333; @@ -56,12 +56,9 @@ .event-body { margin-left: 35px; margin-right: 100px; + color: #777; - .event-info { - color: #666; - } .event-note { - color: #666; margin-top: 5px; .md { @@ -72,7 +69,7 @@ border: none; background: #f9f9f9; border-radius: 0; - color: #666; + color: #777; margin: 0 20px; } @@ -120,7 +117,6 @@ padding: 3px; padding-left: 0; border: none; - color: #666; .commit-row-title { font-size: 12px; } From 0d5265bbec0ffec004fab0b85b81a0a1bea3b471 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 7 Dec 2014 12:29:37 +0200 Subject: [PATCH 408/408] Execute project services asynchronously Signed-off-by: Dmitriy Zaporozhets --- app/models/project.rb | 10 ++-------- app/models/service.rb | 4 ++++ app/workers/project_service_worker.rb | 9 +++++++++ 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 app/workers/project_service_worker.rb diff --git a/app/models/project.rb b/app/models/project.rb index daf4bdd0aad..32b0145ca24 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -390,14 +390,8 @@ class Project < ActiveRecord::Base end def execute_services(data) - services.each do |service| - - # Call service hook only if it is active - begin - service.execute(data) if service.active - rescue => e - logger.error(e) - end + services.select(&:active).each do |service| + service.async_execute(data) end end diff --git a/app/models/service.rb b/app/models/service.rb index c489c1e96e1..71c8aa39e45 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -82,4 +82,8 @@ class Service < ActiveRecord::Base } end end + + def async_execute(data) + Sidekiq::Client.enqueue(ProjectServiceWorker, id, data) + end end diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb new file mode 100644 index 00000000000..cc0a7f25664 --- /dev/null +++ b/app/workers/project_service_worker.rb @@ -0,0 +1,9 @@ +class ProjectServiceWorker + include Sidekiq::Worker + + sidekiq_options queue: :project_web_hook + + def perform(hook_id, data) + Service.find(hook_id).execute(data) + end +end