diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 045e5805bd0..aaab4b40c4c 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -73,6 +73,6 @@ class DashboardController < ApplicationController protected def load_projects - @projects = current_user.authorized_projects.sorted_by_activity + @projects = current_user.authorized_projects.sorted_by_activity.non_archived end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 1835671fe98..e1c55e7d913 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,7 +5,7 @@ class ProjectsController < ApplicationController # Authorize before_filter :authorize_read_project!, except: [:index, :new, :create] - before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer] + before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] before_filter :require_non_empty_project, only: [:blob, :tree, :graph] layout 'navless', only: [:new, :create, :fork] @@ -116,6 +116,24 @@ class ProjectsController < ApplicationController end end + def archive + return access_denied! unless can?(current_user, :archive_project, project) + project.archive! + + respond_to do |format| + format.html { redirect_to @project } + end + end + + def unarchive + return access_denied! unless can?(current_user, :archive_project, project) + project.unarchive! + + respond_to do |format| + format.html { redirect_to @project } + end + end + private def set_title diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 109acfd192b..f24156e4d85 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -73,14 +73,14 @@ module SearchHelper # Autocomplete results for the current user's projects def projects_autocomplete - current_user.authorized_projects.map do |p| + current_user.authorized_projects.non_archived.map do |p| { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } end end # Autocomplete results for the current user's projects def public_projects_autocomplete - Project.public_or_internal_only(current_user).map do |p| + Project.public_or_internal_only(current_user).non_archived.map do |p| { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 6df56eed5b8..cf925141f2d 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -59,31 +59,35 @@ class Ability # Rules based on role in project if team.masters.include?(user) - rules << project_master_rules + rules += project_master_rules elsif team.developers.include?(user) - rules << project_dev_rules + rules += project_dev_rules elsif team.reporters.include?(user) - rules << project_report_rules + rules += project_report_rules elsif team.guests.include?(user) - rules << project_guest_rules + rules += project_guest_rules end if project.public? || project.internal? - rules << public_project_rules + rules += public_project_rules end if project.owner == user || user.admin? - rules << project_admin_rules + rules += project_admin_rules end if project.group && project.group.has_owner?(user) - rules << project_admin_rules + rules += project_admin_rules end - rules.flatten + if project.archived? + rules -= project_archived_rules + end + + rules end def public_project_rules @@ -125,6 +129,16 @@ class Ability ] end + def project_archived_rules + [ + :write_merge_request, + :push_code, + :push_code_to_protected_branches, + :modify_merge_request, + :admin_merge_request + ] + end + def project_master_rules project_dev_rules + [ :push_code_to_protected_branches, @@ -147,7 +161,8 @@ class Ability :change_namespace, :change_visibility_level, :rename_project, - :remove_project + :remove_project, + :archive_project ] end @@ -160,7 +175,7 @@ class Ability # Only group owner and administrators can manage group if group.has_owner?(user) || user.admin? - rules << [ + rules += [ :manage_group, :manage_namespace ] @@ -174,7 +189,7 @@ class Ability # Only namespace owner and administrators can manage it if namespace.owner == user || user.admin? - rules << [ + rules += [ :manage_namespace ] end diff --git a/app/models/project.rb b/app/models/project.rb index 506f34ca6b6..d389579b3a1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -116,6 +116,8 @@ class Project < ActiveRecord::Base scope :public_only, -> { where(visibility_level: PUBLIC) } scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } + scope :non_archived, -> { where(archived: false) } + enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab class << self @@ -132,7 +134,7 @@ class Project < ActiveRecord::Base end def search query - joins(:namespace).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") + joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") end def find_with_namespace(id) @@ -472,4 +474,12 @@ class Project < ActiveRecord::Base def visibility_level_field visibility_level end + + def archive! + update_attribute(:archived, true) + end + + def unarchive! + update_attribute(:archived, false) + end end diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index b42bbf58dc4..23d78720881 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -82,6 +82,10 @@ = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) .project-info .pull-right + - if project.archived? + %span.label + %i.icon-book + Archived - project.labels.each do |label| %span.label.label-info %i.icon-tag diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 19c150b54fb..ae5deb0b6de 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -7,6 +7,10 @@ %span.visibility-level-label = visibility_level_icon(@project.visibility_level) = visibility_level_label(@project.visibility_level) + - if @project.archived? + %span.visibility-level-label + %i.icon-book + Archived .span7 - unless empty_repo diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 57936cff10f..c56919e792c 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -98,6 +98,33 @@ %i.icon-chevron-down .js-toggle-visibility-container.hide + - if can? current_user, :archive_project, @project + .ui-box.ui-box-danger + .title + - if @project.archived? + Unarchive project + - else + Archive project + .ui-box-body + - if @project.archived? + %p + Unarchiving the project will mark its repository as active. + %br + The project can be committed to. + %br + %strong Once active this project shows up in the search and on the dashboard. + = link_to 'Unarchive', unarchive_project_path(@project), confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be comitted to again.", method: :post, class: "btn btn-remove" + - else + %p + Archiving the project will mark its repository as read-only. + %br + It is hidden from the dashboard and doesn't show up in searches. + %br + %strong Archived projects cannot be committed to! + = link_to 'Archive', archive_project_path(@project), confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to.", method: :post, class: "btn btn-remove" + - else + %p.nothing_here_message Only the project owner can archive a project + - if can?(current_user, :change_namespace, @project) .ui-box.ui-box-danger .title Transfer project diff --git a/config/routes.rb b/config/routes.rb index 188d2099997..8322d6a9d4e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -170,6 +170,8 @@ Gitlab::Application.routes.draw do member do put :transfer post :fork + post :archive + post :unarchive get :autocomplete_sources end diff --git a/db/migrate/20131129154016_add_archived_to_projects.rb b/db/migrate/20131129154016_add_archived_to_projects.rb new file mode 100644 index 00000000000..917e690ba47 --- /dev/null +++ b/db/migrate/20131129154016_add_archived_to_projects.rb @@ -0,0 +1,5 @@ +class AddArchivedToProjects < ActiveRecord::Migration + def change + add_column :projects, :archived, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index e7b3bf09d2d..77d245913e2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -192,6 +192,7 @@ ActiveRecord::Schema.define(version: 20131214224427) do t.boolean "imported", default: false, null: false t.string "import_url" t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false end add_index "projects", ["creator_id"], name: "index_projects_on_owner_id", using: :btree diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature new file mode 100644 index 00000000000..399c9b53d81 --- /dev/null +++ b/features/dashboard/archived_projects.feature @@ -0,0 +1,16 @@ +Feature: Dashboard with archived projects + Background: + Given I sign in as a user + And I own project "Shop" + And I own project "Forum" + And project "Forum" is archived + And I visit dashboard page + + Scenario: I should see non-archived projects on dashboard + Then I should see "Shop" project link + And I should not see "Forum" project link + + Scenario: I should see all projects on projects page + And I visit dashboard projects page + Then I should see "Shop" project link + And I should see "Forum" project link diff --git a/features/project/archived_projects.feature b/features/project/archived_projects.feature new file mode 100644 index 00000000000..9aac29384ba --- /dev/null +++ b/features/project/archived_projects.feature @@ -0,0 +1,39 @@ +Feature: Project Archived + Background: + Given I sign in as a user + And I own project "Shop" + And I own project "Forum" + + Scenario: I should not see archived on project page of not-archive project + And project "Forum" is archived + And I visit project "Shop" page + Then I should not see "Archived" + + Scenario: I should see archived on project page of archive project + And project "Forum" is archived + And I visit project "Forum" page + Then I should see "Archived" + + Scenario: I should not see archived on projects page with no archived projects + And I visit dashboard projects page + Then I should not see "Archived" + + Scenario: I should see archived on projects page with archived projects + And project "Forum" is archived + And I visit dashboard projects page + Then I should see "Archived" + + Scenario: I archive project + When project "Shop" has push event + And I visit project "Shop" page + And I visit edit project "Shop" page + And I set project archived + Then I should see "Archived" + + Scenario: I unarchive project + When project "Shop" has push event + And project "Shop" is archived + And I visit project "Shop" page + And I visit edit project "Shop" page + And I set project unarchived + Then I should not see "Archived" diff --git a/features/steps/dashboard/dashboard_with_archived_projects.rb b/features/steps/dashboard/dashboard_with_archived_projects.rb new file mode 100644 index 00000000000..700f4b426c3 --- /dev/null +++ b/features/steps/dashboard/dashboard_with_archived_projects.rb @@ -0,0 +1,22 @@ +class DashboardWithArchivedProjects < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + When 'project "Forum" is archived' do + project = Project.find_by_name "Forum" + project.update_attribute(:archived, true) + end + + Then 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + Then 'I should not see "Forum" project link' do + page.should_not have_link "Forum" + end + + Then 'I should see "Forum" project link' do + page.should have_link "Forum" + end +end diff --git a/features/steps/project/project_archived.rb b/features/steps/project/project_archived.rb new file mode 100644 index 00000000000..149d293cd08 --- /dev/null +++ b/features/steps/project/project_archived.rb @@ -0,0 +1,37 @@ +class ProjectArchived < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + When 'project "Forum" is archived' do + project = Project.find_by_name "Forum" + project.update_attribute(:archived, true) + end + + When 'project "Shop" is archived' do + project = Project.find_by_name "Shop" + project.update_attribute(:archived, true) + end + + When 'I visit project "Forum" page' do + project = Project.find_by_name "Forum" + visit project_path(project) + end + + Then 'I should not see "Archived"' do + page.should_not have_content "Archived" + end + + Then 'I should see "Archived"' do + page.should have_content "Archived" + end + + When 'I set project archived' do + click_link "Archive" + end + + When 'I set project unarchived' do + click_link "Unarchive" + end + +end \ No newline at end of file diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index cef66b038db..3dc4932a09a 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -14,6 +14,13 @@ module SharedProject @project.team << [@user, :master] end + # Create another specific project called "Forum" + And 'I own project "Forum"' do + @project = Project.find_by_name "Forum" + @project ||= create(:project_with_code, name: "Forum", namespace: @user.namespace, path: 'forum_project') + @project.team << [@user, :master] + end + And 'project "Shop" has push event' do @project = Project.find_by_name("Shop") diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1e05d188234..8aa4c7fed1a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -21,6 +21,7 @@ # imported :boolean default(FALSE), not null # import_url :string(255) # visibility_level :integer default(0), not null +# archived :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index e8870f4d5d8..5f6dff92c0a 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -103,6 +103,33 @@ describe API::API do end end + context "archived project" do + let(:personal_project) { create(:project, namespace: user.namespace) } + + before do + project.team << [user, :developer] + project.archive! + end + + context "git pull" do + it do + pull(key, project) + + response.status.should == 200 + response.body.should == 'true' + end + end + + context "git push" do + it do + push(key, project) + + response.status.should == 200 + response.body.should == 'false' + end + end + end + context "deploy key" do let(:key) { create(:deploy_key) }