From 41734b630ed86c5aa0814cb331f4f8e94e0bf2a2 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Thu, 29 Oct 2015 14:13:30 +0100 Subject: [PATCH 01/39] Ensure variable is evalled --- app/views/notify/project_was_moved_email.text.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb index d8a23dabf4..b2c5f71e46 100644 --- a/app/views/notify/project_was_moved_email.text.erb +++ b/app/views/notify/project_was_moved_email.text.erb @@ -1,4 +1,4 @@ -Project #{@old_path_with_namespace} was moved to another location +Project <%= @old_path_with_namespace %> was moved to another location The project is now located under <%= namespace_project_url(@project.namespace, @project) %> From 2c13723150b9095bb5b1482c62bab7817fd9a49e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 12:58:43 -0500 Subject: [PATCH 02/39] Update doc/install/installation.md for 8-2-stable [ci skip] --- doc/install/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 8028e51dbc..698f54cabc 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -211,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-2-stable gitlab -**Note:** You can change `8-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It From b8ab012544e584210b8b41219f85babdab091f59 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 12:59:03 -0500 Subject: [PATCH 03/39] Update doc/install/installation.md for gitlab-workhorse [ci skip] --- doc/install/installation.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 698f54cabc..1479bdcb76 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -128,11 +128,10 @@ Install the Bundler Gem: ## 3. Go -Since GitLab 8.0, Git HTTP requests are handled by gitlab-git-http-server. -This is a small daemon written in Go. -To install gitlab-git-http-server we need a Go compiler. -The instructions below assume you use 64-bit Linux. You can find -downloads for other platforms at the [Go download +Since GitLab 8.0, Git HTTP requests are handled by gitlab-workhorse (formerly +gitlab-git-http-server). This is a small daemon written in Go. To install +gitlab-workhorse we need a Go compiler. The instructions below assume you +use 64-bit Linux. You can find downloads for other platforms at the [Go download page](https://golang.org/dl). curl -O --progress https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz @@ -323,16 +322,16 @@ GitLab Shell is an SSH access and repository management software developed speci **Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". -### Install gitlab-git-http-server +### Install gitlab-workhorse cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git - cd gitlab-git-http-server - sudo -u git -H git checkout 0.3.0 + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git + cd gitlab-workhorse + sudo -u git -H git checkout 0.4.1 sudo -u git -H make ### Initialize Database and Activate Advanced Features - + # Go to Gitlab installation folder cd /home/git/gitlab From a060a8dad5b44d3da20485123937ddf89169f8c7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 13:06:52 -0500 Subject: [PATCH 04/39] Update doc/update/8.1-to-8.2.md [ci skip] --- doc/update/8.1-to-8.2.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index 3772f624e9..73d899f9c2 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -2,7 +2,8 @@ **NOTE:** GitLab 8.0 introduced several significant changes related to installation and configuration which *are not duplicated here*. Be sure you're -already running a working version of 8.0 before proceeding with this guide. +already running a working version of at least 8.0 before proceeding with this +guide. ### 0. Double-check your Git version @@ -67,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.6.5 +sudo -u git -H git checkout v2.6.6 ``` ### 5. Replace gitlab-git-http-server with gitlab-workhorse @@ -80,7 +81,7 @@ from GitLab 8.1. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse -sudo -u git -H git checkout 0.3.1 +sudo -u git -H git checkout 0.4.1 sudo -u git -H make ``` @@ -165,12 +166,12 @@ To make sure you didn't miss anything run a more thorough check: If all items are green, then congratulations, the upgrade is complete! -## Things went south? Revert to previous version (8.0) +## Things went south? Revert to previous version (8.1) ### 1. Revert the code to the previous version -Follow the [upgrade guide from 7.14 to 8.0](7.14-to-8.0.md), except for the database migration -(The backup is already migrated to the previous version) +Follow the [upgrade guide from 8.0 to 8.1](8.0-to-8.1.md), except for the +database migration (the backup is already migrated to the previous version). ### 2. Restore from the backup From 47b4b91ecd26066a04cc4b506dd62ccaed67bbdf Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 13:10:34 -0500 Subject: [PATCH 05/39] Reorder monthly release tasks for RC1 day It didn't make sense to merge CE master into EE master, and *then* do the RC1 tasks (e.g., install/update docs) because then the RC1 won't have the updated docs. --- 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 4925816daa..d347f58ba0 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -37,9 +37,9 @@ template are explained below: ### Xth: (6 working days before the 22nd) -- [ ] Merge CE `master` into EE `master` via merge request (#LINK) - [ ] Determine QA person and notify this person - [ ] Check the tasks in [how to rc1 guide](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/release/howto_rc1.md) and delegate tasks if necessary +- [ ] Merge CE `master` into EE `master` via merge request (#LINK) - [ ] Create CE and EE RC1 versions (#LINK) - [ ] Build RC1 packages From 05335a3c8584c48a9317bd0919eccee6948de742 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 16:07:27 +0100 Subject: [PATCH 06/39] Create milestones in the group Signed-off-by: Dmitriy Zaporozhets --- .../groups/milestones_controller.rb | 36 +++++++++++++-- app/views/groups/milestones/index.html.haml | 12 +++-- app/views/groups/milestones/new.html.haml | 46 +++++++++++++++++++ config/routes.rb | 2 +- features/groups.feature | 9 +++- features/steps/groups.rb | 22 +++++++++ features/steps/shared/paths.rb | 4 ++ 7 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 app/views/groups/milestones/new.html.haml diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 669f7f3126..8779376d93 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -1,16 +1,34 @@ class Groups::MilestonesController < Groups::ApplicationController before_action :authorize_group_milestone!, only: :update + before_action :group def index - project_milestones = case params[:state] - when 'all'; state - when 'closed'; state('closed') - else state('active') - end + project_milestones = + case params[:state] + when 'all'; state + when 'closed'; state('closed') + else state('active') + end + @group_milestones = Milestones::GroupService.new(project_milestones).execute @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE) end + def new + @group_milestone = OpenStruct.new(title: nil, description: nil) + end + + def create + project_ids = params[:milestone][:project_ids] + title = milestone_params[:title] + + @group.projects.where(id: project_ids).each do |project| + Milestones::CreateService.new(project, current_user, milestone_params).execute + end + + redirect_to group_milestone_path(@group, title.parameterize, title: title) + end + def show project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") @group_milestone = Milestones::GroupService.new(project_milestones).milestone(title) @@ -51,4 +69,12 @@ class Groups::MilestonesController < Groups::ApplicationController def authorize_group_milestone! return render_404 unless can?(current_user, :admin_group, group) end + + def milestone_params + params.require(:milestone).permit( + :title, + :description, + :due_date + ) + end end diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 2bbcad5fdf..ded4f3713f 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -3,9 +3,15 @@ = render 'shared/milestones_filter' .gray-content-block - Only milestones from - %strong #{@group.name} - group are listed here. + .pull-right + %span.pull-right.hidden-xs + = link_to new_group_milestone_path(@group), class: "btn btn-new" do + New Milestone + + .oneline + Only milestones from + %strong #{@group.name} + group are listed here. .milestones %ul.content-list - if @group_milestones.blank? diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml new file mode 100644 index 0000000000..287f89a707 --- /dev/null +++ b/app/views/groups/milestones/new.html.haml @@ -0,0 +1,46 @@ +%h3.page-title + New Milestone + +%p.light + This will create milestone in every selected project +%hr + += form_for @group_milestone, as: :milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f| + .row + .col-md-6 + .form-group + = f.label :title, "Title", class: "control-label" + .col-sm-10 + = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true + %p.hint Required + .form-group.milestone-description + = f.label :description, "Description", class: "control-label" + .col-sm-10 + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' + = render 'projects/notes/hints' + .clearfix + .error-alert + .form-group + = f.label :projects, "Projects", class: "control-label" + .col-sm-10 + = f.collection_select :project_ids, @group.projects, :id, :name, + { selected: @group.projects.map(&:id) }, multiple: true, class: 'select2' + + .col-md-6 + .form-group + = f.label :due_date, "Due Date", class: "control-label" + .col-sm-10= f.hidden_field :due_date + .col-sm-10 + .datepicker + + .form-actions + = f.submit 'Create Milestone', class: "btn-create btn" + = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel" + + +:javascript + $( ".datepicker" ).datepicker({ + dateFormat: "yy-mm-dd", + onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } + }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); diff --git a/config/routes.rb b/config/routes.rb index bd85f4e3c6..c0077ab1a9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -368,7 +368,7 @@ Gitlab::Application.routes.draw do end resource :avatar, only: [:destroy] - resources :milestones, only: [:index, :show, :update] + resources :milestones, only: [:index, :show, :update, :new, :create] end end diff --git a/features/groups.feature b/features/groups.feature index db37fa3b37..615eff6a33 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -153,6 +153,13 @@ Feature: Groups Then I should see group milestone with descriptions and expiry date And I should see group milestone with all issues and MRs assigned to that milestone + Scenario: Create multiple milestones with one form + Given I visit group "Owned" milestones page + And I click new milestone button + And I fill milestone name + When I press create mileston button + Then milestone in each project should be created + # Group projects in settings Scenario: I should see all projects in the project list in settings Given Group "Owned" has archived project @@ -169,4 +176,4 @@ Feature: Groups When I visit group "Owned" page Then I should see group "Owned" Then I should see project "Public-project" - + diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 70388c18fc..a8fba2406a 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -255,6 +255,28 @@ class Spinach::Features::Groups < Spinach::FeatureSteps expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') end + step 'I fill milestone name' do + fill_in 'milestone_title', with: 'v2.9.0' + end + + step 'I click new milestone button' do + click_link "New Milestone" + end + + step 'I press create mileston button' do + click_button "Create Milestone" + end + + step 'milestone in each project should be created' do + group = Group.find_by(name: 'Owned') + expect(page).to have_content "Milestone v2.9.0" + expect(group.projects).to be_present + + group.projects.each do |project| + expect(page).to have_content project.name + end + end + protected def assigned_to_me(key) diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index eb978620da..c74a5fd3bc 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -31,6 +31,10 @@ module SharedPaths visit merge_requests_group_path(Group.find_by(name: "Owned")) end + step 'I visit group "Owned" milestones page' do + visit group_milestones_path(Group.find_by(name: "Owned")) + end + step 'I visit group "Owned" members page' do visit group_group_members_path(Group.find_by(name: "Owned")) end From 986695e136a8f6afa326048b30be77a9265d3bf7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 19:20:48 +0100 Subject: [PATCH 07/39] Refactor global and group milestones logic Signed-off-by: Dmitriy Zaporozhets --- app/controllers/concerns/global_milestones.rb | 19 ++++++ .../dashboard/milestones_controller.rb | 29 ++------- .../groups/application_controller.rb | 11 +++- app/controllers/groups/avatars_controller.rb | 2 - .../groups/group_members_controller.rb | 5 -- .../groups/milestones_controller.rb | 65 ++++++------------- app/finders/milestones_finder.rb | 13 ++++ app/helpers/labels_helper.rb | 2 +- app/helpers/milestones_helper.rb | 2 +- app/models/global_label.rb | 17 +++++ ...group_milestone.rb => global_milestone.rb} | 18 +++-- app/models/group_label.rb | 9 --- app/services/labels/group_service.rb | 26 -------- ...group_service.rb => collection_service.rb} | 2 +- .../dashboard/milestones/index.html.haml | 6 +- app/views/dashboard/milestones/show.html.haml | 36 +++++----- app/views/groups/milestones/index.html.haml | 6 +- app/views/groups/milestones/new.html.haml | 2 +- app/views/groups/milestones/show.html.haml | 42 ++++++------ 19 files changed, 146 insertions(+), 166 deletions(-) create mode 100644 app/controllers/concerns/global_milestones.rb create mode 100644 app/finders/milestones_finder.rb create mode 100644 app/models/global_label.rb rename app/models/{group_milestone.rb => global_milestone.rb} (78%) delete mode 100644 app/models/group_label.rb delete mode 100644 app/services/labels/group_service.rb rename app/services/milestones/{group_service.rb => collection_service.rb} (90%) diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb new file mode 100644 index 0000000000..b428249acd --- /dev/null +++ b/app/controllers/concerns/global_milestones.rb @@ -0,0 +1,19 @@ +module GlobalMilestones + extend ActiveSupport::Concern + + def milestones + @milestones = MilestonesFinder.new.execute(@projects, params) + @milestones = GlobalMilestone.build_collection(@milestones) + @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE) + end + + def milestone + milestones = Milestone.of_projects(@projects).where(title: params[:title]) + + if milestones.present? + @milestone = GlobalMilestone.new(params[:title], milestones) + else + render_404 + end + end +end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index 53896d4f2c..2bdce0f8a0 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -1,34 +1,19 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController - before_action :load_projects + include GlobalMilestones + + before_action :projects + before_action :milestones, only: [:index] + before_action :milestone, only: [:show] def index - project_milestones = case params[:state] - when 'all'; state - when 'closed'; state('closed') - else state('active') - end - @dashboard_milestones = Milestones::GroupService.new(project_milestones).execute - @dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(PER_PAGE) end def show - project_milestones = Milestone.where(project_id: @projects).order("due_date ASC") - @dashboard_milestone = Milestones::GroupService.new(project_milestones).milestone(title) end private - def load_projects - @projects = current_user.authorized_projects.sorted_by_activity.non_archived - end - - def title - params[:title] - end - - def state(state = nil) - conditions = { project_id: @projects } - conditions.reverse_merge!(state: state) if state - Milestone.where(conditions).order("title ASC") + def projects + @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived end end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 6878d4bc07..be801858ea 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -1,8 +1,13 @@ class Groups::ApplicationController < ApplicationController layout 'group' + before_action :group private - + + def group + @group ||= Group.find_by(path: params[:group_id]) + end + def authorize_read_group! unless @group and can?(current_user, :read_group, @group) if current_user.nil? @@ -12,13 +17,13 @@ class Groups::ApplicationController < ApplicationController end end end - + def authorize_admin_group! unless can?(current_user, :admin_group, group) return render_404 end end - + def authorize_admin_group_member! unless can?(current_user, :admin_group_member, group) return render_403 diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb index 6aa64222f7..f390705dc6 100644 --- a/app/controllers/groups/avatars_controller.rb +++ b/app/controllers/groups/avatars_controller.rb @@ -1,8 +1,6 @@ class Groups::AvatarsController < ApplicationController def destroy - @group = Group.find_by(path: params[:group_id]) @group.remove_avatar! - @group.save redirect_to edit_group_path(@group) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 91518c44a9..b25957a06e 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -1,6 +1,5 @@ class Groups::GroupMembersController < Groups::ApplicationController skip_before_action :authenticate_user!, only: [:index] - before_action :group # Authorize before_action :authorize_read_group! @@ -80,10 +79,6 @@ class Groups::GroupMembersController < Groups::ApplicationController protected - def group - @group ||= Group.find_by(path: params[:group_id]) - end - def member_params params.require(:group_member).permit(:access_level, :user_id) end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 8779376d93..6833a550c9 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -1,21 +1,16 @@ class Groups::MilestonesController < Groups::ApplicationController - before_action :authorize_group_milestone!, only: :update - before_action :group + include GlobalMilestones + + before_action :projects + before_action :milestones, only: [:index] + before_action :milestone, only: [:show, :update] + before_action :authorize_group_milestone!, only: [:create, :update] def index - project_milestones = - case params[:state] - when 'all'; state - when 'closed'; state('closed') - else state('active') - end - - @group_milestones = Milestones::GroupService.new(project_milestones).execute - @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE) end def new - @group_milestone = OpenStruct.new(title: nil, description: nil) + @milestone = Milestone.new end def create @@ -26,55 +21,35 @@ class Groups::MilestonesController < Groups::ApplicationController Milestones::CreateService.new(project, current_user, milestone_params).execute end - redirect_to group_milestone_path(@group, title.parameterize, title: title) + redirect_to milestone_path(title) end def show - project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") - @group_milestone = Milestones::GroupService.new(project_milestones).milestone(title) end def update - project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") - @group_milestones = Milestones::GroupService.new(project_milestones).milestone(title) - - @group_milestones.milestones.each do |milestone| - Milestones::UpdateService.new(milestone.project, current_user, params[:milestone]).execute(milestone) + @milestone.milestones.each do |milestone| + Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone) end - respond_to do |format| - format.js - format.html do - redirect_to group_milestones_path(group) - end - end + redirect_back_or_default(default: milestone_path(@milestone.title)) end private - def group - @group ||= Group.find_by(path: params[:group_id]) - end - - def title - params[:title] - end - - def state(state = nil) - conditions = { project_id: group.projects } - conditions.reverse_merge!(state: state) if state - Milestone.where(conditions).order("title ASC") - end - def authorize_group_milestone! return render_404 unless can?(current_user, :admin_group, group) end def milestone_params - params.require(:milestone).permit( - :title, - :description, - :due_date - ) + params.require(:milestone).permit(:title, :description, :due_date, :state_event) + end + + def milestone_path(title) + group_milestone_path(@group, title.parameterize, title: title) + end + + def projects + @projects ||= @group.projects end end diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb new file mode 100644 index 0000000000..71f207ca03 --- /dev/null +++ b/app/finders/milestones_finder.rb @@ -0,0 +1,13 @@ +class MilestonesFinder + def execute(projects, params) + milestones = Milestone.of_projects(projects) + milestones = milestones.order("due_date ASC") + + case params[:state] + when 'closed' then milestones.closed + when 'all' then milestones + else milestones.active + end + end +end + diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index ee04ace35d..795fb439f2 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -100,7 +100,7 @@ module LabelsHelper Label.where(project_id: @projects) end - grouped_labels = Labels::GroupService.new(labels).execute + grouped_labels = GlobalLabel.build_collection(labels) grouped_labels.unshift(Label::None) grouped_labels.unshift(Label::Any) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 37a5b58cce..ad43892b63 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -28,7 +28,7 @@ module MilestonesHelper Milestone.where(project_id: @projects) end.active - grouped_milestones = Milestones::GroupService.new(milestones).execute + grouped_milestones = GlobalMilestone.build_collection(milestones) grouped_milestones.unshift(Milestone::None) grouped_milestones.unshift(Milestone::Any) diff --git a/app/models/global_label.rb b/app/models/global_label.rb new file mode 100644 index 0000000000..0171f7d54b --- /dev/null +++ b/app/models/global_label.rb @@ -0,0 +1,17 @@ +class GlobalLabel + attr_accessor :title, :labels + alias_attribute :name, :title + + def self.build_collection(labels) + labels = labels.group_by(&:title) + + labels.map do |title, label| + new(title, label) + end + end + + def initialize(title, labels) + @title = title + @labels = labels + end +end diff --git a/app/models/group_milestone.rb b/app/models/global_milestone.rb similarity index 78% rename from app/models/group_milestone.rb rename to app/models/global_milestone.rb index 91844da62e..f96e9d41c9 100644 --- a/app/models/group_milestone.rb +++ b/app/models/global_milestone.rb @@ -1,7 +1,15 @@ -class GroupMilestone +class GlobalMilestone attr_accessor :title, :milestones alias_attribute :name, :title + def self.build_collection(milestones) + milestones = milestones.group_by(&:title) + + milestones.map do |title, milestones| + new(title, milestones) + end + end + def initialize(title, milestones) @title = title @milestones = milestones @@ -10,7 +18,7 @@ class GroupMilestone def safe_title @title.parameterize end - + def projects milestones.map { |milestone| milestone.project } end @@ -60,15 +68,15 @@ class GroupMilestone end def issues - @group_issues ||= milestones.map(&:issues).flatten.group_by(&:state) + @issues ||= milestones.map(&:issues).flatten.group_by(&:state) end def merge_requests - @group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) + @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) end def participants - @group_participants ||= milestones.map(&:participants).flatten.compact.uniq + @participants ||= milestones.map(&:participants).flatten.compact.uniq end def opened_issues diff --git a/app/models/group_label.rb b/app/models/group_label.rb deleted file mode 100644 index 0fc39cb877..0000000000 --- a/app/models/group_label.rb +++ /dev/null @@ -1,9 +0,0 @@ -class GroupLabel - attr_accessor :title, :labels - alias_attribute :name, :title - - def initialize(title, labels) - @title = title - @labels = labels - end -end diff --git a/app/services/labels/group_service.rb b/app/services/labels/group_service.rb deleted file mode 100644 index b26cee24d5..0000000000 --- a/app/services/labels/group_service.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Labels - class GroupService < ::BaseService - def initialize(project_labels) - @project_labels = project_labels.group_by(&:title) - end - - def execute - build(@project_labels) - end - - def label(title) - if title - group_label = @project_labels[title].group_by(&:title) - build(group_label).first - else - nil - end - end - - private - - def build(label) - label.map { |title, labels| GroupLabel.new(title, labels) } - end - end -end diff --git a/app/services/milestones/group_service.rb b/app/services/milestones/collection_service.rb similarity index 90% rename from app/services/milestones/group_service.rb rename to app/services/milestones/collection_service.rb index 11d702f1e7..2eefec99e7 100644 --- a/app/services/milestones/group_service.rb +++ b/app/services/milestones/collection_service.rb @@ -1,5 +1,5 @@ module Milestones - class GroupService < Milestones::BaseService + class CollectionService < Milestones::BaseService def initialize(project_milestones) @project_milestones = project_milestones.group_by(&:title) end diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 21b25c3986..635251e237 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -10,10 +10,10 @@ .milestones %ul.content-list - - if @dashboard_milestones.blank? + - if @milestones.blank? %li .nothing-here-block No milestones to show - else - - @dashboard_milestones.each do |milestone| + - @milestones.each do |milestone| = render 'milestone', milestone: milestone - = paginate @dashboard_milestones, theme: "gitlab" + = paginate @milestones, theme: "gitlab" diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 2fe14c6388..580db613ed 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -1,14 +1,14 @@ -- page_title @dashboard_milestone.title, "Milestones" +- page_title @milestone.title, "Milestones" %h4.page-title - .issue-box{ class: "issue-box-#{@dashboard_milestone.closed? ? 'closed' : 'open'}" } - - if @dashboard_milestone.closed? + .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } + - if @milestone.closed? Closed - else Open - Milestone #{@dashboard_milestone.title} + Milestone #{@milestone.title} %hr -- if (@dashboard_milestone.total_items_count == @dashboard_milestone.closed_items_count) && @dashboard_milestone.active? +- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. @@ -22,7 +22,7 @@ %th Open issues %th State %th Due date - - @dashboard_milestone.milestones.each do |milestone| + - @milestone.milestones.each do |milestone| %tr %td = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) @@ -39,46 +39,46 @@ .context %p.lead Progress: - #{@dashboard_milestone.closed_items_count} closed + #{@milestone.closed_items_count} closed – - #{@dashboard_milestone.open_items_count} open - = milestone_progress_bar(@dashboard_milestone) + #{@milestone.open_items_count} open + = milestone_progress_bar(@milestone) %ul.nav.nav-tabs %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues - %span.badge= @dashboard_milestone.issue_count + %span.badge= @milestone.issue_count %li = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do Merge Requests - %span.badge= @dashboard_milestone.merge_requests_count + %span.badge= @milestone.merge_requests_count %li = link_to '#tab-participants', 'data-toggle' => 'tab' do Participants - %span.badge= @dashboard_milestone.participants.count + %span.badge= @milestone.participants.count .pull-right - = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @dashboard_milestone.title), class: "btn edit-milestone-link btn-grouped" + = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues .row .col-md-6 - = render 'issues', title: "Open", issues: @dashboard_milestone.opened_issues + = render 'issues', title: "Open", issues: @milestone.opened_issues .col-md-6 - = render 'issues', title: "Closed", issues: @dashboard_milestone.closed_issues + = render 'issues', title: "Closed", issues: @milestone.closed_issues .tab-pane#tab-merge-requests .row .col-md-6 - = render 'merge_requests', title: "Open", merge_requests: @dashboard_milestone.opened_merge_requests + = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests .col-md-6 - = render 'merge_requests', title: "Closed", merge_requests: @dashboard_milestone.closed_merge_requests + = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests .tab-pane#tab-participants %ul.bordered-list - - @dashboard_milestone.participants.each do |user| + - @milestone.participants.each do |user| %li = link_to user, title: user.name, class: "darken" do = image_tag avatar_icon(user, 32), class: "avatar s32" diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index ded4f3713f..ffd7dd3fc0 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -14,10 +14,10 @@ group are listed here. .milestones %ul.content-list - - if @group_milestones.blank? + - if @milestones.blank? %li .nothing-here-block No milestones to show - else - - @group_milestones.each do |milestone| + - @milestones.each do |milestone| = render 'milestone', milestone: milestone - = paginate @group_milestones, theme: "gitlab" + = paginate @milestones, theme: "gitlab" diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 287f89a707..4c490d8ccb 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -5,7 +5,7 @@ This will create milestone in every selected project %hr -= form_for @group_milestone, as: :milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f| += form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f| .row .col-md-6 .form-group diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index a92ad5d751..e609abca08 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -1,22 +1,22 @@ -- page_title @group_milestone.title, "Milestones" +- page_title @milestone.title, "Milestones" = render "header_title" %h4.page-title - .issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } - - if @group_milestone.closed? + .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } + - if @milestone.closed? Closed - else Open - Milestone #{@group_milestone.title} + Milestone #{@milestone.title} .pull-right - if can?(current_user, :admin_group, @group) - - if @group_milestone.active? - = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" + - if @milestone.active? + = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - else - = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" + = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" %hr -- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active? +- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. @@ -30,7 +30,7 @@ %th Open issues %th State %th Due date - - @group_milestone.milestones.each do |milestone| + - @milestone.milestones.each do |milestone| %tr %td = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) @@ -47,46 +47,46 @@ .context %p.lead Progress: - #{@group_milestone.closed_items_count} closed + #{@milestone.closed_items_count} closed – - #{@group_milestone.open_items_count} open - = milestone_progress_bar(@group_milestone) + #{@milestone.open_items_count} open + = milestone_progress_bar(@milestone) %ul.nav.nav-tabs %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues - %span.badge= @group_milestone.issue_count + %span.badge= @milestone.issue_count %li = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do Merge Requests - %span.badge= @group_milestone.merge_requests_count + %span.badge= @milestone.merge_requests_count %li = link_to '#tab-participants', 'data-toggle' => 'tab' do Participants - %span.badge= @group_milestone.participants.count + %span.badge= @milestone.participants.count .pull-right - = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @group_milestone.title), class: "btn edit-milestone-link btn-grouped" + = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues .row .col-md-6 - = render 'issues', title: "Open", issues: @group_milestone.opened_issues + = render 'issues', title: "Open", issues: @milestone.opened_issues .col-md-6 - = render 'issues', title: "Closed", issues: @group_milestone.closed_issues + = render 'issues', title: "Closed", issues: @milestone.closed_issues .tab-pane#tab-merge-requests .row .col-md-6 - = render 'merge_requests', title: "Open", merge_requests: @group_milestone.opened_merge_requests + = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests .col-md-6 - = render 'merge_requests', title: "Closed", merge_requests: @group_milestone.closed_merge_requests + = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests .tab-pane#tab-participants %ul.bordered-list - - @group_milestone.participants.each do |user| + - @milestone.participants.each do |user| %li = link_to user, title: user.name, class: "darken" do = image_tag avatar_icon(user, 32), class: "avatar s32" From c79d801bf58c58ec21e64cb782176d6dc879a60f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 19:31:02 +0100 Subject: [PATCH 08/39] Fix a bug when milestone/label filter was empty for dashboard issues page Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/controllers/dashboard_controller.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9806666833..dd4306ed0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,6 +42,7 @@ v 8.2.0 (unreleased) - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) - Relative links from a repositories README.md now link to the default branch - Fix trailing whitespace issue in merge request/issue title + - Fix bug when milestone/label filter was empty for dashboard issues page v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 4ebb3d7276..b2c1fa4230 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,5 +1,6 @@ class DashboardController < Dashboard::ApplicationController before_action :event_filter, only: :activity + before_action :projects, only: [:issues, :merge_requests] respond_to :html @@ -47,4 +48,8 @@ class DashboardController < Dashboard::ApplicationController @events = @event_filter.apply_filter(@events).with_associations @events = @events.limit(20).offset(params[:offset] || 0) end + + def projects + @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived + end end From 98d6d491b5187945b2d86db20af411240716980a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 14:38:34 +0100 Subject: [PATCH 09/39] Move global milestone specs Signed-off-by: Dmitriy Zaporozhets --- app/services/milestones/collection_service.rb | 26 ------- spec/models/global_milestone_spec.rb | 65 +++++++++++++++++ .../services/milestones/group_service_spec.rb | 70 ------------------- 3 files changed, 65 insertions(+), 96 deletions(-) delete mode 100644 app/services/milestones/collection_service.rb create mode 100644 spec/models/global_milestone_spec.rb delete mode 100644 spec/services/milestones/group_service_spec.rb diff --git a/app/services/milestones/collection_service.rb b/app/services/milestones/collection_service.rb deleted file mode 100644 index 2eefec99e7..0000000000 --- a/app/services/milestones/collection_service.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Milestones - class CollectionService < Milestones::BaseService - def initialize(project_milestones) - @project_milestones = project_milestones.group_by(&:title) - end - - def execute - build(@project_milestones) - end - - def milestone(title) - if title - group_milestone = @project_milestones[title].group_by(&:title) - build(group_milestone).first - else - nil - end - end - - private - - def build(milestone) - milestone.map{ |title, milestones| GroupMilestone.new(title, milestones) } - end - end -end diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb new file mode 100644 index 0000000000..6eeff30b20 --- /dev/null +++ b/spec/models/global_milestone_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe GlobalMilestone do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:group) { create(:group) } + let(:project1) { create(:project, group: group) } + let(:project2) { create(:project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } + let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } + let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } + let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } + let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } + let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } + + describe :build_collection do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + milestone2_project1, + milestone2_project2, + milestone2_project3 + ] + + @global_milestones = GlobalMilestone.build_collection(milestones) + end + + it 'should have all project milestones' do + expect(@global_milestones.count).to eq(2) + end + + it 'should have all project milestones titles' do + expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123']) + end + + it 'should have all project milestones' do + expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) + end + end + + describe :initialize do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + ] + + @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones) + end + + it 'should have exactly one group milestone' do + expect(@global_milestone.title).to eq('Milestone v1.2') + end + + it 'should have all project milestones with the same title' do + expect(@global_milestone.milestones.count).to eq(3) + end + end +end diff --git a/spec/services/milestones/group_service_spec.rb b/spec/services/milestones/group_service_spec.rb deleted file mode 100644 index 74eb0f99e0..0000000000 --- a/spec/services/milestones/group_service_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'spec_helper' - -describe Milestones::GroupService do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:group) { create(:group) } - let(:project1) { create(:project, group: group) } - let(:project2) { create(:project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } - let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } - let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } - let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } - let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } - let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } - let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } - - describe 'execute' do - context 'with valid projects' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).execute - end - - it 'should have all project milestones' do - expect(@group_milestones.count).to eq(2) - end - - it 'should have all project milestones titles' do - expect(@group_milestones.map { |group_milestone| group_milestone.title }).to match_array(['Milestone v1.2', 'VD-123']) - end - - it 'should have all project milestones' do - expect(@group_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) - end - end - end - - describe 'milestone' do - context 'with valid title' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).milestone('Milestone v1.2') - end - - it 'should have exactly one group milestone' do - expect(@group_milestones.title).to eq('Milestone v1.2') - end - - it 'should have all project milestones with the same title' do - expect(@group_milestones.milestones.count).to eq(3) - end - end - end -end From 8a03cb87442a42c8fc1630ab356dfd35dbb476f7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 14:39:19 +0100 Subject: [PATCH 10/39] Lets add more tests to Milestones services Signed-off-by: Dmitriy Zaporozhets --- app/finders/milestones_finder.rb | 1 - .../services/milestones/close_service_spec.rb | 28 +++++++++++++++++++ .../milestones/create_service_spec.rb | 24 ++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 spec/services/milestones/close_service_spec.rb create mode 100644 spec/services/milestones/create_service_spec.rb diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index 71f207ca03..b704e87890 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -10,4 +10,3 @@ class MilestonesFinder end end end - diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb new file mode 100644 index 0000000000..034c0f22e1 --- /dev/null +++ b/spec/services/milestones/close_service_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Milestones::CloseService do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } + + before do + project.team << [user, :master] + end + + describe :execute do + before do + Milestones::CloseService.new(project, user, {}).execute(milestone) + end + + it { expect(milestone).to be_valid } + it { expect(milestone).to be_closed } + + describe :event do + let(:event) { Event.first } + + it { expect(event.milestone).to be_truthy } + it { expect(event.target).to eq(milestone) } + it { expect(event.action_name).to eq('closed') } + end + end +end diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb new file mode 100644 index 0000000000..757c9a226d --- /dev/null +++ b/spec/services/milestones/create_service_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Milestones::CreateService do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + describe :execute do + context "valid params" do + before do + project.team << [user, :master] + + opts = { + title: 'v2.1.9', + description: 'Patch release to fix security issue' + } + + @milestone = Milestones::CreateService.new(project, user, opts).execute + end + + it { expect(@milestone).to be_valid } + it { expect(@milestone.title).to eq('v2.1.9') } + end + end +end From 8630d476e419ba524cb5f23e25772bcf2223195d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 14:45:20 +0100 Subject: [PATCH 11/39] Add header and page title to new milestone page Signed-off-by: Dmitriy Zaporozhets --- app/views/groups/milestones/new.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 4c490d8ccb..d4284af8a0 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -1,3 +1,6 @@ +- page_title "Milestones" +- header_title group_title(@group, "Milestones", group_milestones_path(@group)) + %h3.page-title New Milestone From f16f315115e732493d976a9c866af351253ecf61 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 15:07:16 +0100 Subject: [PATCH 12/39] Few changes to Group Milestone feature: * Group attachments are not supported so I removed attach file link * Added changelog item * Add markdown hint to project milestone form Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js.coffee | 2 ++ app/views/groups/milestones/new.html.haml | 3 +-- app/views/projects/milestones/_form.html.haml | 6 ++---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dd4306ed0c..27ce7f3668 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.2.0 (unreleased) - Relative links from a repositories README.md now link to the default branch - Fix trailing whitespace issue in merge request/issue title - Fix bug when milestone/label filter was empty for dashboard issues page + - Add ability to create milestone in group projects from single form v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 951173af5d..4059fc39c6 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -28,6 +28,8 @@ class Dispatcher when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() new DropzoneInput($('.milestone-form')) + when 'groups:milestones:new' + new ZenMode() when 'projects:compare:show' new Diff() when 'projects:issues:new','projects:issues:edit' diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index d4284af8a0..800bac4ef0 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -21,7 +21,6 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' - = render 'projects/notes/hints' .clearfix .error-alert .form-group @@ -43,7 +42,7 @@ :javascript - $( ".datepicker" ).datepicker({ + $(".datepicker").datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 255ddab479..24879b19d2 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -23,9 +23,7 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' - .hint - .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + = render 'projects/notes/hints' .clearfix .error-alert .col-md-6 @@ -45,7 +43,7 @@ :javascript - $( ".datepicker" ).datepicker({ + $(".datepicker").datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); From f445d3d1f317d8addbe60cf31fa15ca592ba0d19 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 16 Nov 2015 15:36:14 +0100 Subject: [PATCH 13/39] unique is for indexes. --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index a8e8dfe6bb..4870fe3fc4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -423,7 +423,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree create_table "lfs_objects", force: true do |t| - t.string "oid", null: false, unique: true + t.string "oid", null: false t.integer "size", null: false t.datetime "created_at" t.datetime "updated_at" From 78d542fcd811b6e18209fb97f126fd6291bd411b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 15:56:50 +0100 Subject: [PATCH 14/39] Add milestones documentation to workflow Signed-off-by: Dmitriy Zaporozhets --- doc/workflow/README.md | 1 + doc/workflow/milestones.md | 13 +++++++++++++ doc/workflow/milestones/form.png | Bin 0 -> 88591 bytes doc/workflow/milestones/group_form.png | Bin 0 -> 77087 bytes 4 files changed, 14 insertions(+) create mode 100644 doc/workflow/milestones.md create mode 100644 doc/workflow/milestones/form.png create mode 100644 doc/workflow/milestones/group_form.png diff --git a/doc/workflow/README.md b/doc/workflow/README.md index a2653704c3..c1a4f64981 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -14,5 +14,6 @@ - [Protected branches](protected_branches.md) - [Web Editor](web_editor.md) - [Releases](releases.md) +- [Milestones](milestones.md) - [Merge Requests](merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md new file mode 100644 index 0000000000..1cd1f0f2fc --- /dev/null +++ b/doc/workflow/milestones.md @@ -0,0 +1,13 @@ +# Milestones + +Milestone allows you to group issues and set due date for it. +Milestone is created per project. + +![milestone form](milestones/form.png) + +## Groups and milestones + +You can create milestone with single form for several projects that belongs to the same group. +On the group milestones page you will be able to see this milestones grouped together by name. + +![group milestone form](milestones/group_form.png) diff --git a/doc/workflow/milestones/form.png b/doc/workflow/milestones/form.png new file mode 100644 index 0000000000000000000000000000000000000000..de44c1ffc1a44c21e7d6eff77ff8752d84b6ea8f GIT binary patch literal 88591 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

7chsg28F&v6Qs`*;tr3Ve?{d?nB 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/milestones/group_form.png b/doc/workflow/milestones/group_form.png new file mode 100644 index 0000000000000000000000000000000000000000..38862dcca6895c28a09c6173fc8a2d7b1552b528 GIT binary patch literal 77087 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

hub?d7kLqUFo_P#LClu2 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

AZ-_IvsYe~ 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 IAzhvEA5O_fNB{r; literal 0 HcmV?d00001 From 84c71e5d827f1a0b225b7f6e9eeabe00a9c3eadd Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 16 Nov 2015 15:59:15 +0100 Subject: [PATCH 15/39] Add unique to oid index for lfs object. --- db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb | 7 +++++++ db/schema.rb | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb diff --git a/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb b/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb new file mode 100644 index 0000000000..41b93da0a8 --- /dev/null +++ b/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb @@ -0,0 +1,7 @@ +class AddUniqueForLfsOidIndex < ActiveRecord::Migration + def change + remove_index :lfs_objects, :oid + remove_index :lfs_objects, [:oid, :size] + add_index :lfs_objects, :oid, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 4870fe3fc4..aa76cef9fe 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: 20151114113410) do +ActiveRecord::Schema.define(version: 20151116144118) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -430,8 +430,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.string "file" end - add_index "lfs_objects", ["oid", "size"], name: "index_lfs_objects_on_oid_and_size", using: :btree - add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", using: :btree + add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree create_table "lfs_objects_projects", force: true do |t| t.integer "lfs_object_id", null: false From 929ab909c88e9ac5d87acacb376a39dcfa6a639c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 16:14:19 +0100 Subject: [PATCH 16/39] Group masters should be able to create/close milestones Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups/milestones_controller.rb | 2 +- app/models/ability.rb | 1 + app/views/groups/milestones/_milestone.html.haml | 2 +- app/views/groups/milestones/index.html.haml | 9 +++++---- app/views/groups/milestones/show.html.haml | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 6833a550c9..10233222ee 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -38,7 +38,7 @@ class Groups::MilestonesController < Groups::ApplicationController private def authorize_group_milestone! - return render_404 unless can?(current_user, :admin_group, group) + return render_404 unless can?(current_user, :admin_milestones, group) end def milestone_params diff --git a/app/models/ability.rb b/app/models/ability.rb index 5ae28d5133..d01b3ae6f0 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -233,6 +233,7 @@ class Ability if group.has_master?(user) || group.has_owner?(user) || user.admin? rules.push(*[ :create_projects, + :admin_milestones ]) end diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml index 41dffdd2fb..a20bf75bc3 100644 --- a/app/views/groups/milestones/_milestone.html.haml +++ b/app/views/groups/milestones/_milestone.html.haml @@ -22,7 +22,7 @@ %span.label.label-gray = milestone.project.name .col-sm-6 - - if can?(current_user, :admin_group, @group) + - if can?(current_user, :admin_milestones, @group) - if milestone.closed? = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen" - else diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index ffd7dd3fc0..84ec77c618 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -3,10 +3,11 @@ = render 'shared/milestones_filter' .gray-content-block - .pull-right - %span.pull-right.hidden-xs - = link_to new_group_milestone_path(@group), class: "btn btn-new" do - New Milestone + - if can?(current_user, :admin_milestones, @group) + .pull-right + %span.pull-right.hidden-xs + = link_to new_group_milestone_path(@group), class: "btn btn-new" do + New Milestone .oneline Only milestones from diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index e609abca08..716e93f558 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -9,7 +9,7 @@ Open Milestone #{@milestone.title} .pull-right - - if can?(current_user, :admin_group, @group) + - if can?(current_user, :admin_milestones, @group) - if @milestone.active? = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - else From 32f1a7196817b1073327c607905ee40b9140e6df Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 17:24:14 +0100 Subject: [PATCH 17/39] Fix removing avatar for group Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups/avatars_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb index f390705dc6..76c87366ba 100644 --- a/app/controllers/groups/avatars_controller.rb +++ b/app/controllers/groups/avatars_controller.rb @@ -1,4 +1,4 @@ -class Groups::AvatarsController < ApplicationController +class Groups::AvatarsController < Groups::ApplicationController def destroy @group.remove_avatar! @group.save From b22feb9fc7e77fd7c2920030d7833a1e8f38a8c3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 16 Nov 2015 11:47:31 -0500 Subject: [PATCH 18/39] Version 8.2.0.rc1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a2264f05f5..88b48676b1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.1.0.pre +8.2.0.rc1 \ No newline at end of file From c8e53d4467e1e8cce4db04aafba00d55f014e283 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 15 Nov 2015 15:30:05 -0500 Subject: [PATCH 19/39] Revert "Merge pull request #9820 from huacnlee/avoid-render-form-in-notes-list" This reverts commit 63144cd062f6d259f1f30b6e06eb92a16caa8dec, reversing changes made to 8ab5df9d872414b2cca3ebd16d57b89e2f19e06a. --- app/assets/javascripts/notes.js.coffee | 13 +++++++------ app/controllers/projects/notes_controller.rb | 7 +------ app/views/projects/notes/_note.html.haml | 5 ++++- app/views/projects/notes/_notes_with_form.html.haml | 2 +- app/views/projects/notes/edit.js.erb | 2 -- config/routes.rb | 2 +- features/steps/project/issues/issues.rb | 2 +- spec/features/notes_on_merge_requests_spec.rb | 6 ++++++ spec/features/task_lists_spec.rb | 3 +++ 9 files changed, 24 insertions(+), 18 deletions(-) delete mode 100644 app/views/projects/notes/edit.js.erb diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index b0682f1684..ea75c656bc 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -29,6 +29,7 @@ class @Notes $(document).on "ajax:success", "form.edit_note", @updateNote # Edit note link + $(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".note-edit-cancel", @cancelEdit # Reopen and close actions for Issue/MR combined with note form submit @@ -66,6 +67,7 @@ class @Notes $(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", "form.edit_note" + $(document).off "click", ".js-note-edit" $(document).off "click", ".note-edit-cancel" $(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-attachment-delete" @@ -285,14 +287,13 @@ class @Notes Adds a hidden div with the original content of the note to fill the edit note form with if the user cancels ### - showEditForm: (note, formHTML) -> - nodeText = note.find(".note-text"); - nodeText.hide() - note.find('.note-edit-form').remove() - nodeText.after(formHTML) + showEditForm: (e) -> + e.preventDefault() + note = $(this).closest(".note") note.find(".note-body > .note-text").hide() note.find(".note-header").hide() - form = note.find(".note-edit-form") + base_form = note.find(".note-edit-form") + form = base_form.clone().insertAfter(base_form) form.addClass('current-note-edit-form gfm-form') form.find('.div-dropzone').remove() diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 0c98e2f1bf..41cd08c93c 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] - before_action :find_current_user_notes, except: [:destroy, :edit, :delete_attachment] + before_action :find_current_user_notes, except: [:destroy, :delete_attachment] def index current_fetched_at = Time.now.to_i @@ -29,11 +29,6 @@ class Projects::NotesController < Projects::ApplicationController end end - def edit - @note = note - render layout: false - end - def update @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 8a3292f7da..8880830198 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -7,7 +7,7 @@ .note-header - if note_editable?(note) .note-actions - = link_to edit_namespace_project_note_path(note.project.namespace, note.project, note), title: 'Edit comment', remote: true, class: 'js-note-edit' do + = link_to '#', title: 'Edit comment', class: 'js-note-edit' do = icon('pencil-square-o') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do @@ -59,6 +59,9 @@ .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) + - unless note.system? + -# System notes can't be edited + = render 'projects/notes/edit_form', note: note - if note.attachment.url .note-attachment diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 91cefa6d14..04222b8f7c 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -7,4 +7,4 @@ = render "projects/notes/form", view: params[:view] :javascript - window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") + new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") diff --git a/app/views/projects/notes/edit.js.erb b/app/views/projects/notes/edit.js.erb deleted file mode 100644 index 2599bad5d6..0000000000 --- a/app/views/projects/notes/edit.js.erb +++ /dev/null @@ -1,2 +0,0 @@ -$note = $('.note-row-<%= @note.id %>:visible'); -_notes.showEditForm($note, '<%= escape_javascript(render('edit_form', note: @note)) %>'); diff --git a/config/routes.rb b/config/routes.rb index bd85f4e3c6..696136d964 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -660,7 +660,7 @@ Gitlab::Application.routes.draw do end end - resources :notes, constraints: { id: /\d+/ } do + resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do member do delete :delete_attachment end diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index be134b9c2b..af2da41bad 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -219,7 +219,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'The code block should be unchanged' do - expect(page).to have_content("Command [1]: /usr/local/bin/git , see [text](doc/text)") + expect(page).to have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```") end step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 16d5a03e88..d7cb3b2e86 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -65,6 +65,12 @@ describe 'Comments', feature: true do end describe 'when editing a note', js: true do + it 'should contain the hidden edit form' do + page.within("#note_#{note.id}") do + is_expected.to have_css('.note-edit-form', visible: false) + end + end + describe 'editing the note' do before do find('.note').hover diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 6cbe685a93..fca3c77fc6 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -51,6 +51,7 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end @@ -89,6 +90,7 @@ feature 'Task Lists', feature: true do expect(page).to have_selector('.note .js-task-list-container') expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') + expect(page).to have_selector('.note .js-task-list-container .js-task-list-field') end it 'is only editable by author' do @@ -125,6 +127,7 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end From ccb0c40c54d913fe140231c88f4adcd2d41c5b87 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 15 Nov 2015 14:32:28 -0500 Subject: [PATCH 20/39] Make ProjectWiki touch Project#last_activity_at after wiki actions Closes #3026 --- app/models/project_wiki.rb | 10 ++++++++++ spec/models/project_wiki_spec.rb | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 231973fa54..b5fec38378 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -86,6 +86,8 @@ class ProjectWiki commit = commit_details(:created, message, title) wiki.write_page(title, format, content, commit) + + update_project_activity rescue Gollum::DuplicatePageError => e @error_message = "Duplicate page: #{e.message}" return false @@ -95,10 +97,14 @@ class ProjectWiki commit = commit_details(:updated, message, page.title) wiki.update_page(page, page.name, format, content, commit) + + update_project_activity end def delete_page(page, message = nil) wiki.delete_page(page, commit_details(:deleted, message, page.title)) + + update_project_activity end def page_title_and_dir(title) @@ -146,4 +152,8 @@ class ProjectWiki def path_to_repo @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") end + + def update_project_activity + @project.touch(:last_activity_at) + end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 9f6cdeeaa9..3b88914444 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -184,6 +184,12 @@ describe ProjectWiki do subject.create_page("test page", "some content", :markdown, "commit message") expect(subject.pages.first.page.version.message).to eq("commit message") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.create_page('Test Page', 'This is content') + end end describe "#update_page" do @@ -205,6 +211,12 @@ describe ProjectWiki do it "sets the correct commit message" do expect(@page.version.message).to eq("updated page") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.update_page(@gollum_page, 'Yet more content', :markdown, 'Updated page again') + end end describe "#delete_page" do @@ -217,6 +229,12 @@ describe ProjectWiki do subject.delete_page(@page) expect(subject.pages.count).to eq(0) end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.delete_page(@page) + end end private From b093f50986b6dcd0e4caf33d3c96831155e71db8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 19:55:58 +0100 Subject: [PATCH 21/39] Some code and doc improvements Signed-off-by: Dmitriy Zaporozhets --- app/models/global_milestone.rb | 4 ++++ app/views/dashboard/milestones/show.html.haml | 2 +- app/views/groups/milestones/show.html.haml | 2 +- doc/workflow/milestones.md | 8 ++++---- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index f96e9d41c9..1321ccd963 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -94,4 +94,8 @@ class GlobalMilestone def closed_merge_requests merge_requests.values_at("closed", "merged", "locked").compact.flatten end + + def complete? + total_items_count == closed_items_count + end end diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 580db613ed..83077a398b 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -8,7 +8,7 @@ Milestone #{@milestone.title} %hr -- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? +- if @milestone.complete? && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 716e93f558..d161259e4a 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -16,7 +16,7 @@ = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" %hr -- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? +- if @milestone.complete? && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md index 1cd1f0f2fc..dff36899ae 100644 --- a/doc/workflow/milestones.md +++ b/doc/workflow/milestones.md @@ -1,13 +1,13 @@ # Milestones -Milestone allows you to group issues and set due date for it. -Milestone is created per project. +Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date. +A common use is keeping track of an upcoming software version. Milestones are created per-project. ![milestone form](milestones/form.png) ## Groups and milestones -You can create milestone with single form for several projects that belongs to the same group. -On the group milestones page you will be able to see this milestones grouped together by name. +You can create a milestone for several projects in the same group simultaneously. +On the group's milestones page, you will be able to see the status of that milestone across all of the selected projects. ![group milestone form](milestones/group_form.png) From 3300db70ff53699732672824859186cd083623fa Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Mon, 16 Nov 2015 02:01:26 -0800 Subject: [PATCH 22/39] Rewrite HTTP links to force TLS, where possible --- CONTRIBUTING.md | 8 ++++---- app/views/admin/users/_profile.html.haml | 4 ++-- app/views/users/show.html.haml | 4 ++-- doc/ci/docker/using_docker_build.md | 4 ++-- doc/ci/quick_start/README.md | 6 +++--- doc/customization/libravatar.md | 6 +++--- doc/development/architecture.md | 2 +- doc/hooks/custom_hooks.md | 2 +- doc/install/database_mysql.md | 2 +- doc/install/installation.md | 4 ++-- doc/integration/ldap.md | 4 ++-- doc/legal/corporate_contributor_license_agreement.md | 2 +- .../individual_contributor_license_agreement.md | 2 +- doc/markdown/markdown.md | 12 ++++++------ doc/operations/unicorn.md | 4 ++-- doc/release/security.md | 4 ++-- doc/ssh/README.md | 2 +- doc/update/6.x-or-7.x-to-7.14.md | 2 +- doc/workflow/gitlab_flow.md | 12 ++++++------ doc/workflow/importing/migrating_from_svn.md | 4 ++-- 20 files changed, 45 insertions(+), 45 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f79ff413a..5d85c9f3fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Closing policy for issues and merge requests @@ -35,7 +35,7 @@ The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple. -Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. +Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](https://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. ### Issue tracker guidelines @@ -72,7 +72,7 @@ If you can, please submit a merge request with the fix or improvements including 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code 1. Add your changes to the [CHANGELOG](CHANGELOG) 1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message -1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) +1. If you have multiple commits please combine them into one commit by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. Push the commit to your fork 1. Submit a merge request (MR) to the master branch 1. The MR title should describe the change you want to make @@ -181,4 +181,4 @@ This code of conduct applies both within project spaces and in public spaces whe Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com -This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) diff --git a/app/views/admin/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml index 90d9980c85..7d11edc79e 100644 --- a/app/views/admin/users/_profile.html.haml +++ b/app/views/admin/users/_profile.html.haml @@ -16,11 +16,11 @@ - unless user.linkedin.blank? %li %span.light LinkedIn: - %strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}" + %strong= link_to user.linkedin, "https://www.linkedin.com/in/#{user.linkedin}" - unless user.twitter.blank? %li %span.light Twitter: - %strong= link_to user.twitter, "http://www.twitter.com/#{user.twitter}" + %strong= link_to user.twitter, "https://twitter.com/#{user.twitter}" - unless user.website_url.blank? %li %span.light Website: diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 3099241218..d5a92cb816 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -32,11 +32,11 @@ = icon('skype') - unless @user.linkedin.blank? .profile-link-holder - = link_to "http://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do + = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do = icon('linkedin-square') - unless @user.twitter.blank? .profile-link-holder - = link_to "http://www.twitter.com/#{@user.twitter}", title: "Twitter" do + = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do = icon('twitter-square') - unless @user.website_url.blank? .profile-link-holder diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 5af27470d2..4b1788a9af 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -35,7 +35,7 @@ GitLab Runner then executes build scripts as `gitlab-runner` user. ```bash $ sudo gitlab-runner register -n \ - --url http://gitlab.com/ci \ + --url https://gitlab.com/ci \ --token RUNNER_TOKEN \ --executor shell --description "My Runner" @@ -84,7 +84,7 @@ In order to do that follow the steps: ```bash $ sudo gitlab-runner register -n \ - --url http://gitlab.com/ci \ + --url https://gitlab.com/ci \ --token RUNNER_TOKEN \ --executor docker \ --description "My Docker Runner" \ diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index a87a1f806f..d69064a91f 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -6,7 +6,7 @@ To start building projects with GitLab CI a few steps needs to be done. First you need to have a working GitLab and GitLab CI instance. -You can omit this step if you use [GitLab.com](http://GitLab.com/). +You can omit this step if you use [GitLab.com](https://GitLab.com/). ## 2. Create repository on GitLab @@ -16,7 +16,7 @@ Push your application to that repository. ## 3. Add project to CI The next part is to login to GitLab CI. -Point your browser to the URL you have set GitLab or use [gitlab.com/ci](http://gitlab.com/ci/). +Point your browser to the URL you have set GitLab or use [gitlab.com/ci](https://gitlab.com/ci/). On the first screen you will see a list of GitLab's projects that you have access to: @@ -97,7 +97,7 @@ If you do it correctly your runner should be shown under **Runners activated for ### Shared runners -If you use [gitlab.com/ci](http://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc. +If you use [gitlab.com/ci](https://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc. These are special virtual machines that are run on GitLab's infrastructure that can build any project. To enable **Shared runners** you have to go to **Runners** and click **Enable shared runners** for this project. diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md index 54c1780c3a..bd2c242afc 100644 --- a/doc/customization/libravatar.md +++ b/doc/customization/libravatar.md @@ -2,7 +2,7 @@ GitLab by default supports [Gravatar](https://gravatar.com) avatar service. Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is -[heavily based on gravatar](http://wiki.libravatar.org/api/). +[heavily based on gravatar](https://wiki.libravatar.org/api/). This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server. @@ -31,7 +31,7 @@ the configuration options as follows: ## Self-hosted -If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration +If you are [running your own libravatar service](https://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration but the important part is to provide the same placeholders so GitLab can parse the URL correctly. For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is @@ -63,7 +63,7 @@ Run `sudo gitlab-ctl reconfigure` for changes to take effect. ## Default URL for missing images -[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service. +[Libravatar supports different sets](https://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service. In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set. For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"` diff --git a/doc/development/architecture.md b/doc/development/architecture.md index c00d290371..6101a71a8d 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -146,7 +146,7 @@ nginx Apache httpd -- [Explanation of Apache logs](http://httpd.apache.org/docs/2.2/logs.html). +- [Explanation of Apache logs](https://httpd.apache.org/docs/2.2/logs.html). - `/var/log/apache2/` contains error and output logs (on Ubuntu). - `/var/log/httpd/` contains error and output logs (on RHEL). diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index 548c484bc0..0f2665a3bf 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -7,7 +7,7 @@ Please explore webhooks as an option if you do not have filesystem access. For a 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) +[Git SCM Server-Side Hooks](https://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 diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index c565e90da2..513ad69ec2 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -2,7 +2,7 @@ ## Note -We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](http://bugs.mysql.com/bug.php?id=65830) that [suggested](http://bugs.mysql.com/bug.php?id=50909) [fixes](http://bugs.mysql.com/bug.php?id=65830) [have](http://bugs.mysql.com/bug.php?id=63164). +We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164). ## MySQL diff --git a/doc/install/installation.md b/doc/install/installation.md index 8028e51dbc..193db10e4d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -106,7 +106,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](https://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 @@ -298,7 +298,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Install Gems -**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](http://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2. +**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](https://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2. # For PostgreSQL (note, the option says "without ... mysql") sudo -u git -H bundle install --deployment --without development test mysql aws kerberos diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index 9b7d8fa396..7e2920b886 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -71,7 +71,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server # Filter LDAP users # - # Format: RFC 4515 http://tools.ietf.org/search/rfc4515 + # Format: RFC 4515 https://tools.ietf.org/search/rfc4515 # Ex. (employeeType=developer) # # Note: GitLab does not support omniauth-ldap's custom filter syntax. @@ -145,7 +145,7 @@ If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `ema ## Using an LDAP filter to limit access to your GitLab server If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter. -The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). +The filter must comply with [RFC 4515](https://tools.ietf.org/search/rfc4515). ```ruby # For omnibus packages; new LDAP server syntax diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md index 13bf15fcf4..7b94506c29 100644 --- a/doc/legal/corporate_contributor_license_agreement.md +++ b/doc/legal/corporate_contributor_license_agreement.md @@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and 8. It is your responsibility to notify GitLab B.V. when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab B.V.. -This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. +This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md index 72b01433dd..f97c252fd7 100644 --- a/doc/legal/individual_contributor_license_agreement.md +++ b/doc/legal/individual_contributor_license_agreement.md @@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and 8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. -This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. +This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index ac3851f8c9..bc8e7d155e 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -43,7 +43,7 @@ You can also use other rich text files in GitLab. You might have to install a de ## Newlines -GFM honors the markdown specification in how [paragraphs and line breaks are handled](http://daringfireball.net/projects/markdown/syntax#p). +GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p). A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. Line-breaks, or softreturns, are rendered if you end a line with two or more spaces @@ -72,14 +72,14 @@ do_this_and_do_that_and_another_thing GFM will autolink almost any URL you copy and paste into your text. - * http://www.google.com + * https://www.google.com * https://google.com/ * ftp://ftp.us.debian.org/debian/ * smb://foo/bar/baz * irc://irc.freenode.net/gitlab * http://localhost:3000 -* http://www.google.com +* https://www.google.com * https://google.com/ * ftp://ftp.us.debian.org/debian/ * smb://foo/bar/baz @@ -390,7 +390,7 @@ There are two ways to create links, inline-style and reference-style. [arbitrary case-insensitive reference text]: https://www.mozilla.org [1]: http://slashdot.org - [link text itself]: http://www.reddit.com + [link text itself]: https://www.reddit.com [I'm an inline-style link](https://www.google.com) @@ -406,7 +406,7 @@ Some text to show that the reference links can follow later. [arbitrary case-insensitive reference text]: https://www.mozilla.org [1]: http://slashdot.org -[link text itself]: http://www.reddit.com +[link text itself]: https://www.reddit.com **Note** @@ -583,5 +583,5 @@ By including colons in the header row, you can align the text within that column ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). -- The [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. +- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. - [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md index 3998da01f0..bad61151bd 100644 --- a/doc/operations/unicorn.md +++ b/doc/operations/unicorn.md @@ -52,7 +52,7 @@ leak memory, probably because it does not handle user requests.) To make these memory leaks manageable, GitLab comes with the [unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This -gem [monkey-patches](http://en.wikipedia.org/wiki/Monkey_patch) the Unicorn +gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn workers to do a memory self-check after every 16 requests. If the memory of the Unicorn worker exceeds a pre-set limit then the worker process exits. The Unicorn master then automatically replaces the worker process. @@ -83,4 +83,4 @@ is a normal value for our current GitLab.com setup and traffic. The high frequency of Unicorn memory restarts on some GitLab sites can be a source of confusion for administrators. Usually they are a [red -herring](http://en.wikipedia.org/wiki/Red_herring). +herring](https://en.wikipedia.org/wiki/Red_herring). diff --git a/doc/release/security.md b/doc/release/security.md index 60bcfbb6da..b1a62b333e 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Release Procedure @@ -25,7 +25,7 @@ Please report suspected security vulnerabilities in private to Date: Tue, 17 Nov 2015 13:08:11 +0200 Subject: [PATCH 23/39] Fix md syntax in doc/api/commits.md --- doc/api/commits.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/commits.md b/doc/api/commits.md index 8e4a0ee1b8..93d62b751e 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -188,7 +188,7 @@ Parameters: "target_url": "http://jenkins/project/url", "description": "Jenkins success", "created_at": "2015-10-12T09:47:16.250Z", - "started_at": "2015-10-12T09:47:16.250Z"", + "started_at": "2015-10-12T09:47:16.250Z", "finished_at": "2015-10-12T09:47:16.262Z", "author": { "id": 1, @@ -228,7 +228,7 @@ POST /projects/:id/statuses/:sha "target_url": "http://jenkins/project/url", "description": "Jenkins success", "created_at": "2015-10-12T09:47:16.250Z", - "started_at": "2015-10-12T09:47:16.250Z"", + "started_at": "2015-10-12T09:47:16.250Z", "finished_at": "2015-10-12T09:47:16.262Z", "author": { "id": 1, From 3ae1dee475dbeb9bdf62ed139292cba09b6c98bb Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 16 Nov 2015 12:00:37 +0800 Subject: [PATCH 24/39] Avoid render edit_form when visitor can't edit them. Reverted #9820, github/task_list need a form, textarea for update. --- app/views/projects/notes/_note.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 8880830198..efa7dd01cc 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -59,8 +59,7 @@ .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) - - unless note.system? - -# System notes can't be edited + - if note_editable?(note) = render 'projects/notes/edit_form', note: note - if note.attachment.url From 4597f8ef209b832538c3aa8524c197ef505d625e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 10:44:01 -0500 Subject: [PATCH 25/39] Update monthly release template [ci skip] --- doc/release/monthly.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index d347f58ba0..c9ab87671d 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -54,21 +54,25 @@ template are explained below: - [ ] Update GitLab.com with RC1 - [ ] Create the regression issue in the CE issue tracker: - > This is a meta issue to index possible regressions in this monthly release - > and any patch versions. - > - > Please do not raise or discuss issues directly in this issue but link to - > issues that might warrant a patch release. If there is a Merge Request - > that fixes the issue, please link to that as well. - > - > Please only post one regression issue and/or merge request per comment. - > Comments will be updated by the release manager as they are addressed. + ``` + This is a meta issue to index possible regressions in this monthly release + and any patch versions. + + Please do not raise or discuss issues directly in this issue but link to + issues that might warrant a patch release. If there is a Merge Request + that fixes the issue, please link to that as well. + + Please only post one regression issue and/or merge request per comment. + Comments will be updated by the release manager as they are addressed. + ``` - [ ] Tweet about RC1 release: - > GitLab x.y.0.rc1 is available: https://packages.gitlab.com/gitlab/unstable - > Use at your own risk. Please link regressions issues from - > LINK_TO_REGRESSION_ISSUE + ``` + GitLab x.y.0.rc1 is available: https://packages.gitlab.com/gitlab/unstable + Use at your own risk. Please link regressions issues from + LINK_TO_REGRESSION_ISSUE + ``` ### Xth: (3 working days before the 22nd) From 85102d094f44741e6fbf53910df1bed363a020bb Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 11:02:11 -0500 Subject: [PATCH 26/39] Update CHANGELOG [ci skip] --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 3c22df7c9a..ca09d9c09f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ Please view this file on the master branch, on stable branches it's out of date. -v 8.2.0 (unreleased) +v 8.3.0 (unreleased) + +v 8.2.0 - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) From 1ec7cea59751b83c364116e9c20260537e14fd58 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 11:43:47 -0500 Subject: [PATCH 27/39] Merge branch 'param-view' into 'master' See merge request !1816 --- app/helpers/diff_helper.rb | 8 ++++++-- app/views/projects/commit/show.html.haml | 2 +- app/views/projects/diffs/_diffs.html.haml | 2 +- app/views/projects/diffs/_file.html.haml | 3 +-- app/views/projects/notes/_form.html.haml | 2 +- app/views/projects/notes/_notes_with_form.html.haml | 4 ++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index b889fb2897..f47d5c4c74 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -1,4 +1,8 @@ module DiffHelper + def diff_view + params[:view] == 'parallel' ? 'parallel' : 'inline' + end + def allowed_diff_size if diff_hard_limit_enabled? Commit::DIFF_HARD_LIMIT_FILES @@ -137,7 +141,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do + link_to url_for(params_copy), id: "inline-diff-btn", class: (diff_view == 'inline' ? 'btn active' : 'btn') do 'Inline' end end @@ -148,7 +152,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do + link_to url_for(params_copy), id: "parallel-diff-btn", class: (diff_view == 'parallel' ? 'btn active' : 'btn') do 'Side-by-side' end end diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 30a3973828..85e203cbe5 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -3,4 +3,4 @@ = render "commit_box" = render "ci_menu" if @ci_commit = render "projects/diffs/diffs", diffs: @diffs, project: @project -= render "projects/notes/notes_with_form", view: params[:view] += render "projects/notes/notes_with_form" diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index e46bf1ab1e..416fb4da07 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,4 +1,4 @@ -- if params[:view] == 'parallel' +- if diff_view == 'parallel' - fluid_layout true - diff_files = safe_diff_files(diffs) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 410ff6abb4..c745b4e69b 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -33,7 +33,7 @@ -# Skipp all non non-supported blobs - return unless blob.respond_to?('text?') - if blob.text? - - if params[:view] == 'parallel' + - if diff_view == 'parallel' = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - else = render "projects/diffs/text_file", diff_file: diff_file, index: i @@ -42,4 +42,3 @@ = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i - else .nothing-here-block No preview for this file type - diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 13dfa0a1bb..5dd84317e3 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,5 +1,5 @@ = form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f| - = hidden_field_tag :view, params[:view] + = hidden_field_tag :view, diff_view = hidden_field_tag :line_type = note_target_fields(@note) = f.hidden_field :commit_id diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 04222b8f7c..99c1b0fa43 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -4,7 +4,7 @@ .js-main-target-form - if can? current_user, :create_note, @project - = render "projects/notes/form", view: params[:view] + = render "projects/notes/form", view: diff_view :javascript - new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") + new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}") From 8a1cafeb9decf2738311ea45a15d8fef0489b7da Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 18:23:27 +0000 Subject: [PATCH 28/39] Merge branch 'james11/gitlab-ce-removable-group-owner' into 'master' Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master Replaces !1708. Fixes #1111. See merge request !1815 --- CHANGELOG | 1 + .../groups/group_members_controller.rb | 30 +++++----- .../projects/project_members_controller.rb | 34 ++++++----- app/models/ability.rb | 58 +++++++++++++------ app/models/group.rb | 7 +-- app/models/member.rb | 38 +++++++++--- app/models/project.rb | 2 +- .../group_members/_group_member.html.haml | 6 +- .../groups/group_members/index.html.haml | 6 +- .../project_members/_project_member.html.haml | 11 ++-- .../projects/project_members/_team.html.haml | 4 +- .../projects/project_members/update.js.haml | 3 +- features/groups.feature | 8 +++ features/steps/groups.rb | 18 ++++++ 14 files changed, 149 insertions(+), 77 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ca09d9c09f..cc9fcf1992 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,7 @@ v 8.2.0 - Fix trailing whitespace issue in merge request/issue title - Fix bug when milestone/label filter was empty for dashboard issues page - Add ability to create milestone in group projects from single form + - Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez) v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index b25957a06e..0e902c4bb4 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -3,8 +3,7 @@ class Groups::GroupMembersController < Groups::ApplicationController # Authorize before_action :authorize_read_group! - before_action :authorize_admin_group!, except: [:index, :leave] - before_action :authorize_admin_group_member!, only: [:create, :resend_invite] + before_action :authorize_admin_group_member!, except: [:index, :leave] def index @project = @group.projects.find(params[:project_id]) if params[:project_id] @@ -17,7 +16,8 @@ class Groups::GroupMembersController < Groups::ApplicationController end @members = @members.order('access_level DESC').page(params[:page]).per(50) - @group_member = GroupMember.new + + @group_member = @group.group_members.new end def create @@ -27,24 +27,23 @@ class Groups::GroupMembersController < Groups::ApplicationController end def update - @member = @group.group_members.find(params[:id]) + @group_member = @group.group_members.find(params[:id]) - return render_403 unless can?(current_user, :update_group_member, @member) + return render_403 unless can?(current_user, :update_group_member, @group_member) - @member.update_attributes(member_params) + @group_member.update_attributes(member_params) end def destroy @group_member = @group.group_members.find(params[:id]) - if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner. - @group_member.destroy - respond_to do |format| - format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } - format.js { render nothing: true } - end - else - return render_403 + return render_403 unless can?(current_user, :destroy_group_member, @group_member) + + @group_member.destroy + + respond_to do |format| + format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } + format.js { render nothing: true } end end @@ -63,10 +62,11 @@ class Groups::GroupMembersController < Groups::ApplicationController end def leave - @group_member = @group.group_members.where(user_id: current_user.id).first + @group_member = @group.group_members.find_by(user_id: current_user) if can?(current_user, :destroy_group_member, @group_member) @group_member.destroy + redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.") else if @group.last_owner?(current_user) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 9de5269cd2..07eb94e4f4 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,6 +1,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize - before_action :authorize_admin_project!, except: :leave + before_action :authorize_admin_project_member!, except: :leave def index @project_members = @project.project_members @@ -29,10 +29,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_member = @project.project_members.new end - def new - @project_member = @project.project_members.new - end - def create @project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user) @@ -41,11 +37,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController def update @project_member = @project.project_members.find(params[:id]) + + return render_403 unless can?(current_user, :update_project_member, @project_member) + @project_member.update_attributes(member_params) end def destroy @project_member = @project.project_members.find(params[:id]) + + return render_403 unless can?(current_user, :destroy_project_member, @project_member) + @project_member.destroy respond_to do |format| @@ -71,16 +73,22 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def leave - if @project.namespace == current_user.namespace - message = 'You can not leave your own project. Transfer or delete the project.' - return redirect_back_or_default(default: { action: 'index' }, options: { alert: message }) - end + @project_member = @project.project_members.find_by(user_id: current_user) - @project.project_members.find_by(user_id: current_user).destroy + if can?(current_user, :destroy_project_member, @project_member) + @project_member.destroy - respond_to do |format| - format.html { redirect_to dashboard_projects_path } - format.js { render nothing: true } + respond_to do |format| + format.html { redirect_to dashboard_projects_path, notice: "You left the project." } + format.js { render nothing: true } + end + else + if current_user == @project.owner + message = 'You can not leave your own project. Transfer or delete the project.' + redirect_back_or_default(default: { action: 'index' }, options: { alert: message }) + else + render_403 + end end end diff --git a/app/models/ability.rb b/app/models/ability.rb index d01b3ae6f0..500af08d20 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -15,6 +15,7 @@ class Ability when "Group" then group_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject) when "GroupMember" then group_member_abilities(user, subject) + when "ProjectMember" then project_member_abilities(user, subject) else [] end.concat(global_abilities(user)) end @@ -231,19 +232,19 @@ class Ability # Only group masters and group owners can create new projects in group if group.has_master?(user) || group.has_owner?(user) || user.admin? - rules.push(*[ + rules += [ :create_projects, :admin_milestones - ]) + ] end # Only group owner and administrators can admin group if group.has_owner?(user) || user.admin? - rules.push(*[ + rules += [ :admin_group, :admin_namespace, :admin_group_member - ]) + ] end rules.flatten @@ -254,16 +255,15 @@ class Ability # Only namespace owner and administrators can admin it if namespace.owner == user || user.admin? - rules.push(*[ + rules += [ :create_projects, :admin_namespace - ]) + ] end rules.flatten end - [:issue, :merge_request].each do |name| define_method "#{name}_abilities" do |user, subject| rules = [] @@ -304,15 +304,39 @@ class Ability rules = [] target_user = subject.user group = subject.group - can_manage = group_abilities(user, group).include?(:admin_group_member) - if can_manage && (user != target_user) - rules << :update_group_member - rules << :destroy_group_member + unless group.last_owner?(target_user) + can_manage = group_abilities(user, group).include?(:admin_group_member) + + if can_manage && user != target_user + rules << :update_group_member + rules << :destroy_group_member + end + + if user == target_user + rules << :destroy_group_member + end end - if !group.last_owner?(user) && (can_manage || (user == target_user)) - rules << :destroy_group_member + rules + end + + def project_member_abilities(user, subject) + rules = [] + target_user = subject.user + project = subject.project + + unless target_user == project.owner + can_manage = project_abilities(user, project).include?(:admin_project_member) + + if can_manage && user != target_user + rules << :update_project_member + rules << :destroy_project_member + end + + if user == target_user + rules << :destroy_project_member + end end rules @@ -320,10 +344,10 @@ class Ability def abilities @abilities ||= begin - abilities = Six.new - abilities << self - abilities - end + abilities = Six.new + abilities << self + abilities + end end private diff --git a/app/models/group.rb b/app/models/group.rb index 793a3b5ef2..2c9e75496b 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -20,8 +20,9 @@ require 'file_size_validator' class Group < Namespace include Gitlab::ConfigHelper include Referable - + has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' + alias_method :members, :group_members has_many :users, through: :group_members validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } @@ -110,10 +111,6 @@ class Group < Namespace has_owner?(user) && owners.size == 1 end - def members - group_members - end - def avatar_type unless self.avatar.image? self.errors.add :avatar, "only images allowed" diff --git a/app/models/member.rb b/app/models/member.rb index cae8caa23f..28aee2e379 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -30,13 +30,22 @@ class Member < ActiveRecord::Base validates :user, presence: true, unless: :invite? validates :source, presence: true - validates :user_id, uniqueness: { scope: [:source_type, :source_id], + validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source", allow_nil: true } validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true - validates :invite_email, presence: { if: :invite? }, - email: { strict_mode: true, allow_nil: true }, - uniqueness: { scope: [:source_type, :source_id], allow_nil: true } + validates :invite_email, + presence: { + if: :invite? + }, + email: { + strict_mode: true, + allow_nil: true + }, + uniqueness: { + scope: [:source_type, :source_id], + allow_nil: true + } scope :invite, -> { where(user_id: nil) } scope :non_invite, -> { where("user_id IS NOT NULL") } @@ -73,7 +82,7 @@ class Member < ActiveRecord::Base def add_user(members, user_id, access_level, current_user = nil) user = user_for_id(user_id) - + # `user` can be either a User object or an email to be invited if user.is_a?(User) member = members.find_or_initialize_by(user_id: user.id) @@ -82,10 +91,21 @@ class Member < ActiveRecord::Base member.invite_email = user end - member.created_by ||= current_user - member.access_level = access_level + if can_update_member?(current_user, member) + member.created_by ||= current_user + member.access_level = access_level - member.save + member.save + end + end + + private + + def can_update_member?(current_user, member) + # There is no current user for bulk actions, in which case anything is allowed + !current_user || + current_user.can?(:update_group_member, member) || + current_user.can?(:update_project_member, member) end end @@ -95,7 +115,7 @@ class Member < ActiveRecord::Base def accept_invite!(new_user) return false unless invite? - + self.invite_token = nil self.invite_accepted_at = Time.now.utc diff --git a/app/models/project.rb b/app/models/project.rb index 9ea0d15497..a099a67cf6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -42,7 +42,7 @@ class Project < ActiveRecord::Base include Sortable include AfterCommitQueue include CaseSensitivity - + extend Gitlab::ConfigHelper extend Enumerize diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index 3c19381321..be94b1abc1 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -1,6 +1,5 @@ - user = member.user - return unless user || member.invite? -- show_roles = true if show_roles.nil? %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %span{class: ("list-item-name" if show_controls)} @@ -25,11 +24,11 @@ = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) - - if show_controls && can?(current_user, :admin_group_member, member) + - if show_controls && can?(current_user, :admin_group_member, @group) = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do Resend invite - - if show_roles + - if should_user_see_group_roles?(current_user, @group) %span.pull-right %strong= member.human_access - if show_controls @@ -37,6 +36,7 @@ = button_tag class: "btn-xs btn js-toggle-button", title: 'Edit access level', type: 'button' do %i.fa.fa-pencil-square-o + - if can?(current_user, :destroy_group_member, member)   - if current_user == user diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 15d289471c..d4ad33a8bf 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,8 +1,6 @@ - page_title "Members" - header_title group_title(@group, "Members", group_group_members_path(@group)) -- show_roles = should_user_see_group_roles?(current_user, @group) - -- if show_roles +- if should_user_see_group_roles?(current_user, @group) %p.light Members of group have access to all group projects. Read more about permissions @@ -32,7 +30,7 @@ (#{@members.total_count}) %ul.well-list - @members.each do |member| - = render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true + = render 'groups/group_members/group_member', member: member, show_controls: true = paginate @members, theme: 'gitlab' diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml index 76c46d1d80..f07cd97e63 100644 --- a/app/views/projects/project_members/_project_member.html.haml +++ b/app/views/projects/project_members/_project_member.html.haml @@ -24,18 +24,19 @@ = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) - - if current_user_can_admin_project + - if can?(current_user, :admin_project_member, @project) = link_to resend_invite_namespace_project_project_member_path(@project.namespace, @project, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do Resend invite - - if current_user_can_admin_project - - unless @project.personal? && user == current_user - .pull-right - %strong= member.human_access + - if can?(current_user, :admin_project_member, @project) + .pull-right + %strong= member.human_access + - if can?(current_user, :update_project_member, member) = button_tag class: "btn-xs btn js-toggle-button", title: 'Edit access level', type: 'button' do %i.fa.fa-pencil-square-o + - if can?(current_user, :destroy_project_member, member)   - if current_user == user = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 615c425e59..b807fb2cc9 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,5 +1,3 @@ -- can_admin_project = can?(current_user, :admin_project, @project) - .panel.panel-default.prepend-top-20 .panel-heading %strong #{@project.name} @@ -8,4 +6,4 @@ (#{members.count}) %ul.well-list - members.each do |project_member| - = render 'project_member', member: project_member, current_user_can_admin_project: can_admin_project + = render 'project_member', member: project_member diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 811b185882..2fb3a41d54 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,2 @@ -- can_admin_project = can?(current_user, :admin_project, @project) :plain - $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member, current_user_can_admin_project: can_admin_project))}'); + $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member))}'); diff --git a/features/groups.feature b/features/groups.feature index 615eff6a33..abf3769a84 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -59,6 +59,14 @@ Feature: Groups When I select "Mike" as "Reporter" Then I should see "Mike" in team list as "Reporter" + @javascript + Scenario: Ignore add user to group when is already Owner + Given gitlab user "Mike" + When I visit group "Owned" members page + And I click link "Add members" + When I select "Mike" as "Reporter" + Then I should see "Mike" in team list as "Owner" + @javascript Scenario: Invite user to group When I visit group "Owned" members page diff --git a/features/steps/groups.rb b/features/steps/groups.rb index a8fba2406a..9c0313537b 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -48,6 +48,17 @@ class Spinach::Features::Groups < Spinach::FeatureSteps click_button "Add users to group" end + step 'I select "Mike" as "Master"' do + user = User.find_by(name: "Mike") + + page.within ".users-group-form" do + select2(user.id, from: "#user_ids", multiple: true) + select "Master", from: "access_level" + end + + click_button "Add users to group" + end + step 'I should see "Mike" in team list as "Reporter"' do page.within '.well-list' do expect(page).to have_content('Mike') @@ -55,6 +66,13 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end end + step 'I should see "Mike" in team list as "Owner"' do + page.within '.well-list' do + expect(page).to have_content('Mike') + expect(page).to have_content('Owner') + end + end + step 'I select "sjobs@apple.com" as "Reporter"' do page.within ".users-group-form" do select2("sjobs@apple.com", from: "#user_ids", multiple: true) From c9f032470116e0cda23e605a66eceaad928b3e02 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 18:28:09 +0000 Subject: [PATCH 29/39] Merge branch 'adamliesko/gitlab-ce-notification-upon-unassignment' into 'master' Add notification to the former assignee upon unassignment Replaces !1769 Fixes #3395 See merge request !1818 --- CHANGELOG | 1 + app/services/notification_service.rb | 12 ++++++++---- spec/services/issues/update_service_spec.rb | 12 ++++++++---- spec/services/merge_requests/update_service_spec.rb | 11 +++++++---- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cc9fcf1992..8e31d2a043 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.2.0 - Add "added", "modified" and "removed" properties to commit object in webhook - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Allow groups to appear in the search results if the group owner allows it + - Add email notification to former assignee upon unassignment (Adam Lieskovský) - New design for project graphs page - Remove deprecated dumped yaml file generated from previous job definitions - Fix incoming email config defaults diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index a6b2234865..6ed6ebca58 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -361,11 +361,13 @@ class NotificationService end def reassign_resource_email(target, project, current_user, method) - assignee_id_was = previous_record(target, "assignee_id") - recipients = build_recipients(target, project, current_user) + previous_assignee_id = previous_record(target, "assignee_id") + previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id + + recipients = build_recipients(target, project, current_user, [previous_assignee]) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, assignee_id_was, current_user.id) + mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id) end end @@ -377,9 +379,11 @@ class NotificationService end end - def build_recipients(target, project, current_user) + def build_recipients(target, project, current_user, extra_recipients = nil) recipients = target.participants(current_user) + recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients + recipients = add_project_watchers(recipients, project) recipients = reject_mention_users(recipients, project) recipients = reject_muted_users(recipients, project) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a91be3b447..f55527ee9a 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -3,13 +3,15 @@ require 'spec_helper' describe Issues::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:issue) { create(:issue, title: 'Old title') } + let(:user3) { create(:user) } + let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) } let(:label) { create(:label) } let(:project) { issue.project } before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do @@ -34,9 +36,11 @@ describe Issues::UpdateService do it { expect(@issue.labels.count).to eq(1) } it { expect(@issue.labels.first.title).to eq('Bug') } - it 'should send email to user2 about assign of new issue' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(issue.title) end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index c75173c145..2ed51d223b 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe MergeRequests::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:merge_request) { create(:merge_request, :simple, title: 'Old title') } + let(:user3) { create(:user) } + let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) } let(:project) { merge_request.project } let(:label) { create(:label) } @@ -47,9 +48,11 @@ describe MergeRequests::UpdateService do with(@merge_request, 'update') end - it 'should send email to user2 about assign of new merge_request' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new merge request and email to user3 about merge request unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(merge_request.title) end From 17a0cd46ccdad003b3bf36061fb16caa24a977a1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 13:48:28 -0500 Subject: [PATCH 30/39] Merge branch 'contributor-graph-by-email' into 'master' See merge request !1705 --- CHANGELOG | 4 +++- .../javascripts/stat_graph_contributors_util.js.coffee | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8e31d2a043..56072e10ef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,8 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) v 8.2.0 +v 8.2.0 (unreleased) + - Fix grouping of contributors by email in graph. - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) @@ -1955,4 +1957,4 @@ v 0.8.0 - stability - security fixes - increased test coverage - - email notification + - email notification \ No newline at end of file diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee index cfe5508290..f5584bcfe4 100644 --- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee @@ -6,7 +6,7 @@ window.ContributorsStatGraphUtil = for entry in log @add_date(entry.date, total) unless total[entry.date]? - data = by_author[entry.author_name] #|| by_email[entry.author_email] + data = by_author[entry.author_name] || by_email[entry.author_email] data ?= @add_author(entry, by_author, by_email) @add_date(entry.date, data) unless data[entry.date] @@ -95,5 +95,4 @@ window.ContributorsStatGraphUtil = if date_range is null || date_range[0] <= new Date(date) <= date_range[1] true else - false - + false \ No newline at end of file From ea5a10b065e6f2d02b627790832424729ed578ce Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 18:51:40 +0000 Subject: [PATCH 31/39] Merge branch 'compare-first-commit' into 'master' Correctly set comparison first commit when range includes a merge commit Fixes #3207. See merge request !1814 --- app/controllers/projects/compare_controller.rb | 4 ++-- app/helpers/diff_helper.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 55134e11d1..5200d609cc 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -20,8 +20,8 @@ class Projects::CompareController < Projects::ApplicationController if compare_result @commits = Commit.decorate(compare_result.commits, @project) @diffs = compare_result.diffs - @commit = @commits.last - @first_commit = @commits.first + @commit = @project.commit(head_ref) + @first_commit = @project.commit(base_ref) @line_notes = [] end end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index f47d5c4c74..0fd5c98658 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -175,7 +175,7 @@ module DiffHelper def commit_for_diff(diff) if diff.deleted_file first_commit = @first_commit || @commit - first_commit.parent + first_commit.parent || @first_commit else @commit end From 8382fac7cc7f9c1ce31b57044c7930db72bbf71a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 15:19:20 -0500 Subject: [PATCH 32/39] Update CHANGELOG [ci skip] --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 56072e10ef..94f07a3168 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) v 8.2.0 -v 8.2.0 (unreleased) - Fix grouping of contributors by email in graph. - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) @@ -1957,4 +1956,4 @@ v 0.8.0 - stability - security fixes - increased test coverage - - email notification \ No newline at end of file + - email notification From 371e8505f813b434c72ac252a16e2618d3161f0d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Nov 2015 14:56:29 +0000 Subject: [PATCH 33/39] Merge branch 'ce-mirror-backport' into 'master' Backport relevant changes from gitlab-org/gitlab-ee!51 To do: - [x] Update gitlab-shell See merge request !1822 --- GITLAB_SHELL_VERSION | 2 +- app/assets/stylesheets/framework/common.scss | 4 ++ .../stylesheets/framework/tw_bootstrap.scss | 2 +- .../projects/imports_controller.rb | 4 +- app/helpers/projects_helper.rb | 8 --- app/models/project.rb | 18 +++-- app/models/repository.rb | 47 +++++++++----- app/services/projects/create_service.rb | 8 +-- app/views/projects/_home_panel.html.haml | 17 ++--- .../projects/buttons/_download.html.haml | 4 ++ .../projects/buttons/_dropdown.html.haml | 2 - app/views/projects/buttons/_fork.html.haml | 25 +++---- app/views/projects/buttons/_star.html.haml | 2 +- app/views/projects/imports/new.html.haml | 25 ++++--- app/views/projects/imports/show.html.haml | 2 +- app/views/projects/new.html.haml | 15 +---- app/views/shared/_clone_panel.html.haml | 5 +- app/views/shared/_import_form.html.haml | 16 +++++ app/workers/repository_fork_worker.rb | 14 ++-- app/workers/repository_import_worker.rb | 65 ++++++++++++------- ...51110125604_add_import_error_to_project.rb | 5 ++ db/schema.rb | 5 +- features/steps/dashboard/new_project.rb | 1 - lib/gitlab/backend/shell.rb | 7 +- spec/services/projects/fork_service_spec.rb | 17 ++--- spec/workers/repository_fork_worker_spec.rb | 1 - 26 files changed, 176 insertions(+), 145 deletions(-) create mode 100644 app/views/projects/buttons/_download.html.haml create mode 100644 app/views/shared/_import_form.html.haml create mode 100644 db/migrate/20151110125604_add_import_error_to_project.rb diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 338a5b5d8f..e261122d5c 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.6.6 +2.6.7 diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index ddbacd7fd4..40f4beb196 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -328,6 +328,10 @@ table { } } +.well { + margin-bottom: 0; +} + .search_box { @extend .well; text-align: center; diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 99d028d122..50c0cf61f4 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -172,7 +172,7 @@ } .panel-body { - form { + form, pre { margin: 0; } diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 066b66014f..fb8788f081 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -28,8 +28,8 @@ class Projects::ImportsController < Projects::ApplicationController if @project.import_finished? redirect_to(project_path(@project)) and return else - redirect_to new_namespace_project_import_path(@project.namespace, - @project) && return + redirect_to(new_namespace_project_import_path(@project.namespace, + @project)) and return end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 690ae2090d..c9cd4a0d54 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -253,14 +253,6 @@ module ProjectsHelper filename_path(project, :version) end - def hidden_pass_url(original_url) - result = URI(original_url) - result.password = '*****' unless result.password.nil? - result - rescue - original_url - end - def project_wiki_path_with_version(proj, page, version, is_newest) url_params = is_newest ? {} : { version_id: version } namespace_project_wiki_path(proj.namespace, proj, page, url_params) diff --git a/app/models/project.rb b/app/models/project.rb index a099a67cf6..70a648e68a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -310,15 +310,17 @@ class Project < ActiveRecord::Base def add_import_job if forked? - unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path) - import_fail - end + RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path) else - RepositoryImportWorker.perform_async(id) + RepositoryImportWorker.perform_async(self.id) end end def clear_import_data + update(import_error: nil) + + ProjectCacheWorker.perform_async(self.id) + self.import_data.destroy if self.import_data end @@ -346,6 +348,14 @@ class Project < ActiveRecord::Base import_status == 'finished' end + def safe_import_url + result = URI.parse(self.import_url) + result.password = '*****' unless result.password.nil? + result.to_s + rescue + original_url + end + def check_limit unless creator.can_create_project? or namespace.kind == 'group' errors[:limit_reached] << ("Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it") diff --git a/app/models/repository.rb b/app/models/repository.rb index f76b770e86..c183610346 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -6,7 +6,7 @@ class Repository include Gitlab::ShellAdapter - attr_accessor :raw_repository, :path_with_namespace, :project + attr_accessor :path_with_namespace, :project def self.clean_old_archives repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path @@ -19,14 +19,18 @@ class Repository def initialize(path_with_namespace, default_branch = nil, project = nil) @path_with_namespace = path_with_namespace @project = project + end - if path_with_namespace - @raw_repository = Gitlab::Git::Repository.new(path_to_repo) - @raw_repository.autocrlf = :input + def raw_repository + return nil unless path_with_namespace + + @raw_repository ||= begin + repo = Gitlab::Git::Repository.new(path_to_repo) + repo.autocrlf = :input + repo + rescue Gitlab::Git::Repository::NoRepository + nil end - - rescue Gitlab::Git::Repository::NoRepository - nil end # Return absolute path to repository @@ -105,29 +109,25 @@ class Repository end def add_branch(branch_name, ref) - cache.expire(:branch_names) - @branches = nil + expire_branches_cache gitlab_shell.add_branch(path_with_namespace, branch_name, ref) end def add_tag(tag_name, ref, message = nil) - cache.expire(:tag_names) - @tags = nil + expire_tags_cache gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) end def rm_branch(branch_name) - cache.expire(:branch_names) - @branches = nil + expire_branches_cache gitlab_shell.rm_branch(path_with_namespace, branch_name) end def rm_tag(tag_name) - cache.expire(:tag_names) - @tags = nil + expire_tags_cache gitlab_shell.rm_tag(path_with_namespace, tag_name) end @@ -169,6 +169,16 @@ class Repository end end + def expire_tags_cache + cache.expire(:tag_names) + @tags = nil + end + + def expire_branches_cache + cache.expire(:branch_names) + @branches = nil + end + def expire_cache cache_keys.each do |key| cache.expire(key) @@ -484,7 +494,7 @@ class Repository root_ref_commit = commit(root_ref) if branch_commit - rugged.merge_base(root_ref_commit.id, branch_commit.id) == branch_commit.id + is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end @@ -494,6 +504,11 @@ class Repository rugged.merge_base(first_commit_id, second_commit_id) end + def is_ancestor?(ancestor_id, descendant_id) + merge_base(ancestor_id, descendant_id) == ancestor_id + end + + def search_files(query, ref) offset = 2 args = %W(#{Gitlab.config.git.bin_path} grep -i -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref}) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 5b84527ecc..700a1db04d 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -55,7 +55,9 @@ module Projects @project.save if @project.persisted? && !@project.import? - raise 'Failed to create repository' unless @project.create_repository + unless @project.create_repository + raise 'Failed to create repository' + end end end @@ -94,9 +96,7 @@ module Projects @project.team << [current_user, :master, current_user] end - if @project.import? - @project.import_start - end + @project.import_start if @project.import? end end end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 8c0980369f..88d54bf6f2 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -18,17 +18,12 @@ .project-repo-buttons .split-one = render 'projects/buttons/star' + = render 'projects/buttons/fork' - - unless empty_repo - = render 'projects/buttons/fork' - = render "shared/clone_panel" - .split-repo-buttons - - unless empty_repo - - if can? current_user, :download_code, @project - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do - = icon('download fw') - - = render 'projects/buttons/dropdown' - = render 'projects/buttons/notifications' + .split-repo-buttons + = render "projects/buttons/download" + = render 'projects/buttons/dropdown' + + = render 'projects/buttons/notifications' diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml new file mode 100644 index 0000000000..14ee2263b7 --- /dev/null +++ b/app/views/projects/buttons/_download.html.haml @@ -0,0 +1,4 @@ +- unless @project.empty_repo? + - if can? current_user, :download_code, @project + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', rel: 'nofollow', title: "Download ZIP" do + = icon('download') diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 18cae8ef6d..b277b765b6 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -32,5 +32,3 @@ = link_to new_namespace_project_tag_path(@project.namespace, @project) do = icon('tags fw') New tag - - diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 8f2f631eb7..2d3abf0905 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -1,12 +1,13 @@ -- if current_user && can?(current_user, :fork_project, @project) - - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn' do - = icon('code-fork fw') - Fork - %span.count - = @project.forks_count - - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do - = icon('code-fork fw') - %span.count - = @project.forks_count +- unless @project.empty_repo? + - if current_user && can?(current_user, :fork_project, @project) + - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 + = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do + = icon('code-fork fw') + Fork + %span.count + = @project.forks_count + - else + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do + = icon('code-fork fw') + %span.count + = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 0658390203..41a3ec6d90 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,5 +1,5 @@ - if current_user - = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do + = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do = icon('star fw') %span.count = @project.star_count diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 92a87690c5..6027fb2336 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -1,22 +1,19 @@ - page_title "Import repository" %h3.page-title - - if @project.import_failed? - Import failed. Retry? - - else - Import repository + Import repository %hr +- if @project.import_failed? + .panel.panel-danger + .panel-heading The repository could not be imported. + .panel-body + %pre + :preserve + #{@project.import_error.try(:strip)} + = form_for @project, url: namespace_project_import_path(@project.namespace, @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' - .well.prepend-top-20 - 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/importing/migrating_from_svn.html"} + = render "shared/import_form", f: f + .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 index 06886d215a..c0d1ce0d12 100644 --- a/app/views/projects/imports/show.html.haml +++ b/app/views/projects/imports/show.html.haml @@ -8,7 +8,7 @@ - else Import in progress. - unless @project.forked? - %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)} + %p.monospace git clone --bare #{@project.safe_import_url} %p Please wait while we import the repository for you. Refresh at will. :javascript new ProjectImport(); diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index a02c12f06a..c9d1fc3da2 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -23,7 +23,6 @@ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2} - if import_sources_enabled? - .project-import.js-toggle-container .form-group %label.control-label Import project from @@ -82,19 +81,7 @@ %span Any repo by URL .js-toggle-content.hide - .form-group.import-url-data - = f.label :import_url, class: 'control-label' do - %span Git repository URL - .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' - .well.prepend-top-20 - %ul - %li - The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. - %li - The import will time out after 4 minutes. For big repositories, use a clone/push combination. - %li - To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. + = render "shared/import_form", f: f .prepend-botton-10 diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 2e4aab3630..8bcb24ae9d 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -21,7 +21,6 @@ = gitlab_config.protocol.upcase = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true - if project.kind_of?(Project) - .input-group-addon - .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" } + .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } } + .visibility-level-label = visibility_level_icon(project.visibility_level) - diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml new file mode 100644 index 0000000000..285af56ad7 --- /dev/null +++ b/app/views/shared/_import_form.html.haml @@ -0,0 +1,16 @@ +.form-group.import-url-data + = f.label :import_url, class: 'control-label' do + %span Git repository URL + .col-sm-10 + = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' + + .well.prepend-top-20 + %ul + %li + The repository must be accessible over http://, https:// or git://. + %li + If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. + %li + The import will time out after 4 minutes. For big repositories, use a clone/push combination. + %li + To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index acd1c43f06..2f991c5233 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -13,22 +13,20 @@ class RepositoryForkWorker end result = gitlab_shell.fork_repository(source_path, target_path) - unless result logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}") + project.update(import_error: "The project could not be forked.") project.import_fail - project.save return end - if project.valid_repo? - ProjectCacheWorker.perform_async(project.id) - project.import_finish - else - project.import_fail + unless project.valid_repo? logger.error("Project #{id} had an invalid repository after fork") + project.update(import_error: "The forked repository is invalid.") + project.import_fail + return end - project.save + project.import_finish end end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index ea2808045e..1de4916199 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -7,37 +7,52 @@ class RepositoryImportWorker def perform(project_id) project = Project.find(project_id) - unless project.import_url == Project::UNKNOWN_IMPORT_URL - import_result = gitlab_shell.send(:import_repository, - project.path_with_namespace, - project.import_url) - return project.import_fail unless import_result - else + if project.import_url == Project::UNKNOWN_IMPORT_URL + # In this case, we only want to import issues, not a repository. unless project.create_repository - return project.import_fail + project.update(import_error: "The repository could not be created.") + project.import_fail + return + end + else + begin + gitlab_shell.import_repository(project.path_with_namespace, project.import_url) + rescue Gitlab::Shell::Error => e + project.update(import_error: e.message) + project.import_fail + return end end - data_import_result = case project.import_type - when 'github' - Gitlab::GithubImport::Importer.new(project).execute - when 'gitlab' - Gitlab::GitlabImport::Importer.new(project).execute - when 'bitbucket' - Gitlab::BitbucketImport::Importer.new(project).execute - when 'google_code' - Gitlab::GoogleCodeImport::Importer.new(project).execute - when 'fogbugz' - Gitlab::FogbugzImport::Importer.new(project).execute - else - true - end - return project.import_fail unless data_import_result + data_import_result = + case project.import_type + when 'github' + Gitlab::GithubImport::Importer.new(project).execute + when 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + when 'bitbucket' + Gitlab::BitbucketImport::Importer.new(project).execute + when 'google_code' + Gitlab::GoogleCodeImport::Importer.new(project).execute + when 'fogbugz' + Gitlab::FogbugzImport::Importer.new(project).execute + else + true + end - Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' + unless data_import_result + project.update(import_error: "The remote issue data could not be imported.") + project.import_fail + return + end + + if project.import_type == 'bitbucket' + Gitlab::BitbucketImport::KeyDeleter.new(project).execute + end project.import_finish - project.save - ProjectCacheWorker.perform_async(project.id) + + # Explicitly update mirror so that upstream remote is created and fetched + project.update_mirror end end diff --git a/db/migrate/20151110125604_add_import_error_to_project.rb b/db/migrate/20151110125604_add_import_error_to_project.rb new file mode 100644 index 0000000000..7fc990f8d0 --- /dev/null +++ b/db/migrate/20151110125604_add_import_error_to_project.rb @@ -0,0 +1,5 @@ +class AddImportErrorToProject < ActiveRecord::Migration + def change + add_column :projects, :import_error, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index aa76cef9fe..440a33e200 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -640,7 +640,10 @@ ActiveRecord::Schema.define(version: 20151116144118) do t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" - t.integer "commit_count", default: 0 + t.integer "commit_count", default: 0 + t.boolean "merge_requests_ff_only_enabled", default: false + t.text "issues_template" + t.text "import_error" end add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 44a4aa9844..a0aad66184 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -44,7 +44,6 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps git_import_instructions = first('.js-toggle-content') expect(git_import_instructions).to be_visible expect(git_import_instructions).to have_content "Git repository URL" - expect(git_import_instructions).to have_content "The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL:" end step 'I click on "Google Code"' do diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 01b8bda05c..87ac30b5ff 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -1,6 +1,6 @@ module Gitlab class Shell - class AccessDenied < StandardError; end + class Error < StandardError; end class KeyAdder < Struct.new(:io) def add_key(id, key) @@ -36,8 +36,9 @@ 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']) + output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240']) + raise Error, output unless status.zero? + true end # Move repository diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index e397b2b9b4..1feba6ce04 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -25,13 +25,6 @@ describe Projects::ForkService do end end - context 'fork project failure' do - it "fails due to transaction failure" do - @to_project = fork_project(@from_project, @to_user, false) - expect(@to_project.import_failed?) - end - end - context 'project already exists' do it "should fail due to validation, not transaction failure" do @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) @@ -66,7 +59,7 @@ describe Projects::ForkService do 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 = fork_project(@project, @group_owner, @opts) expect(to_project.owner).to eq(@group) expect(to_project.namespace).to eq(@group) expect(to_project.name).to eq(@project.name) @@ -78,7 +71,7 @@ describe Projects::ForkService do 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 = fork_project(@project, @developer, @opts) expect(to_project.errors[:namespace]).to eq(['is not valid']) end end @@ -87,7 +80,7 @@ describe Projects::ForkService 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) + to_project = fork_project(@project, @group_owner, @opts) expect(existing_project.persisted?).to be_truthy expect(to_project.errors[:name]).to eq(['has already been taken']) expect(to_project.errors[:path]).to eq(['has already been taken']) @@ -95,8 +88,8 @@ describe Projects::ForkService do end end - def fork_project(from_project, user, fork_success = true, params = {}) - allow(RepositoryForkWorker).to receive(:perform_async).and_return(fork_success) + def fork_project(from_project, user, params = {}) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) Projects::ForkService.new(from_project, user, params).execute end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index aa03110696..245f066df1 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -12,7 +12,6 @@ describe RepositoryForkWorker do project.path_with_namespace, fork_project.namespace.path). and_return(true) - expect(ProjectCacheWorker).to receive(:perform_async) subject.perform(project.id, project.path_with_namespace, From 2dd4314faea057ac6c51463455665d3b326cc679 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 19 Nov 2015 14:41:44 +0000 Subject: [PATCH 34/39] Merge branch 'client-max-body-size' into 'master' Do not limit workhorse POST/PUT size in NGINX Limiting, if any, should happen in gitlab-workhorse. See merge request !1831 --- lib/support/nginx/gitlab | 4 +--- lib/support/nginx/gitlab-ssl | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 93f2ad07ae..f38e2e6209 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -114,7 +114,6 @@ server { } location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -140,7 +139,6 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -148,13 +146,13 @@ server { # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location @gitlab-workhorse { + client_max_body_size 0; ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. # gzip off; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 90749947fa..4cc6a17db0 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -161,7 +161,6 @@ server { } location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -187,7 +186,6 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -195,13 +193,13 @@ server { # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location @gitlab-workhorse { + client_max_body_size 0; ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. gzip off; From 71cb328b4aafcfb772801e50033f686cbc3755ed Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 19 Nov 2015 12:29:47 +0000 Subject: [PATCH 35/39] Merge branch 'update_nginx_deploy' into 'master' Deploy page should be shown for all pages not just root Related to change already merged to Omnibus - gitlab-org/omnibus-gitlab!540 This change makes it so the deploy page is shown on all GitLab pages if the page exists. Users will no longer receive the 502 when deploys are happening. See merge request !1828 --- lib/support/nginx/gitlab | 2 +- lib/support/nginx/gitlab-ssl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index f38e2e6209..0cf5292b29 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -67,7 +67,7 @@ server { location / { ## Serve static files from defined root folder. ## @gitlab is a named location for the upstream fallback, see below. - try_files $uri $uri/index.html $uri.html @gitlab; + try_files $uri /index.html $uri.html @gitlab; } ## We route uploads through GitLab to prevent XSS and enforce access control. diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 4cc6a17db0..31a651c87f 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -112,7 +112,7 @@ server { location / { ## Serve static files from defined root folder. ## @gitlab is a named location for the upstream fallback, see below. - try_files $uri $uri/index.html $uri.html @gitlab; + try_files $uri /index.html $uri.html @gitlab; } ## We route uploads through GitLab to prevent XSS and enforce access control. From 32771f06f1c683c38ecea577cc39b064c5211f55 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Wed, 18 Nov 2015 10:03:27 +0000 Subject: [PATCH 36/39] Merge branch 'add_lfs_doc' into 'master' Documentation for Git LFS Doc explaining how to use Git LFS, how to use Git LFS with GitLab, known issues and troubleshooting tips. See merge request !1813 --- doc/workflow/git_lfs.md | 136 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 doc/workflow/git_lfs.md diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md new file mode 100644 index 0000000000..4990a9b5aa --- /dev/null +++ b/doc/workflow/git_lfs.md @@ -0,0 +1,136 @@ +# Git LFS + +Managing large files such as audio, video and graphics files has always been one of the shortcomings of Git. +The general recommendation is to not have Git repositories larger than 1GB to preserve performance. + +GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) (EE only), however in certain +environments it is not always convenient to use different commands to differentiate between the large files and regular ones. + +Git LFS makes this simpler for the end user by removing the requirement to learn new commands. + + +## How it works + +Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests. +Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file. + +## Requirements + +* Git LFS is supported in GitLab starting with version 8.2 +* Git LFS client version 0.6.0 and up + +## GitLab and Git LFS + +### Configuration + +Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on. + +There are two configuration options to help GitLab server administrators: + +* Enabling/disabling Git LFS support +* Changing the location of LFS object storage + +#### Omnibus packages + +In `/etc/gitlab/gitlab.rb`: + +```ruby +gitlab_rails['lfs_enabled'] = false +gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects" +``` + +#### Installations from source + +In `config/gitlab.yml`: + +```yaml + lfs: + enabled: false + storage_path: /mnt/storage/lfs-objects +``` + +## Known limitations + +* Git LFS v1 original API is not supported since it was deprecated early in LFS development, starting with Git LFS version 0.6.0 +* When SSH is set as a remote, Git LFS objects still go through HTTPS +* Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended +* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported +* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the url to Git config manually (see #troubleshooting-tips) + +## Using Git LFS + +Lets take a look at the workflow when you need to check large files into your Git repository with Git LFS: +For example, if you want to upload a very large file and check it into your Git repository: + +```bash +git clone git@gitlab.example.com:group/project.git +git lfs init # initialize the Git LFS project project +git lfs track "*.iso" # select the file extensions that you want to treat as large files +``` + +Once a certain file extension is marked for tracking as a LFS object you can use Git as usual without having to redo the command to track a file with the same extension: + +```bash +cp ~/tmp/debian.iso ./ # copy a large file into the current directory +git add . # add the large file to the project +git commit -am "Added Debian iso" # commit the file meta data +git push origin master # sync the git repo and large file to the GitLab server +``` + +Downloading a single large file is also very simple: + +```bash +git clone git@gitlab.example.com:group/project.git +git lfs fetch debian.iso # download the large file +``` + + +## Troubleshooting + +### error: Repository or object not found + +There are a couple of reasons why this error can occur: + +* Wrong version of LFS client used: + +Check the version of Git LFS on the client machine with `git lfs version`. Only version 0.6.0 and newer are supported. + +* Project is using deprecated LFS API + +Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try using Git LFS client newer than 0.6.0. + +### Invalid status for : 501 + +When attempting to push a LFS object to a GitLab server that doesn't have Git LFS support enabled, server will return status `error 501`. Check with your GitLab administrator why Git LFS is not enabled on the server. See [Configuration section](#configuration) for instructions on how to enable LFS support. + +### getsockopt: connection refused + +If you push a LFS object to a project and you receive an error similar to: `Post /info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`, +the LFS client is trying to reach GitLab through HTTPS. However, your GitLab instance is being served on HTTP. + +This behaviour is caused by Git LFS using HTTPS connections by default when a `lfsurl` is not set in the Git config. + +To prevent this from happening, set the lfs url in project Git config: + +```bash + +git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/objects/batch" +``` + +### Credentials are always required when pushing an object + +Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required. + +By default, Git has support for remembering the credentials for each repository you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials). + +For example, you can tell Git to remember the password for a period of time in which you expect to push the objects: + +```bash +git config --global credential.helper 'cache --timeout=3600' +``` + +This will remember the credentials for an hour after which Git operations will require re-authentication. + +If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available. + +More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage) \ No newline at end of file From 61c6e9697195b44a683e78d8b23c2d28eda15075 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 19 Nov 2015 10:00:58 +0000 Subject: [PATCH 37/39] Merge branch 'check_if_it_should_be_archived_in_backup' into 'master' Check which folders and archives should be packed before passing to tar command. If user uses backup task with SKIP and skips one of the archives listed(uploads, builds, artifacts) backup create will give an error: `Cannot stat: No such file or directory`. This MR fixes that by checking for skipped items. Additionally, compact everything to avoid `TypeError: no implicit conversion of nil into String` errors. See merge request !1824 --- lib/backup/manager.rb | 14 ++++++-------- spec/tasks/gitlab/backup_rake_spec.rb | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 9e15d5411a..e7eda7c6f4 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -150,17 +150,15 @@ module Backup private def backup_contents - folders_to_backup + ["uploads.tar.gz", "builds.tar.gz", "artifacts.tar.gz", "backup_information.yml"] + folders_to_backup + archives_to_backup + ["backup_information.yml"] + end + + def archives_to_backup + %w{uploads builds artifacts}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact end def folders_to_backup - folders = %w{repositories db} - - if ENV["SKIP"] - return folders.reject{ |folder| ENV["SKIP"].include?(folder) } - end - - folders + %w{repositories db}.reject{ |name| skipped?(name) } end def settings diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 06559c3925..fb5e74af64 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -149,7 +149,7 @@ describe 'gitlab:app namespace rake task' do # Redirect STDOUT and run the rake task orig_stdout = $stdout $stdout = StringIO.new - ENV["SKIP"] = "repositories" + ENV["SKIP"] = "repositories,uploads" run_rake_task('gitlab:backup:create') $stdout = orig_stdout @@ -180,6 +180,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke + expect(Rake::Task["gitlab:backup:uploads:restore"]).not_to receive :invoke expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke From 506e0be36bcef746c3f2e469a5e1046380ca7d76 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 19 Nov 2015 13:44:34 +0000 Subject: [PATCH 38/39] Merge branch 'lfs-client-doc' into 'master' Add link to git-lfs client [ci skip] See merge request !1832 --- doc/workflow/git_lfs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md index 4990a9b5aa..e1064051fe 100644 --- a/doc/workflow/git_lfs.md +++ b/doc/workflow/git_lfs.md @@ -17,7 +17,7 @@ Once the request is authorized, Git LFS client receives instructions from where ## Requirements * Git LFS is supported in GitLab starting with version 8.2 -* Git LFS client version 0.6.0 and up +* Git LFS [client](https://git-lfs.github.com) version 0.6.0 and up ## GitLab and Git LFS From 9535d91e8db07cb32f408a62dcc169ba302566f4 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 19 Nov 2015 15:09:28 +0000 Subject: [PATCH 39/39] Merge branch 'update-gitlab-shell-doc' into 'master' Update gitlab-shell documentation to 2.6.7 See merge request !1836 --- doc/install/installation.md | 2 +- doc/update/8.1-to-8.2.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 7ef46b0406..52ae30af80 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -312,7 +312,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.6.6] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.7] 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/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index 73d899f9c2..261031442a 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -68,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.6.6 +sudo -u git -H git checkout v2.6.7 ``` ### 5. Replace gitlab-git-http-server with gitlab-workhorse