diff --git a/CHANGELOG b/CHANGELOG index 45f479aaf8..0efc75e51d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,24 @@ Please view this file on the master branch, on stable branches it's out of date. +v 7.14.0 (unreleased) + - Fix full screen mode for snippet comments (Daniel Gerhardt) + - Fix 404 error in files view after deleting the last file in a repository (Stan Hu) + - Fix label read access for unauthenticated users (Daniel Gerhardt) + - Fix access to disabled features for unauthenticated users (Daniel Gerhardt) + - Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu) + - Fix file upload dialog for comment editing (Daniel Gerhardt) + - Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu) + - Expire Rails cache entries after two weeks to prevent endless Redis growth + - Add support for destroying project milestones (Stan Hu) + - Add fetch command to the MR page. + - Fix bug causing "Remove source-branch" option not to work for merge requests from the same project. + +v 7.13.1 + - Revert issue caching + - Reverted cache for events + v 7.13.0 (unreleased) + - Remove repository graph log to fix slow cache updates after push event (Stan Hu) - Only enable HSTS header for HTTPS and port 443 (Stan Hu) - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu) - Fix redirection to home page URL for unauthorized users (Daniel Gerhardt) @@ -15,7 +33,6 @@ v 7.13.0 (unreleased) - Add `two_factor_enabled` field to admin user API (Stan Hu) - Fix invalid timestamps in RSS feeds (Rowan Wookey) - Fix downloading of patches on public merge requests when user logged out (Stan Hu) - - The password for the default administrator (root) account has been changed from "5iveL!fe" to "password". - Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu) - Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu) - Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu) @@ -47,6 +64,8 @@ v 7.13.0 (unreleased) - Faster code search in repository and wiki. Fixes search page timeout for big repositories - Allow administrators to disable 2FA for a specific user - Add error message for SSH key linebreaks + - Store commits count in database (will populate with valid values only after first push) + - Rebuild cache after push to repository in background job v 7.12.2 - Correctly show anonymous authorized applications under Profile > Applications. @@ -122,6 +141,7 @@ v 7.12.0 - Improve group removing logic - Trigger create-hooks on backup restore task - Add option to automatically link omniauth and LDAP identities + - Allow special character in users bio. I.e.: I <3 GitLab v 7.11.4 - Fix missing bullets when creating lists @@ -140,9 +160,6 @@ v 7.11.1 v 7.11.0 - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) - Get editing comments to work in Chrome 43 again. - - Allow special character in users bio. I.e.: I <3 GitLab - -v 7.11.0 - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) - Don't show duplicate deploy keys - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) diff --git a/CHANGELOG-EE b/CHANGELOG-EE index 8973f756a1..df4928920c 100644 --- a/CHANGELOG-EE +++ b/CHANGELOG-EE @@ -1,6 +1,15 @@ -v 7.13 (Unrelease) +v 7.13.2 + - Fix group web hook + +v 7.13.1 + - Merge community edition changes for version 7.13.1 + - Fix: "Rebase before merge" doesn't work when source branch is in the same project + +v 7.13 - Fix git hook validation on initial push to master branch. - Reset approvals on push + - Fix 500 error when the source project of an MR is deleted + - Ability to define merge request approvers v 7.12.2 - Fixed the alignment of project settings icons @@ -171,4 +180,4 @@ v 6.2.0 - Use omniauth-ldap nickname attribute as GitLab username - Improve group sharing UI for installation with many groups - Fix empty LDAP group raises exception - - Respect LDAP user filter for git access + - Respect LDAP user filter for git access \ No newline at end of file diff --git a/Gemfile b/Gemfile index a00200574a..14ea25d627 100644 --- a/Gemfile +++ b/Gemfile @@ -46,7 +46,7 @@ gem "gitlab_git", '~> 7.2.5' gem 'gitlab-grack', '~> 2.0.2', require: 'grack' # LDAP Auth -# GitLab fork with several improvements to original library. For full list of changes +# GitLab fork with several improvements to original library. For full list of changes # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap" gem 'net-ldap' @@ -55,9 +55,9 @@ gem 'net-ldap' gem 'gollum-lib', '~> 4.0.2' # Language detection -# GitLab fork of linguist does not require pygments/python dependency. -# New version of original gem also dropped pygments support but it has strict -# dependency to unstable rugged version. We have internal issue for replacing +# GitLab fork of linguist does not require pygments/python dependency. +# New version of original gem also dropped pygments support but it has strict +# dependency to unstable rugged version. We have internal issue for replacing # fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052. gem "gitlab-linguist", "~> 3.0.1", require: "linguist" @@ -230,7 +230,7 @@ end group :development, :test do gem 'awesome_print' - gem 'byebug' + gem 'byebug', platform: :mri gem 'fuubar', '~> 2.0.0' gem 'pry-rails' diff --git a/README.md b/README.md index c96a9028fd..d535e5790e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +# GitLab + +[![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master) +[![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq) +[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) +[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) + ## Canonical source The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. @@ -41,21 +48,12 @@ To see how GitLab looks please see the [features page on our website](https://ab ## Editions -There are two editions of GitLab. -*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license. +There are two editions of GitLab: -*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users. -To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/). +- GitLab Community Edition (CE) is available freely under the MIT Expat license. +- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/). -## Code status - -- [![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master) on ci.gitlab.com (master branch) - -- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq) - -- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) - -- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) +Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code. ## Website @@ -70,7 +68,31 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ## Requirements -GitLab requires the following software: +Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems. + +## Installation + +The recommended way to install GitLab is with the [Omnibus packages](https://about.gitlab.com/downloads/) on our package server. +Compared to an installation from source, this is faster and less error prone. +Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager. + +There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information. + +You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password. + +## Install a development environment + +To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). +If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone. +One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file: + + cp config/unicorn.rb.example.development config/unicorn.rb + +Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development). + +## Software stack + +GitLab is a Ruby on Rails application that runs on the following software: - Ubuntu/Debian/CentOS/RHEL - Ruby (MRI) 2.0 or 2.1 @@ -78,15 +100,7 @@ GitLab requires the following software: - Redis 2.0+ - MySQL or PostgreSQL -Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems. - -## Installation - -The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to an installation from source, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager. - -There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information. - -You can access a new installation with the login **`root`** and password **`password`**, after login you are required to set a unique password. +For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html). ## Third-party applications @@ -100,16 +114,6 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m For upgrading information please see our [update page](https://about.gitlab.com/update/). -## Install a development environment - -To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). -If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone. -One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file: - - cp config/unicorn.rb.example.development config/unicorn.rb - -Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development). - ## Documentation All documentation can be found on [doc.gitlab.com/ee/](http://doc.gitlab.com/ee/). @@ -125,4 +129,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ## Is it awesome? Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. -[These people](https://twitter.com/gitlab/favorites) seem to like it. \ No newline at end of file +[These people](https://twitter.com/gitlab/favorites) seem to like it. diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index fd766738ae..9dbd7fa676 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -132,7 +132,10 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new ZenMode() new DropzoneInput($('.wiki-form')) - when 'snippets', 'labels', 'graphs' + when 'snippets' + shortcut_handler = new ShortcutsNavigation() + new ZenMode() if path[2] == 'show' + when 'labels', 'graphs' shortcut_handler = new ShortcutsNavigation() when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee index a8b3c1fa33..e604e6025c 100644 --- a/app/assets/javascripts/line_highlighter.js.coffee +++ b/app/assets/javascripts/line_highlighter.js.coffee @@ -70,7 +70,7 @@ class @LineHighlighter @clearHighlight() - lineNumber = $(event.target).data('line-number') + lineNumber = $(event.target).closest('a').data('line-number') current = @hashToRange(@_hash) unless current[0] && event.shiftKey diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 7462975bd3..b21cb7904b 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -15,9 +15,7 @@ class @MergeRequest this.$('.show-all-commits').on 'click', => this.showAllCommits() - # `MergeRequests#new` has no tab-persisting or lazy-loading behavior - unless @opts.action == 'new' - new MergeRequestTabs(@opts) + @initTabs() # Prevent duplicate event bindings @disableTaskList() @@ -29,6 +27,14 @@ class @MergeRequest $: (selector) -> this.$el.find(selector) + initTabs: -> + if @opts.action != 'new' + # `MergeRequests#new` has no tab-persisting or lazy-loading behavior + new MergeRequestTabs(@opts) + else + # Show the first tab (Commits) + $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show') + showAllCommits: -> this.$('.first-commits').remove() this.$('.all-commits').removeClass 'hide' diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 1c05a2b9fe..bcff7bcc49 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -298,7 +298,7 @@ class @Notes note.find(".note-header").hide() base_form = note.find(".note-edit-form") form = base_form.clone().insertAfter(base_form) - form.addClass('current-note-edit-form') + form.addClass('current-note-edit-form gfm-form') form.find('.div-dropzone').remove() # Show the attachment delete link diff --git a/app/controllers/projects/approvers_controller.rb b/app/controllers/projects/approvers_controller.rb new file mode 100644 index 0000000000..27f39aaa92 --- /dev/null +++ b/app/controllers/projects/approvers_controller.rb @@ -0,0 +1,14 @@ +class Projects::ApproversController < ApplicationController + def destroy + if params[:merge_request_id] + authorize_create_merge_request! + merge_request = project.merge_requests.find_by!(iid: params[:merge_request_id]) + merge_request.approvers.find(params[:id]).destroy + else + authorize_admin_project! + project.approvers.find(params[:id]).destroy + end + + redirect_to :back + end +end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index b954f85420..526f7bafc7 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -149,6 +149,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def automerge return access_denied! unless @merge_request.can_be_merged_by?(current_user) + return render_404 unless @merge_request.approved? if @merge_request.automergeable? AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params) @@ -201,6 +202,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def approve + unless @merge_request.can_approve?(current_user) + return render_404 + end + @approval = @merge_request.approvals.new @approval.user = current_user @@ -286,7 +291,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_request_params params.require(:merge_request).permit( :title, :assignee_id, :source_project_id, :source_branch, - :target_project_id, :target_branch, :milestone_id, + :target_project_id, :target_branch, :milestone_id, :approver_ids, :state_event, :description, :task_num, label_ids: [] ) end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 61689488d1..9efe9704d1 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -64,7 +64,12 @@ class Projects::MilestonesController < Projects::ApplicationController end def destroy - return access_denied! unless can?(current_user, :admin_milestone, @milestone) + return access_denied! unless can?(current_user, :admin_milestone, @project) + + update_params = { milestone: nil } + @milestone.issues.each do |issue| + Issues::UpdateService.new(@project, current_user, update_params).execute(issue) + end @milestone.destroy diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index b659e15f24..92e4bc16d9 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -7,13 +7,15 @@ class Projects::TreeController < Projects::ApplicationController before_action :authorize_download_code! def show + return not_found! unless @repository.commit(@ref) + if tree.entries.empty? if @repository.blob_at(@commit.id, @path) redirect_to( namespace_project_blob_path(@project.namespace, @project, File.join(@ref, @path)) ) and return - else + elsif @path.present? return not_found! end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 8fbd2b31c3..399deb86a0 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,6 +1,6 @@ class ProjectsController < ApplicationController - prepend_before_action :render_go_import, only: [:show] - skip_before_action :authenticate_user!, only: [:show] + prepend_before_filter :render_go_import, only: [:show] + skip_before_action :authenticate_user!, only: [:show, :activity] before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] @@ -195,7 +195,7 @@ class ProjectsController < ApplicationController :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, :wiki_enabled, :merge_requests_template, :visibility_level, :merge_requests_rebase_enabled, :import_url, :last_activity_at, :namespace_id, :avatar, :merge_requests_rebase_default, - :approvals_before_merge + :approvals_before_merge, :approver_ids ) end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 2ac3dd03bd..c148ab2d08 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -68,4 +68,42 @@ module MergeRequestsHelper } ) end + + def render_items_list(items, separator = "and") + items_cnt = items.size + + case items_cnt + when 1 + items.first + when 2 + "#{items.first} #{separator} #{items.last}" + else + last_item = items.pop + "#{items.join(", ")} #{separator} #{last_item}" + end + end + + def render_require_section(merge_request) + str = if merge_request.approvals_left == 1 + "Requires one more approval" + else + "Requires #{merge_request.approvals_left} more approvals" + end + + if merge_request.approvers_left.any? + more_approvals = merge_request.approvals_left - merge_request.approvers_left.count + approvers_names = merge_request.approvers_left.map(&:name) + + case true + when more_approvals > 0 + str << " (from #{render_items_list(approvers_names + ["#{more_approvals} more"])})" + when more_approvals < 0 + str << " (from #{render_items_list(approvers_names, "or")})" + else + str << " (from #{render_items_list(approvers_names)})" + end + end + + str + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f5814a8f9f..d4cdd2ab7e 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -131,8 +131,12 @@ module ProjectsHelper nav_tabs << :snippets end + if can?(current_user, :read_label, project) + nav_tabs << :labels + end + if can?(current_user, :read_milestone, project) - nav_tabs << [:milestones, :labels] + nav_tabs << :milestones end nav_tabs.flatten @@ -284,4 +288,18 @@ module ProjectsHelper def readme_cache_key [@project.id, @project.commit.sha, "readme"].join('-') end + + def round_commit_count(project) + count = project.commit_count + + if count > 10000 + '10000+' + elsif count > 5000 + '5000+' + elsif count > 1000 + '1000+' + else + count + end + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 625f49108c..2d33f3f5e7 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -47,10 +47,11 @@ class Ability end if project && project.public? - [ + rules = [ :read_project, :read_wiki, :read_issue, + :read_label, :read_milestone, :read_project_snippet, :read_project_member, @@ -58,6 +59,8 @@ class Ability :read_note, :download_code ] + + rules - project_disabled_features_rules(project) else group = if subject.kind_of?(Group) subject @@ -118,28 +121,7 @@ class Ability rules -= project_archived_rules end - unless project.issues_enabled - rules -= named_abilities('issue') - end - - unless project.merge_requests_enabled - rules -= named_abilities('merge_request') - end - - unless project.issues_enabled or project.merge_requests_enabled - rules -= named_abilities('label') - rules -= named_abilities('milestone') - end - - unless project.snippets_enabled - rules -= named_abilities('project_snippet') - end - - unless project.wiki_enabled - rules -= named_abilities('wiki') - end - - rules + rules - project_disabled_features_rules(project) end end @@ -221,6 +203,33 @@ class Ability ] end + def project_disabled_features_rules(project) + rules = [] + + unless project.issues_enabled + rules += named_abilities('issue') + end + + unless project.merge_requests_enabled + rules += named_abilities('merge_request') + end + + unless project.issues_enabled or project.merge_requests_enabled + rules += named_abilities('label') + rules += named_abilities('milestone') + end + + unless project.snippets_enabled + rules += named_abilities('project_snippet') + end + + unless project.wiki_enabled + rules += named_abilities('wiki') + end + + rules + end + def group_abilities(user, group) rules = [] diff --git a/app/models/approval.rb b/app/models/approval.rb index 21d4f1a99a..5ac0cab9bc 100644 --- a/app/models/approval.rb +++ b/app/models/approval.rb @@ -1,3 +1,14 @@ +# == Schema Information +# +# Table name: approvals +# +# id :integer not null, primary key +# merge_request_id :integer not null +# user_id :integer not null +# created_at :datetime +# updated_at :datetime +# + class Approval < ActiveRecord::Base belongs_to :user belongs_to :merge_request diff --git a/app/models/approver.rb b/app/models/approver.rb new file mode 100644 index 0000000000..984692658c --- /dev/null +++ b/app/models/approver.rb @@ -0,0 +1,16 @@ +# == Schema Information +# +# Table name: approvers +# +# id :integer not null, primary key +# target_id :integer not null +# target_type :string(255) +# user_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +class Approver < ActiveRecord::Base + belongs_to :target, polymorphic: true + belongs_to :user +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ad8f469306..aa30f119b2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -36,6 +36,7 @@ class MergeRequest < ActiveRecord::Base has_one :merge_request_diff, dependent: :destroy has_many :approvals, dependent: :destroy + has_many :approvers, as: :target, dependent: :destroy after_create :create_merge_request_diff after_update :update_merge_request_diff @@ -239,6 +240,10 @@ class MergeRequest < ActiveRecord::Base execute(self, commit_message) end + def remove_source_branch? + self.should_remove_source_branch && !self.source_project.root_ref?(self.source_branch) && !self.for_fork? + end + def open? opened? || reopened? end @@ -419,16 +424,29 @@ class MergeRequest < ActiveRecord::Base approvals_required - approvals.count end + def approvers_left + user_ids = overall_approvers.map(&:user_id) - approvals.map(&:user_id) + User.where id: user_ids + end + def approvals_required target_project.approvals_before_merge end def requires_approve? - !approvals_required.zero? + approvals_required.nonzero? + end + + def overall_approvers + if approvers.any? + approvers + else + target_project.approvers + end end def approved? - approvals.count >= approvals_required + approvals_left.zero? end def approved_by?(user) @@ -439,6 +457,15 @@ class MergeRequest < ActiveRecord::Base approvals.map(&:user) end + def can_approve?(user) + approvers_left.include?(user) || + (any_approver_allowed? && !approved_by?(user)) + end + + def any_approver_allowed? + approvals_left > approvers_left.count + end + def has_ci? source_project.ci_service && commits.any? end @@ -460,4 +487,10 @@ class MergeRequest < ActiveRecord::Base "Open" end end + + def approver_ids=(value) + value.split(",").map(&:strip).each do |user_id| + approvers.find_or_initialize_by(user_id: user_id, target_id: id) + end + end end diff --git a/app/models/project.rb b/app/models/project.rb index 5084e5e2d6..87e47ae560 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -117,6 +117,7 @@ class Project < ActiveRecord::Base has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy has_many :starrers, through: :users_star_projects, source: :user + has_many :approvers, as: :target, dependent: :destroy has_many :project_group_links, dependent: :destroy has_many :invited_groups, through: :project_group_links, source: :group @@ -511,7 +512,7 @@ class Project < ActiveRecord::Base end if group group.hooks.send(hooks_scope).each do |hook| - hook.async_execute(data) + hook.async_execute(data, hooks_scope.to_s) end end end @@ -702,6 +703,10 @@ class Project < ActiveRecord::Base update_attribute(:repository_size, repository.size) end + def update_commit_count + update_attribute(:commit_count, repository.commit_count) + end + def forks_count ForkedProjectLink.where(forked_from_project_id: self.id).count end @@ -760,4 +765,10 @@ class Project < ActiveRecord::Base def jira_tracker_active? jira_tracker? && jira_service.active end + + def approver_ids=(value) + value.split(",").map(&:strip).each do |user_id| + approvers.find_or_create_by(user_id: user_id, target_id: id) + end + end end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index c284e19fe5..5aaa4e85cb 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -22,8 +22,12 @@ class GitlabCiService < CiService API_PREFIX = "api/v1" prop_accessor :project_url, :token - validates :project_url, presence: true, if: :activated? - validates :token, presence: true, if: :activated? + validates :project_url, + presence: true, + format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated? + validates :token, + presence: true, + format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated? after_save :compose_service_hook, if: :activated? diff --git a/app/models/repository.rb b/app/models/repository.rb index 6262b5c4c9..807b33b2a3 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -94,18 +94,6 @@ class Repository gitlab_shell.rm_tag(path_with_namespace, tag_name) end - def round_commit_count - if commit_count > 10000 - '10000+' - elsif commit_count > 5000 - '5000+' - elsif commit_count > 1000 - '1000+' - else - commit_count - end - end - def branch_names cache.fetch(:branch_names) { raw_repository.branch_names } end @@ -130,28 +118,29 @@ class Repository cache.fetch(:size) { raw_repository.size } end + def cache_keys + %i(size branch_names tag_names commit_count + readme version contribution_guide changelog license) + end + + def build_cache + cache_keys.each do |key| + unless cache.exist?(key) + send(key) + end + end + end + def expire_cache - %i(size branch_names tag_names commit_count graph_log - readme version contribution_guide changelog license).each do |key| + cache_keys.each do |key| cache.expire(key) end end - def graph_log - cache.fetch(:graph_log) do - commits = raw_repository.log(limit: 6000, skip_merges: true, - ref: root_ref) - - commits.map do |rugged_commit| - commit = Gitlab::Git::Commit.new(rugged_commit) - - { - author_name: commit.author_name, - author_email: commit.author_email, - additions: commit.stats.additions, - deletions: commit.stats.deletions, - } - end + def rebuild_cache + cache_keys.each do |key| + cache.expire(key) + send(key) end end @@ -442,8 +431,7 @@ class Repository filename = nil startline = 0 - lines = result.lines - lines.each_with_index do |line, index| + result.each_line.each_with_index do |line, index| if line =~ /^.*:.*:\d+:/ ref, filename, startline = line.split(':') startline = startline.to_i - index @@ -451,11 +439,11 @@ class Repository end end - data = lines.map do |line| - line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') - end + data = "" - data = data.join("") + result.each_line do |line| + data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') + end OpenStruct.new( filename: filename, diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 5ab79c9b29..a96dc3238c 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -43,7 +43,7 @@ module Files def after_commit(sha, branch) commit = repository.commit(sha) - full_ref = 'refs/heads/' + branch + full_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}" old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA GitPushService.new.execute(project, current_user, old_sha, sha, full_ref) end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index de18d0f91a..f679101ac5 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -21,7 +21,6 @@ class GitPushService project.ensure_satellite_exists project.repository.expire_cache - project.update_repository_size if push_remove_branch?(ref, newrev) @push_commits = [] @@ -61,6 +60,7 @@ class GitPushService EventCreateService.new.push(project, user, @push_data) project.execute_hooks(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks) + ProjectCacheWorker.perform_async(project.id) end protected diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 075a6118da..1cc42b0b0a 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -2,15 +2,15 @@ class GitTagPushService attr_accessor :project, :user, :push_data def execute(project, user, oldrev, newrev, ref) - @project, @user = project, user + project.repository.expire_cache + @project, @user = project, user @push_data = build_push_data(oldrev, newrev, ref) EventCreateService.new.push(project, user, @push_data) project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks) - - project.repository.expire_cache + ProjectCacheWorker.perform_async(project.id) true end diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb index df793fc997..db824d452d 100644 --- a/app/services/merge_requests/auto_merge_service.rb +++ b/app/services/merge_requests/auto_merge_service.rb @@ -37,6 +37,14 @@ module MergeRequests # Merge local branches using rugged instead of satellites if sha = commit after_commit(sha, merge_request.target_branch) + + if merge_request.remove_source_branch? + DeleteBranchService.new(merge_request.source_project, current_user).execute(merge_request.source_branch) + end + + true + else + false end end end @@ -55,7 +63,7 @@ module MergeRequests def after_commit(sha, branch) commit = repository.commit(sha) - full_ref = 'refs/heads/' + branch + full_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}" old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA GitPushService.new.execute(project, current_user, old_sha, sha, full_ref) end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index c7d9040281..4d6be724dd 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -74,7 +74,7 @@ module MergeRequests # Reset approvals for merge request # Note: we should reset approvals for merge requests from forks too def reset_approvals_for_merge_requests - if @project.approvals_before_merge.nonzero? && @project.reset_approvers_on_push + if @project.approvals_before_merge.nonzero? && @project.reset_approvals_on_push merge_requests = @project.merge_requests.opened.where(source_branch: @branch_name).to_a merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a merge_requests = filter_merge_requests(merge_requests) diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index b8409f6466..5ab5ffc238 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -8,11 +8,10 @@ = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' = render "events/event/created_project", event: event - else - = cache event do - = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' - - if event.push? - = render "events/event/push", event: event - - elsif event.commented? - = render "events/event/note", event: event - - else - = render "events/event/common", event: event + = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' + - if event.push? + = render "events/event/push", event: event + - elsif event.commented? + = render "events/event/note", event: event + - else + = render "events/event/common", event: event diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml index d65fb52937..d769c91545 100644 --- a/app/views/explore/projects/_project.html.haml +++ b/app/views/explore/projects/_project.html.haml @@ -14,7 +14,7 @@ .repo-info - unless project.empty_repo? - = link_to pluralize(project.repository.round_commit_count, 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch) + = link_to pluralize(round_commit_count(project), 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch) · = link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project) · diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml index 74268c9bde..92dc58c10d 100644 --- a/app/views/profiles/two_factor_auths/new.html.haml +++ b/app/views/profiles/two_factor_auths/new.html.haml @@ -5,7 +5,7 @@ Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code. - More information is available in the #{link_to('documentation', help_page_path('workflow', 'two_factor_authentication'))}. + More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}. %hr diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index a55ee66384..35e2ba957e 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -28,11 +28,40 @@ .col-sm-10 = f.number_field :approvals_before_merge, class: "form-control", min: 0 .help-block - How many users should approve merge request before it can be accepted. 0 - approval is disabled + Number of users to approve a merge request before it can be accepted. 0 - approving is disabled - .form-group.reset-approvers-on-push + .form-group.reset-approvals-on-push .col-sm-offset-2.col-sm-10 .checkbox - = f.label :reset_approvers_on_push do - = f.check_box :reset_approvers_on_push - %span.descr Reset approvers on push + = f.label :reset_approvals_on_push do + = f.check_box :reset_approvals_on_push + %span.descr Reset approvals on push + .help-block Approvals are reset when new data is pushed to the merge request + + .form-group + = f.label :approver_ids, class: 'control-label' do + Approvers + .col-sm-10 + = users_select_tag("project[approver_ids]", multiple: true, class: 'input-large', scope: :all, email_user: true) + .help-block + Add an approver suggestion for each merge request + + .panel.panel-default.prepend-top-10 + .panel-heading + Approvers + %small + (#{@project.approvers.count(:all)}) + %ul.well-list + - @project.approvers.each do |approver| + %li + = link_to approver.user.name, approver.user + .pull-right + = link_to namespace_project_approver_path(@project.namespace, @project, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove approver' do + = icon("sign-out") + Remove + - if @project.approvers.empty? + %li There are no approvers + + +:coffeescript + new UsersSelect() diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index a05cf8034b..adcb87b976 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1,7 +1,7 @@ .project-edit-container .project-edit-errors .project-edit-content - %div + .panel %h3.page-title Project settings %hr diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 1b45bb1af0..b6910c8f79 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -3,43 +3,42 @@ .issue-check = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" - = cache issue do - .issue-title - %span.issue-title-text - = link_to_gfm issue.title, issue_path(issue), class: "row_title" - .issue-labels - - issue.labels.each do |label| - = link_to_label(label, project: issue.project) - .pull-right.light - - if issue.closed? - %span - CLOSED - - if issue.assignee - = link_to_member(@project, issue.assignee, name: false) - - note_count = issue.notes.user.count - - if note_count > 0 -   - %span - %i.fa.fa-comments - = note_count - - else -   - %span.issue-no-comments - %i.fa.fa-comments - = 0 - - .issue-info - = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe - - if issue.votes_count > 0 - = render 'votes/votes_inline', votable: issue - - if issue.milestone + .issue-title + %span.issue-title-text + = link_to_gfm issue.title, issue_path(issue), class: "row_title" + .issue-labels + - issue.labels.each do |label| + = link_to_label(label, project: issue.project) + .pull-right.light + - if issue.closed? + %span + CLOSED + - if issue.assignee + = link_to_member(@project, issue.assignee, name: false) + - note_count = issue.notes.user.count + - if note_count > 0   %span - %i.fa.fa-clock-o - = issue.milestone.title - - if issue.tasks? - %span.task-status - = issue.task_status + %i.fa.fa-comments + = note_count + - else +   + %span.issue-no-comments + %i.fa.fa-comments + = 0 - .pull-right.issue-updated-at - %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} + .issue-info + = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe + - if issue.votes_count > 0 + = render 'votes/votes_inline', votable: issue + - if issue.milestone +   + %span + %i.fa.fa-clock-o + = issue.milestone.title + - if issue.tasks? + %span.task-status + = issue.task_status + + .pull-right.issue-updated-at + %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index b6d9b135c7..faaa85896c 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -31,6 +31,16 @@ %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) + - if @merge_request.open? and @merge_request.source_branch_exists? + .append-bottom-20 + .slead + %span + Fetch the branch with + %strong.label-branch< + git fetch + \ #{@merge_request.source_project.http_url_to_repo} + \ #{@merge_request.source_branch} + = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/widget/show.html.haml" diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml index 8372afa61b..9210798f39 100644 --- a/app/views/projects/merge_requests/branch_from.js.haml +++ b/app/views/projects/merge_requests/branch_from.js.haml @@ -1,2 +1,3 @@ :plain $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}"); + $('.js-timeago').timeago() diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml index f7ede0ded5..32fe2d535f 100644 --- a/app/views/projects/merge_requests/branch_to.js.haml +++ b/app/views/projects/merge_requests/branch_to.js.haml @@ -1,2 +1,3 @@ :plain $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}"); + $('.js-timeago').timeago() diff --git a/app/views/projects/merge_requests/widget/open/_approve.html.haml b/app/views/projects/merge_requests/widget/open/_approve.html.haml index cb2d76d2ee..20dfa25351 100644 --- a/app/views/projects/merge_requests/widget/open/_approve.html.haml +++ b/app/views/projects/merge_requests/widget/open/_approve.html.haml @@ -1,8 +1,8 @@ %div %h4 - Requires #{pluralize(@merge_request.approvals_left, 'more approval')} - %p Each merge request in this project must be approved by #{pluralize(@merge_request.approvals_required, 'person')} before it can be accepted. - - unless @merge_request.approved_by?(current_user) + = render_require_section(@merge_request) + + - if @merge_request.can_approve?(current_user) .append-bottom-10 = form_for [:approve, @project.namespace.becomes(Namespace), @project, @merge_request], method: :post do |f| - = f.submit "Approve Merge Request", class: "btn btn-primary approve-btn" \ No newline at end of file + = f.submit "Approve Merge Request", class: "btn btn-primary approve-btn" diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 14a0580f96..2ce5358fa7 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -5,6 +5,10 @@ %i.fa.fa-pencil-square-o Edit = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close" + = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do + %i.fa.fa-trash-o + Remove + %h4 = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) - if milestone.expired? and not milestone.closed? diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 5947498e37..7b1681df33 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -19,6 +19,9 @@ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped" - else = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" + = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do + %i.fa.fa-trash-o + Remove %hr - if @milestone.issues.any? && @milestone.can_be_closed? diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 5478a887f9..4a1009686c 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -56,10 +56,9 @@ .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} - = cache [note, 'markdown'] do - .note-text - = preserve do - = markdown(note.note, {no_header_anchors: true}) + .note-text + = preserve do + = markdown(note.note, {no_header_anchors: true}) = render 'projects/notes/edit_form', note: note - if note.attachment.url diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 98d9053eb1..769dd68f08 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -6,14 +6,16 @@ = render 'shared/no_ssh' = render 'shared/no_password' -= render 'projects/last_push' +- if prefer_readme? + = render 'projects/last_push' + = render "home_panel" .project-stats %ul.nav.nav-pills %li = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do - = pluralize(number_with_delimiter(@repository.commit_count), 'commit') + = pluralize(number_with_delimiter(@project.commit_count), 'commit') %li = link_to namespace_project_branches_path(@project.namespace, @project) do = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch') diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml index 46990895d3..d1bd5ef968 100644 --- a/app/views/shared/issuable/_context.html.haml +++ b/app/views/shared/issuable/_context.html.haml @@ -8,7 +8,7 @@ - else none .issuable-context-selectbox - - if can?(current_user, :admin_issue, @project) + - if can?(current_user, :"admin_#{issuable.class.to_s.underscore}", @project) = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true) %div.prepend-top-20.clearfix @@ -24,7 +24,7 @@ - else none .issuable-context-selectbox - - if can?(current_user, :admin_issue, @project) + - if can?(current_user, :"admin_#{issuable.class.to_s.underscore}", @project) = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'}) = hidden_field_tag :issuable_context = f.submit class: 'btn hide' diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index e434e1b6b9..f1f271db78 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -81,6 +81,33 @@ = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank - if issuable.is_a?(MergeRequest) + - if @project.approvals_before_merge.nonzero? + .form-group + = f.label :approver_ids, class: 'control-label' do + Approvers + .col-sm-10 + = users_select_tag("merge_request[approver_ids]", multiple: true, class: 'input-large', scope: :all, email_user: true) + .help-block + Merge Request should be approved by these users. + - if @project.approvers.any? + Suggested approver(s): #{render_items_list(@project.approvers.map{ |approver| approver.user.name})}. + You can override the project settings by setting your own list of approvers. + + .panel.panel-default.prepend-top-10 + .panel-heading + Approvers + %small + (#{@merge_request.approvers.count(:all)}) + %ul.well-list + - @merge_request.approvers.each do |approver| + %li + = link_to approver.user.name, approver.user + .pull-right + = link_to namespace_project_merge_request_approver_path(@project.namespace, @project, @merge_request, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove approver' do + = icon("sign-out") + Remove + - if @merge_request.approvers.empty? + %li There are no approvers %hr - if @merge_request.new_record? .form-group diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb new file mode 100644 index 0000000000..55cb6af232 --- /dev/null +++ b/app/workers/project_cache_worker.rb @@ -0,0 +1,15 @@ +class ProjectCacheWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(project_id) + project = Project.find(project_id) + project.update_repository_size + project.update_commit_count + + if project.repository.root_ref + project.repository.build_cache + end + end +end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index e6a50afedb..94832872d1 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -28,7 +28,7 @@ class RepositoryImportWorker project.import_finish project.save project.satellite.create unless project.satellite.exists? - project.update_repository_size + ProjectCacheWorker.perform_async(project.id) Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' end end diff --git a/config/application.rb b/config/application.rb index 7e899cc3b5..a96e22211e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -96,6 +96,7 @@ module Gitlab end redis_config_hash[:namespace] = 'cache:gitlab' + redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever config.cache_store = :redis_store, redis_config_hash # This is needed for gitlab-shell diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb index 2931bd1094..33ad25d220 100644 --- a/config/initializers/7_omniauth.rb +++ b/config/initializers/7_omniauth.rb @@ -14,6 +14,7 @@ if Gitlab::LDAP::Config.enabled? end end +OmniAuth.config.full_host = Settings.gitlab['url'] OmniAuth.config.allowed_request_methods = [:post] #In case of auto sign-in, the GET method is used (users don't get to click on a button) OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present? diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index d422acb31d..6139ddbe6c 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -6,7 +6,8 @@ Doorkeeper.configure do # This block will be called to check whether the resource owner is authenticated or not. resource_owner_authenticator do # Put your resource owner authentication logic here. - # Example implementation: + # Ensure user is redirected to redirect_uri after login + session[:user_return_to] = request.fullpath current_user || redirect_to(new_user_session_url) end diff --git a/config/routes.rb b/config/routes.rb index 3bff1c20fc..61b30d0ebb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -507,6 +507,7 @@ Gitlab::Application.routes.draw do get :branch_to get :update_branches end + resources :approvers, only: :destroy end resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } @@ -519,7 +520,7 @@ Gitlab::Application.routes.draw do end end - resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do + resources :milestones, constraints: { id: /\d+/ } do member do put :sort_issues put :sort_merge_requests @@ -569,6 +570,8 @@ Gitlab::Application.routes.draw do get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } end end + + resources :approvers, only: :destroy end get "/audit_events" => "audit_events#project_log" diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index b25d0dfc70..bba2fc4b18 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -5,7 +5,7 @@ Gitlab::Seeder.quiet do s.email = 'admin@example.com' s.notification_email = 'admin@example.com' s.username = 'root' - s.password = 'password' + s.password = '5iveL!fe' s.admin = true s.projects_limit = 100 s.confirmed_at = DateTime.now diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index 8783977092..8f71198e47 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -11,9 +11,42 @@ Sidekiq::Testing.inline! do 'https://github.com/twitter/flight.git', 'https://github.com/twitter/typeahead.js.git', 'https://github.com/h5bp/html5-boilerplate.git', + 'https://github.com/google/material-design-lite.git', + 'https://github.com/jlevy/the-art-of-command-line.git', + 'https://github.com/FreeCodeCamp/freecodecamp.git', + 'https://github.com/google/deepdream.git', + 'https://github.com/jtleek/datasharing.git', + 'https://github.com/WebAssembly/design.git', + 'https://github.com/airbnb/javascript.git', + 'https://github.com/tessalt/echo-chamber-js.git', + 'https://github.com/atom/atom.git', + 'https://github.com/ipselon/react-ui-builder.git', + 'https://github.com/mattermost/platform.git', + 'https://github.com/purifycss/purifycss.git', + 'https://github.com/facebook/nuclide.git', + 'https://github.com/wbkd/awesome-d3.git', + 'https://github.com/kilimchoi/engineering-blogs.git', + 'https://github.com/gilbarbara/logos.git', + 'https://github.com/gaearon/redux.git', + 'https://github.com/awslabs/s2n.git', + 'https://github.com/arkency/reactjs_koans.git', + 'https://github.com/twbs/bootstrap.git', + 'https://github.com/chjj/ttystudio.git', + 'https://github.com/DrBoolean/mostly-adequate-guide.git', + 'https://github.com/octocat/Spoon-Knife.git', + 'https://github.com/opencontainers/runc.git', + 'https://github.com/googlesamples/android-topeka.git' ] - project_urls.each_with_index do |url, i| + # You can specify how many projects you need during seed execution + size = if ENV['SIZE'].present? + ENV['SIZE'].to_i + else + 8 + end + + + project_urls.first(size).each_with_index do |url, i| group_path, project_path = url.split('/')[-2..-1] group = Group.find_by(path: group_path) diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index 1af8dfc0ef..1c8740f6ba 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,5 +1,5 @@ if ENV['GITLAB_ROOT_PASSWORD'].blank? - password = 'password' + password = '5iveL!fe' expire_time = Time.now else password = ENV['GITLAB_ROOT_PASSWORD'] diff --git a/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb index ffa22e6d5e..61ff0af41f 100644 --- a/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb +++ b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb @@ -1,5 +1,7 @@ class AddSessionExpireDelayForApplicationSettings < ActiveRecord::Migration def change - add_column :application_settings, :session_expire_delay, :integer, default: 10080, null: false + unless column_exists?(:application_settings, :session_expire_delay) + add_column :application_settings, :session_expire_delay, :integer, default: 10080, null: false + end end -end \ No newline at end of file +end diff --git a/db/migrate/20150707222220_add_approvers_table.rb b/db/migrate/20150707222220_add_approvers_table.rb new file mode 100644 index 0000000000..4af7fe1c5c --- /dev/null +++ b/db/migrate/20150707222220_add_approvers_table.rb @@ -0,0 +1,14 @@ +class AddApproversTable < ActiveRecord::Migration + def change + create_table :approvers do |t| + t.integer :target_id, null: false + t.string :target_type + t.integer :user_id, null: false + + t.timestamps + + t.index [:target_id, :target_type] + t.index :user_id + end + end +end diff --git a/db/migrate/20150717130904_add_commits_count_to_project.rb b/db/migrate/20150717130904_add_commits_count_to_project.rb new file mode 100644 index 0000000000..9b46daa593 --- /dev/null +++ b/db/migrate/20150717130904_add_commits_count_to_project.rb @@ -0,0 +1,5 @@ +class AddCommitsCountToProject < ActiveRecord::Migration + def change + add_column :projects, :commit_count, :integer, default: 0 + end +end diff --git a/db/migrate/20150717155058_rename_reset_approvers.rb b/db/migrate/20150717155058_rename_reset_approvers.rb new file mode 100644 index 0000000000..cf7e74b659 --- /dev/null +++ b/db/migrate/20150717155058_rename_reset_approvers.rb @@ -0,0 +1,5 @@ +class RenameResetApprovers < ActiveRecord::Migration + def change + rename_column :projects, :reset_approvers_on_push, :reset_approvals_on_push + end +end diff --git a/db/schema.rb b/db/schema.rb index 97edb560f4..3754b72bf8 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: 20150713160110) do +ActiveRecord::Schema.define(version: 20150717155058) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -57,6 +57,17 @@ ActiveRecord::Schema.define(version: 20150713160110) do t.datetime "updated_at" end + create_table "approvers", force: true do |t| + t.integer "target_id", null: false + t.string "target_type" + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "approvers", ["target_id", "target_type"], name: "index_approvers_on_target_id_and_target_type", using: :btree + add_index "approvers", ["user_id"], name: "index_approvers_on_user_id", using: :btree + create_table "audit_events", force: true do |t| t.integer "author_id", null: false t.string "type", null: false @@ -443,7 +454,8 @@ ActiveRecord::Schema.define(version: 20150713160110) do t.string "import_source" t.boolean "merge_requests_rebase_default", default: true t.integer "approvals_before_merge", default: 0, null: false - t.boolean "reset_approvers_on_push", default: true + t.boolean "reset_approvals_on_push", default: true + t.integer "commit_count", default: 0 end add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 7b0873a911..bb551fc67f 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -49,7 +49,8 @@ Parameters: "state": "active", "created_at": "2012-04-29T08:46:00Z" }, - "description":"fixed login page css paddings" + "description":"fixed login page css paddings", + "work_in_progress": false } ] ``` @@ -94,7 +95,8 @@ Parameters: "state": "active", "created_at": "2012-04-29T08:46:00Z" }, - "description":"fixed login page css paddings" + "description":"fixed login page css paddings", + "work_in_progress": false } ``` @@ -118,6 +120,7 @@ Parameters: "project_id": 4, "title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.", "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.", + "work_in_progress": false, "state": "reopened", "created_at": "2015-02-02T19:49:39.159Z", "updated_at": "2015-02-02T20:08:49.959Z", @@ -336,14 +339,6 @@ Parameters: ```json { - "author": { - "id": 1, - "username": "admin", - "email": "admin@example.com", - "name": "Administrator", - "blocked": false, - "created_at": "2012-04-29T08:46:00Z" - }, "note": "text1" } ``` diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 3e4400b544..d6272cd591 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -13,3 +13,7 @@ Step-by-step guides on the basics of working with Git and GitLab. * [Create a project](create-project.md) * [Create a group](create-group.md) + +* [Create a branch](create-branch.md) + +* [Fork a project](fork-project.md) diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md index ed210ba542..2b5767dd2d 100644 --- a/doc/gitlab-basics/basic-git-commands.md +++ b/doc/gitlab-basics/basic-git-commands.md @@ -1,58 +1,58 @@ # Basic Git commands -* Go to the master branch to pull the latest changes from there +### Go to the master branch to pull the latest changes from there ``` git checkout master ``` -* Download the latest changes in the project, so that you work on an up-to-date copy (this is important to do every time you work on a project), while you setup tracking branches +### Download the latest changes in the project +This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches. ``` git pull REMOTE NAME-OF-BRANCH -u ``` (REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch) -* Create a branch (remember that spaces won't be recognized, you need to use a hyphen or underscore) +### Create a branch +Spaces won't be recognized, so you need to use a hyphen or underscore. ``` git checkout -b NAME-OF-BRANCH ``` -* Work on a branch that has already been created +### Work on a branch that has already been created ``` git checkout NAME-OF-BRANCH ``` -* To see the changes you've made (it's important to be aware of what's happening and what's the status of your changes) +### View the changes you've made +It's important to be aware of what's happening and what's the status of your changes. ``` git status ``` -* Add changes to commit (you'll be able to see your changes in red when you type "git status") +### Add changes to commit +You'll see your changes in red when you type "git status". ``` git add CHANGES IN RED git commit -m "DESCRIBE THE INTENTION OF THE COMMIT" ``` -* Send changes to gitlab.com +### Send changes to gitlab.com ``` -git push origin NAME-OF-BRANCH +git push REMOTE NAME-OF-BRANCH ``` -* Throw away all changes in the Git repository, but leave unstaged things +### Delete all changes in the Git repository, but leave unstaged things ``` git checkout . ``` -* Delete all changes in the Git repository, including untracked files +### Delete all changes in the Git repository, including untracked files ``` git clean -f ``` -* Remove all the changes that you don't want to send to gitlab.com -``` -git add NAME-OF-FILE -all -``` - -* Merge created branch with master branch. You need to be in the created branch +### Merge created branch with master branch +You need to be in the created branch. ``` git checkout NAME-OF-BRANCH git merge master diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index a596bf20c7..a8223a9b16 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -2,46 +2,47 @@ ## Start working on your project -* In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com) +In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com). -* When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen +When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. ![Select a project](basicsimages/select_project.png) -* To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step) +To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). ![Copy the HTTPS or SSH](basicsimages/https.png) ## On the command line -* To clone your project, go to your computer's shell and type the following command +### Clone your project +Go to your computer's shell and type the following command: ``` git clone PASTE HTTPS OR SSH HERE ``` -* A clone of the project will be created in your computer +A clone of the project will be created in your computer. -* Go into a project, directory or file to work in it +### Go into a project, directory or file to work in it ``` cd NAME-OF-PROJECT-OR-FILE ``` -* Go back one directory or file +### Go back one directory or file ``` cd ../ ``` -* To see what’s in the directory that you are in +### View what’s in the directory that you are in ``` ls ``` -* Create a directory +### Create a directory ``` mkdir NAME-OF-YOUR-DIRECTORY ``` -* Create a README.md or file in directory +### Create a README.md or file in directory ``` touch README.md nano README.md @@ -51,22 +52,23 @@ nano README.md #### Press: enter ``` -* Remove a file +### Remove a file ``` rm NAME-OF-FILE ``` -* Remove a directory and all of its contents +### Remove a directory and all of its contents ``` rm -rf NAME-OF-DIRECTORY ``` -* View history in the command line +### View history in the command line ``` history ``` -* Carry out commands for which the account you are using lacks authority. (You will be asked for an administrator’s password) +### Carry out commands for which the account you are using lacks authority +You will be asked for an administrator’s password. ``` sudo ``` diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md new file mode 100644 index 0000000000..7556b0f663 --- /dev/null +++ b/doc/gitlab-basics/create-branch.md @@ -0,0 +1,39 @@ +# How to create a branch + +A branch is an independent line of development. + +New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project. + +To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab. + +To create a new branch in GitLab, sign in and then select a project on the right side of your screen: + +![Select a project](basicsimages/select_project.png) + +Click on "commits" on the menu on the left side of your screen: + +![Commits](basicsimages/commits.png) + +Click on the "branches" tab: + +![Branches](basicsimages/branches.png) + +Click on the "new branch" button on the right side of the screen: + +![New branch](basicsimages/newbranch.png) + +Fill out the information required: + +1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores) + +1. On the "create from" space, add the the name of the branch you want to branch off from + +1. Click on the button "create branch" + +![Branch info](basicsimages/branch_info.png) + +### Note: + +You will be able to find and select the name of your branch in the white box next to a project's name: + +![Branch name](basicsimages/branch_name.png) diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index e3963f6601..90d40cb6c5 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -1,14 +1,12 @@ # How to create a project in GitLab -## Create a project +To create a new project, sign in to [GitLab.com](https://gitlab.com). -* Sign in to [GitLab.com](https://gitlab.com) - -* Go to your Dashboard and click on "new project" on the right side of your screen +Go to your Dashboard and click on "new project" on the right side of your screen. ![Create a project](basicsimages/new_project.png) -* Fill out the required information +Fill out the required information: 1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores) diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index e1acc2bd65..dcd3e6ffb3 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -4,34 +4,34 @@ You need to connect your computer to your GitLab account through SSH Keys. They ## Generate your SSH Key -* Create an account on GitLab. Sign up and check your email for your confirmation link +Create an account on GitLab. Sign up and check your email for your confirmation link. -* After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account +After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account. ## Add your SSH Key -* At the top right corner, click on "profile settings" +At the top right corner, click on "profile settings": ![profile settings](basicsimages/profile_settings.png) -* On the left side menu click on "SSH Keys" +On the left side menu click on "SSH Keys": ![SSH Keys](basicsimages/shh_keys.png) -* Then click on the green button "Add SSH Key" +Then click on the green button "Add SSH Key": ![Add SSH Key](basicsimages/add_sshkey.png) -* There, you should paste the SSH Key that your commandline will generate for you. Below you'll find the steps to generate it +There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: ![Paste SSH Key](basicsimages/paste_sshkey.png) -## To generate an SSH Key on your commandline +## To generate an SSH Key on your command line -* Go to your [commandline](start-using-git.md) and follow the [instructions](https://gitlab.com/help/ssh/README) to generate it +Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it. -* Copy the SSH Key that your commandline created and paste it on the "Key" box on the GitLab page. The title will be added automatically +Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. ![Paste SSH Key](basicsimages/key.png) -* Now, you'll be able to use Git over SSH, instead of Git over HTTP. +Now, you'll be able to use Git over SSH, instead of Git over HTTP. diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md new file mode 100644 index 0000000000..5173aae2c0 --- /dev/null +++ b/doc/gitlab-basics/fork-project.md @@ -0,0 +1,19 @@ +# How to fork a project + +A fork is a copy of an original repository that you can put somewhere else +or where you can experiment and apply changes that you can later decide if +publishing or not, without affecting your original project. + +It takes just a few steps to fork a project in GitLab. + +Sign in to [gitlab.com](https://gitlab.com). + +Select a project on the right side of your screen: + +![Select a project](basicsimages/select_project.png) + +Click on the "fork" button on the right side of your screen: + +![Fork](basicsimages/fork.png) + +Click on the user or group to where you'd like to add the forked project. diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index 21d93ed2e4..5b1c6c1cd4 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -1,10 +1,10 @@ -# Start using Git on the commandline +# Start using Git on the command line -If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/) +If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/). ## Open a shell -* Depending on your operating system, find the shell of your preference. Here are some suggestions +Depending on your operating system, find the shell of your preference. Here are some suggestions. - [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX @@ -14,54 +14,48 @@ If you want to start using a Git and GitLab, make sure that you have created an ## Check if Git has already been installed -* Git is usually preinstalled on Mac and Linux - -* Type the following command and then press enter +Git is usually preinstalled on Mac and Linux. +Type the following command and then press enter: ``` git --version ``` -* You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). -* If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window +If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window. -* After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed +After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed. ## Add your Git username and set your email -* It is important because every Git commit that you create will use this information - -* On your shell, type the following command to add your username +It is important because every Git commit that you create will use this information. +On your shell, type the following command to add your username: ``` git config --global user.name ADD YOUR USERNAME ``` -* Then verify that you have the correct username - +Then verify that you have the correct username: ``` git config --global user.name ``` -* To set your email address, type the following command - +To set your email address, type the following command: ``` git config --global user.email ADD YOUR EMAIL ``` -* To verify that you entered your email correctly, type - +To verify that you entered your email correctly, type: ``` git config --global user.email ``` -* You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project +You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project. ## Check your information -* To view the information that you entered, type - +To view the information that you entered, type: ``` git config --global --list ``` diff --git a/doc/install/installation.md b/doc/install/installation.md index a3c0505e2a..d8303a905a 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -404,7 +404,7 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in: root - password + 5iveL!fe **Important Note:** On login you'll be prompted to change the password. diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md index fb215c8b26..f60ce35d3e 100644 --- a/doc/profile/two_factor_authentication.md +++ b/doc/profile/two_factor_authentication.md @@ -63,5 +63,10 @@ your phone's application or a recovery code to log in. 1. Go to **Account**. 1. Click **Disable Two-factor Authentication**. +## Note to GitLab administrators + +You need to take special care to that 2FA keeps working after +[restoring a GitLab backup](../raketasks/backup_restore.md). + [Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en [FreeOTP]: https://fedorahosted.org/freeotp/ diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 39a13b14fb..36ab2b9195 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -9,6 +9,13 @@ This archive will be saved in backup_path (see `config/gitlab.yml`). The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup. You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1. +You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json` +(for omnibus packages) or `/home/git/gitlab/.secret` (for installations +from source). This file contains the database encryption key used +for two-factor authentication. If you restore a GitLab backup without +restoring the database encryption key, users who have two-factor +authentication enabled will loose access to your GitLab server. + If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)* ``` @@ -143,15 +150,39 @@ with the name of your bucket: ## Storing configuration files -Please be informed that a backup does not store your configuration files. +Please be informed that a backup does not store your configuration +files. One reason for this is that your database contains encrypted +information for two-factor authentication. Storing encrypted +information along with its key in the same place defeats the purpose +of using encryption in the first place! + If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration). If you have a cookbook installation there should be a copy of your configuration in Chef. -If you have an installation from source, please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). +If you have an installation from source, please consider backing up your `.secret` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). + +At the very **minimum** you should backup `/etc/gitlab/gitlab-secrets.json` +(Omnibus) or `/home/git/gitlab/.secret` (source) to preserve your +database encryption key. ## Restore a previously created backup You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1. +### Prerequisites + +You need to have a working GitLab installation before you can perform +a restore. This is mainly because the system user performing the +restore actions ('git') is usually not allowed to create or delete +the SQL database it needs to import data into ('gitlabhq_production'). +All existing data will be either erased (SQL) or moved to a separate +directory (repositories, uploads). + +If some or all of your GitLab users are using two-factor authentication +(2FA) then you must also make sure to restore +`/etc/gitlab/gitlab-secrets.json` (Omnibus) or `/home/git/gitlab/.secret` +(installations from source). Note that you need to run `gitlab-ctl +reconfigure` after changing `gitlab-secrets.json`. + ### Installation from source ``` diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 3bc9218721..ca9696e957 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -6,6 +6,7 @@ It starts 7 working days before the release. The release manager doesn't have to perform all the work but must ensure someone is assigned. The current release manager must schedule the appointment of the next release manager. The new release manager should create overall issue to track the progress. +The release manager should be the only person pushing/merging commits to the x-y-stable branches. ## Release Manager diff --git a/doc/workflow/README.md b/doc/workflow/README.md index f938a74e3c..a61d38d3fe 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -1,6 +1,5 @@ # Workflow -- [Approval for merge requests](approval_for_merge_requests.md) - [Authorization for merge requests](authorization_for_merge_requests.md) - [Change your time zone](timezone.md) - [Feature branch workflow](workflow.md) @@ -10,7 +9,8 @@ - [Keyboard shortcuts](shortcuts.md) - [Labels](labels.md) - [Manage large binaries with git annex](git_annex.md) -- [Notifications](notifications.md) +- [Merge Request Approvals](merge_request_approvals.md) +- [Notification emails](notifications.md) - [Project Features](project_features.md) - [Project forking workflow](forking_workflow.md) - [Protected branches](protected_branches.md) diff --git a/doc/workflow/approval_for_merge_requests.md b/doc/workflow/approval_for_merge_requests.md deleted file mode 100644 index a093777c74..0000000000 --- a/doc/workflow/approval_for_merge_requests.md +++ /dev/null @@ -1,18 +0,0 @@ -# Approval for merge requests - -Usually you accept merge request if it is ready and reviewed. But in certain -cases you want to be sure that every merge request will be reviewed by several -people before it gets merged. You can use GitLab approve feature for enforcing such -workflow. To enable it you should go to project settings page and set -"Approvals required" field to numeric value. For example if you set it to number 3 -that means each merge request should receive 3 approves from different people -before it can be merged via user interface. - - -![approve_setting](approval_for_merge_requests/settings.png) - -After you set approves value you can see on merge request page "Approve" button -instead of "Accept" one. As soon as merge request get enough approvals it will -be available for merge with user interface. - -![approve_merge_request](approval_for_merge_requests/mr.png) diff --git a/doc/workflow/approval_for_merge_requests/mr.png b/doc/workflow/approval_for_merge_requests/mr.png deleted file mode 100644 index 59addfd7f9..0000000000 Binary files a/doc/workflow/approval_for_merge_requests/mr.png and /dev/null differ diff --git a/doc/workflow/approval_for_merge_requests/settings.png b/doc/workflow/approval_for_merge_requests/settings.png deleted file mode 100644 index d3ddda49ad..0000000000 Binary files a/doc/workflow/approval_for_merge_requests/settings.png and /dev/null differ diff --git a/doc/workflow/merge_request_approvals.md b/doc/workflow/merge_request_approvals.md new file mode 100644 index 0000000000..f77886e011 --- /dev/null +++ b/doc/workflow/merge_request_approvals.md @@ -0,0 +1,66 @@ +# Merge Request Approvals + +If you want to make sure every merge request is approved by one or more +people, you can enforce this workflow by using merge request approvals. +Merge request approvals allow you to set the number of necessary approvals +and predefine a list of approvers that will need to approve every +merge request in a project. + +![Merge request approval](merge_request_approvals/2_approvals.png) + +## Configuring Approvals + +You can configure the approvals in the project settings, under merge requests. + +![Merge Request Approvals in Project Settings](merge_request_approvals/approvals_settings.png) + +To enable it, set **Approvals required** to 1 or higher. + +### Approvals Required + +This sets the amount of approvals required before being able to merge a merge request. +At 0, this disables the feature. Any value above requires that amount of different +users to approve the merge request. + +### Reset approvals on push + +With this setting turned on, approvals are reset when a new push +is done to the merge request branch. + +Turn "Reset approvals on push" off if you want approvals to persist, +independent of changes to the merge request. + +### Approvers + +At approvers you can define the default set of users that need to approve a +merge request. + +If there are more approvers than required approvals, any subset of these users +can approve the merge request. + +If there are less approvers than required approvals, all the set approvers plus +any other user(s) need to approve the merge request before being able to merge it. + +![One specific user and someone else need to approve this merge request](merge_request_approvals/1_named_approval.png) + +If the approvers are equal to the amount of required approvals, all the approvers are +required to approve merge requests. + +![Two specific users need to approve this merge request](merge_request_approvals/2_named_approvals.png) + +Note that approvers can be changed during merge request creation. + +## Using Approvals + +After configuring Approvals, you will see the following during merge request creation. + +![Choosing approvers in merge request creation](merge_request_approvals/approvals_mr.png) + +You can change the default set of approvers before creating the merge request. +You can't change the amount of required approvals. This ensures that you're +not forced to adjust settings when someone is unavailable for approval, yet +the process is still enforced. + +To approve a merge request, simply press the button. + +![Merge request approval](merge_request_approvals/2_approvals.png) \ No newline at end of file diff --git a/doc/workflow/merge_request_approvals/1_named_approval.png b/doc/workflow/merge_request_approvals/1_named_approval.png new file mode 100644 index 0000000000..9757dd81d1 Binary files /dev/null and b/doc/workflow/merge_request_approvals/1_named_approval.png differ diff --git a/doc/workflow/merge_request_approvals/2_approvals.png b/doc/workflow/merge_request_approvals/2_approvals.png new file mode 100644 index 0000000000..14f631ca02 Binary files /dev/null and b/doc/workflow/merge_request_approvals/2_approvals.png differ diff --git a/doc/workflow/merge_request_approvals/2_named_approvals.png b/doc/workflow/merge_request_approvals/2_named_approvals.png new file mode 100644 index 0000000000..a4e275c61a Binary files /dev/null and b/doc/workflow/merge_request_approvals/2_named_approvals.png differ diff --git a/doc/workflow/merge_request_approvals/approvals_mr.png b/doc/workflow/merge_request_approvals/approvals_mr.png new file mode 100644 index 0000000000..6a1041105e Binary files /dev/null and b/doc/workflow/merge_request_approvals/approvals_mr.png differ diff --git a/doc/workflow/merge_request_approvals/approvals_settings.png b/doc/workflow/merge_request_approvals/approvals_settings.png new file mode 100644 index 0000000000..29502ce2a2 Binary files /dev/null and b/doc/workflow/merge_request_approvals/approvals_settings.png differ diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index 17215de677..2b5f06dd1f 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -1,6 +1,6 @@ -# GitLab Notifications +# GitLab Notification Emails -GitLab has notifications system in place to notify a user of events important for the workflow. +GitLab has a notification system in place to notify a user of events that are important for the workflow. ## Notification settings @@ -67,5 +67,3 @@ Below is the table of events users can be notified of: | Reopen merge request | Project members [1] | [1] higher than participating | | Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating | | New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating | - - diff --git a/docker/Dockerfile b/docker/Dockerfile index 86f6c896a6..05521af696 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -30,7 +30,9 @@ RUN ( \ echo "" && \ echo "# Docker options" && \ echo "# Prevent Postgres from trying to allocate 25% of total memory" && \ - echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb + echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb && \ + mkdir -p /assets/ && \ + cp /etc/gitlab/gitlab.rb /assets/gitlab.rb # Expose web & ssh EXPOSE 443 80 22 diff --git a/docker/README.md b/docker/README.md index 293ca79f91..e4d56cdb33 100644 --- a/docker/README.md +++ b/docker/README.md @@ -10,7 +10,7 @@ It might take a while before the docker container is responding to queries. You can check the status with something like `sudo docker logs -f gitlab`. -You can login to the web interface with username `root` and password `password`. +You can login to the web interface with username `root` and password `5iveL!fe`. Next time, you can just use docker start and stop to run the container. @@ -165,3 +165,5 @@ sudo docker push gitlab/gitlab-ce:latest Please see the [troubleshooting](troubleshooting.md) file in this directory. Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it. + +Our docker image runs chef at every start to generate GitLab configuration. diff --git a/docker/assets/wrapper b/docker/assets/wrapper index 966b2cab4a..8bc8370fbc 100755 --- a/docker/assets/wrapper +++ b/docker/assets/wrapper @@ -13,4 +13,9 @@ function entrypoint() { gitlab-ctl tail # tail all logs } +if [[ ! -e /etc/gitlab/gitlab.rb ]]; then + cp /assets/gitlab.rb /etc/gitlab/gitlab.rb + chmod 0600 /etc/gitlab/gitlab.rb +fi + entrypoint diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature index 9ac65b1257..bfbaaec5a3 100644 --- a/features/project/issues/milestones.feature +++ b/features/project/issues/milestones.feature @@ -17,6 +17,10 @@ Feature: Project Issues Milestones And I submit new milestone "v2.3" Then I should see milestone "v2.3" + Scenario: I delete new milestone + Given I click link to remove milestone "v2.2" + And I should see no milestones + @javascript Scenario: Listing closed issues Given the milestone has open and closed issues diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 2d236601b7..ee222ccf66 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -240,6 +240,21 @@ Feature: Project Merge Requests When I click link "Approve" Then I should see approved merge request "Bug NS-04" + Scenario: I approve merge request if I am an approver + Given merge request 'Bug NS-04' must be approved by current user + And I click link "Bug NS-04" + And I should not see merge button + And I should see message that MR require an approval from me + When I click link "Approve" + Then I should see approved merge request "Bug NS-04" + + Scenario: I can not approve merge request if I am not an approver + Given merge request 'Bug NS-04' must be approved by some user + And I click link "Bug NS-04" + And I should not see merge button + When I should not see Approve button + And I should see message that MR require an approval + Scenario: I should see rebase checkbox Given project "Shop" have "Bug NS-05" open merge request with diffs inside And rebase before merge enabled diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb index 708c524394..61e62c2adb 100644 --- a/features/steps/project/issues/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -56,4 +56,12 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps step 'I should see 3 issues' do expect(page).to have_selector('#tab-issues li.issue-row', count: 4) end + + step 'I click link to remove milestone "v2.2"' do + click_link 'Remove' + end + + step 'I should see no milestones' do + expect(page).to have_content('No milestones to show') + end end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 358c0ab19f..47b53aeaad 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -346,6 +346,24 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps project.save! end + step 'merge request \'Bug NS-04\' must be approved by current user' do + merge_request = MergeRequest.find_by!(title: "Bug NS-04") + project = merge_request.target_project + project.approvals_before_merge = 1 + project.approvers.create(user_id: current_user.id) + project.ensure_satellite_exists + project.save! + end + + step 'merge request \'Bug NS-04\' must be approved by some user' do + merge_request = MergeRequest.find_by!(title: "Bug NS-04") + project = merge_request.target_project + project.approvals_before_merge = 1 + project.approvers.create(user_id: create(:user).id) + project.ensure_satellite_exists + project.save! + end + step 'I click link "Approve"' do page.within '.mr-state-widget' do click_button 'Approve Merge Request' @@ -358,12 +376,30 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end end + step 'I should not see Approve button' do + page.within '.mr-state-widget' do + expect(page).not_to have_button("Approve") + end + end + step 'I should see approved merge request "Bug NS-04"' do page.within '.mr-state-widget' do expect(page).to have_button("Accept Merge Request") end end + step 'I should see message that MR require an approval from me' do + page.within '.mr-state-widget' do + expect(page).to have_content("Requires one more approval (from #{current_user.name})") + end + end + + step 'I should see message that MR require an approval' do + page.within '.mr-state-widget' do + expect(page).to have_content("Requires one more approval") + end + end + step 'rebase before merge enabled' do project = merge_request.target_project project.merge_requests_rebase_enabled = true diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9c15dc9b9f..b96a122743 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -181,6 +181,7 @@ module API expose :source_project_id, :target_project_id expose :label_names, as: :labels expose :description + expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone end diff --git a/lib/backup/database.rb b/lib/backup/database.rb index b8aa6b9ff2..c5a5396cbb 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -25,6 +25,7 @@ module Backup abort 'Backup failed' unless success $progress.print 'Compressing database ... ' + FileUtils.rm_f db_file_name_gz success = system('gzip', db_file_name) report_success(success) abort 'Backup failed: compress error' unless success diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 70bfe05977..03c410726a 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -327,7 +327,7 @@ module Gitlab link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}" text = "[#{filename}](#{link})" - text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/ + text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/i text end.compact end diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb index a4e113fa1b..df9790981c 100644 --- a/lib/gitlab/satellite/merge_action.rb +++ b/lib/gitlab/satellite/merge_action.rb @@ -187,7 +187,7 @@ module Gitlab end def remove_source_branch(repo) - if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch) && !merge_request.for_fork? + if merge_request.remove_source_branch? # will raise CommandFailed when push fails repo.git.push(default_options, :origin, ":#{merge_request.source_branch}") end diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb index fa016a170c..8ddc351129 100644 --- a/lib/repository_cache.rb +++ b/lib/repository_cache.rb @@ -18,4 +18,12 @@ class RepositoryCache def fetch(key, &block) backend.fetch(cache_key(key), &block) end + + def exist?(key) + backend.exist?(cache_key(key)) + end + + def read(key) + backend.read(cache_key(key)) + end end diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 946902e2f6..a3455728a9 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -41,7 +41,7 @@ shell_path="/bin/bash" test -f /etc/default/gitlab && . /etc/default/gitlab # Switch to the app_user if it is not he/she who is running the script. -if [ "$USER" != "$app_user" ]; then +if [ `whoami` != "$app_user" ]; then eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit; fi diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb new file mode 100644 index 0000000000..d3868c1320 --- /dev/null +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Projects::MilestonesController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:milestone) { create(:milestone, project: project) } + let(:issue) { create(:issue, project: project, milestone: milestone) } + + before do + sign_in(user) + project.team << [user, :master] + controller.instance_variable_set(:@project, project) + end + + describe "#destroy" do + it "should remove milestone" do + expect(issue.milestone_id).to eq(milestone.id) + delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js + expect(response).to be_success + expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound) + issue.reload + expect(issue.milestone_id).to eq(nil) + # Check system note left for milestone removal + last_note = project.issues.find(issue.id).notes[-1].note + expect(last_note).to eq('Milestone removed') + end + end +end diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb similarity index 69% rename from spec/controllers/tree_controller_spec.rb rename to spec/controllers/projects/tree_controller_spec.rb index e09caf5df1..5391585635 100644 --- a/spec/controllers/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -8,9 +8,6 @@ describe Projects::TreeController do sign_in(user) project.team << [user, :master] - - allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz']) - allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0']) controller.instance_variable_set(:@project, project) end @@ -44,6 +41,32 @@ describe Projects::TreeController do let(:id) { 'invalid-branch/encoding/' } it { is_expected.to respond_with(:not_found) } end + + context "valid empty branch, invalid path" do + let(:id) { 'empty-branch/invalid-path/' } + it { is_expected.to respond_with(:not_found) } + end + + context "valid empty branch" do + let(:id) { 'empty-branch' } + it { is_expected.to respond_with(:success) } + end + + context "invalid SHA commit ID" do + let(:id) { 'ff39438/.gitignore' } + it { is_expected.to respond_with(:not_found) } + end + + context "valid SHA commit ID" do + let(:id) { '6d39438' } + it { is_expected.to respond_with(:success) } + end + + context "valid SHA commit ID with path" do + let(:id) { '6d39438/.gitignore' } + it { expect(response.status).to eq(302) } + end + end describe 'GET show with blob path' do diff --git a/spec/features/admin/security_spec.rb b/spec/features/security/admin_access_spec.rb similarity index 96% rename from spec/features/admin/security_spec.rb rename to spec/features/security/admin_access_spec.rb index 175fa9d464..fe8cd7b760 100644 --- a/spec/features/admin/security_spec.rb +++ b/spec/features/security/admin_access_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Admin::Projects", feature: true do + include AccessMatchers + describe "GET /admin/projects" do subject { admin_namespaces_projects_path } diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb index 67238e3ab7..c38cddbb90 100644 --- a/spec/features/security/dashboard_access_spec.rb +++ b/spec/features/security/dashboard_access_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Dashboard access", feature: true do + include AccessMatchers + describe "GET /dashboard" do subject { dashboard_path } diff --git a/spec/features/security/group/group_access_spec.rb b/spec/features/security/group/group_access_spec.rb deleted file mode 100644 index 6379314945..0000000000 --- a/spec/features/security/group/group_access_spec.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'spec_helper' - -describe "Group access", feature: true do - describe "GET /projects/new" do - it { expect(new_group_path).to be_allowed_for :admin } - it { expect(new_group_path).to be_allowed_for :user } - it { expect(new_group_path).to be_denied_for :visitor } - end - - describe "Group" do - let(:group) { create(:group) } - - let(:owner) { create(:owner) } - let(:master) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:nonmember) { create(:user) } - - before do - group.add_user(owner, Gitlab::Access::OWNER) - group.add_user(master, Gitlab::Access::MASTER) - group.add_user(reporter, Gitlab::Access::REPORTER) - group.add_user(guest, Gitlab::Access::GUEST) - end - - describe "GET /groups/:path" do - subject { group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/issues" do - subject { issues_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/merge_requests" do - subject { merge_requests_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/group_members" do - subject { group_group_members_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/edit" do - subject { edit_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/projects" do - subject { projects_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - end -end diff --git a/spec/features/security/group/internal_group_access_spec.rb b/spec/features/security/group/internal_group_access_spec.rb deleted file mode 100644 index d17a7412e4..0000000000 --- a/spec/features/security/group/internal_group_access_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'spec_helper' - -describe "Group with internal project access", feature: true do - describe "Group" do - let(:group) { create(:group) } - - let(:owner) { create(:owner) } - let(:master) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:nonmember) { create(:user) } - - before do - group.add_user(owner, Gitlab::Access::OWNER) - group.add_user(master, Gitlab::Access::MASTER) - group.add_user(reporter, Gitlab::Access::REPORTER) - group.add_user(guest, Gitlab::Access::GUEST) - - create(:project, :internal, group: group) - end - - describe "GET /groups/:path" do - subject { group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/issues" do - subject { issues_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/merge_requests" do - subject { merge_requests_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/group_members" do - subject { group_group_members_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - end - - describe "GET /groups/:path/edit" do - subject { edit_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - end -end diff --git a/spec/features/security/group/mixed_group_access_spec.rb b/spec/features/security/group/mixed_group_access_spec.rb deleted file mode 100644 index b3db7b5dea..0000000000 --- a/spec/features/security/group/mixed_group_access_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'spec_helper' - -describe "Group access", feature: true do - describe "Group" do - let(:group) { create(:group) } - - let(:owner) { create(:owner) } - let(:master) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:nonmember) { create(:user) } - - before do - group.add_user(owner, Gitlab::Access::OWNER) - group.add_user(master, Gitlab::Access::MASTER) - group.add_user(reporter, Gitlab::Access::REPORTER) - group.add_user(guest, Gitlab::Access::GUEST) - - create(:project, :internal, path: "internal_project", group: group) - create(:project, :public, path: "public_project", group: group) - end - - describe "GET /groups/:path" do - subject { group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/issues" do - subject { issues_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/merge_requests" do - subject { merge_requests_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/group_members" do - subject { group_group_members_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/edit" do - subject { edit_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - end -end diff --git a/spec/features/security/group/public_group_access_spec.rb b/spec/features/security/group/public_group_access_spec.rb deleted file mode 100644 index c16f0c0d1e..0000000000 --- a/spec/features/security/group/public_group_access_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'spec_helper' - -describe "Group with public project access", feature: true do - describe "Group" do - let(:group) { create(:group) } - - let(:owner) { create(:owner) } - let(:master) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:nonmember) { create(:user) } - - before do - group.add_user(owner, Gitlab::Access::OWNER) - group.add_user(master, Gitlab::Access::MASTER) - group.add_user(reporter, Gitlab::Access::REPORTER) - group.add_user(guest, Gitlab::Access::GUEST) - - create(:project, :public, group: group) - end - - describe "GET /groups/:path" do - subject { group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/issues" do - subject { issues_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/merge_requests" do - subject { merge_requests_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/group_members" do - subject { group_group_members_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - end - - describe "GET /groups/:path/edit" do - subject { edit_group_path(group) } - - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - end - end -end diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb new file mode 100644 index 0000000000..0f36e47498 --- /dev/null +++ b/spec/features/security/group_access_spec.rb @@ -0,0 +1,284 @@ +require 'rails_helper' + +describe 'Group access', feature: true do + include AccessMatchers + + def group + @group ||= create(:group) + end + + def create_project(access_level) + if access_level == :mixed + create(:empty_project, :public, group: group) + create(:empty_project, :internal, group: group) + else + create(:empty_project, access_level, group: group) + end + end + + def group_member(access_level, group = group) + level = Object.const_get("Gitlab::Access::#{access_level.upcase}") + + create(:user).tap do |user| + group.add_user(user, level) + end + end + + describe 'GET /groups/new' do + subject { new_group_path } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end + + describe 'GET /groups/:path' do + subject { group_path(group) } + + context 'with public projects' do + before(:all) { create_project(:public) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with mixed projects' do + before(:all) { create_project(:mixed) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with internal projects' do + before(:all) { create_project(:internal) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end + + context 'with no projects' do + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end + + describe 'GET /groups/:path/issues' do + subject { issues_group_path(group) } + + context 'with public projects' do + before(:all) { create_project(:public) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with mixed projects' do + before(:all) { create_project(:mixed) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with internal projects' do + before(:all) { create_project(:internal) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end + + context 'with no projects' do + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end + + describe 'GET /groups/:path/merge_requests' do + subject { merge_requests_group_path(group) } + + context 'with public projects' do + before(:all) { create_project(:public) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with mixed projects' do + before(:all) { create_project(:mixed) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with internal projects' do + before(:all) { create_project(:internal) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end + + context 'with no projects' do + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end + + describe 'GET /groups/:path/group_members' do + subject { group_group_members_path(group) } + + context 'with public projects' do + before(:all) { create_project(:public) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with mixed projects' do + before(:all) { create_project(:mixed) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + end + + context 'with internal projects' do + before(:all) { create_project(:internal) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end + + context 'with no projects' do + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_allowed_for group_member(:master) } + it { is_expected.to be_allowed_for group_member(:reporter) } + it { is_expected.to be_allowed_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end + + describe 'GET /groups/:path/edit' do + subject { edit_group_path(group) } + + context 'with public projects' do + before(:all) { create_project(:public) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_denied_for group_member(:master) } + it { is_expected.to be_denied_for group_member(:reporter) } + it { is_expected.to be_denied_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + + context 'with mixed projects' do + before(:all) { create_project(:mixed) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_denied_for group_member(:master) } + it { is_expected.to be_denied_for group_member(:reporter) } + it { is_expected.to be_denied_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + + context 'with internal projects' do + before(:all) { create_project(:internal) } + + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_denied_for group_member(:master) } + it { is_expected.to be_denied_for group_member(:reporter) } + it { is_expected.to be_denied_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + + context 'with no projects' do + it { is_expected.to be_allowed_for group_member(:owner) } + it { is_expected.to be_denied_for group_member(:master) } + it { is_expected.to be_denied_for group_member(:reporter) } + it { is_expected.to be_denied_for group_member(:guest) } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + end +end diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index bcabc2d53a..c19678ab38 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -1,18 +1,11 @@ require 'spec_helper' describe "Profile access", feature: true do - before do - @u1 = create(:user) - end - - describe "GET /login" do - it { expect(new_user_session_path).not_to be_not_found_for :visitor } - end + include AccessMatchers describe "GET /profile/keys" do subject { profile_keys_path } - it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } @@ -21,7 +14,6 @@ describe "Profile access", feature: true do describe "GET /profile" do subject { profile_path } - it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } @@ -30,7 +22,6 @@ describe "Profile access", feature: true do describe "GET /profile/account" do subject { profile_account_path } - it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } @@ -39,7 +30,6 @@ describe "Profile access", feature: true do describe "GET /profile/preferences" do subject { profile_preferences_path } - it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } @@ -48,7 +38,6 @@ describe "Profile access", feature: true do describe "GET /profile/audit_log" do subject { audit_log_profile_path } - it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } @@ -57,7 +46,6 @@ describe "Profile access", feature: true do describe "GET /profile/notifications" do subject { profile_notifications_path } - it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 54a09131b5..a82374d8e8 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Internal Project Access", feature: true do + include AccessMatchers + let(:project) { create(:project, :internal) } let(:master) { create(:user) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 45363c5564..10e69ad89b 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Private Project Access", feature: true do + include AccessMatchers + let(:project) { create(:project) } let(:master) { create(:user) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index fb42d478c6..d8ee44dc28 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Public Project Access", feature: true do + include AccessMatchers + let(:project) { create(:project) } let(:master) { create(:user) } @@ -17,7 +19,6 @@ describe "Public Project Access", feature: true do # readonly project.team << [reporter, :reporter] - end describe "Project should be public" do diff --git a/spec/fixtures/GoogleCodeProjectHosting.json b/spec/fixtures/GoogleCodeProjectHosting.json index d05e77271a..67bb3bae5b 100644 --- a/spec/fixtures/GoogleCodeProjectHosting.json +++ b/spec/fixtures/GoogleCodeProjectHosting.json @@ -382,6 +382,11 @@ "fileName" : "screenshot.png", "fileSize" : 0, "mimetype" : "image/png" + }, { + "attachmentId" : "001", + "fileName" : "screenshot1.PNG", + "fileSize" : 0, + "mimetype" : "image/x-png" } ] }, { "id" : 1, diff --git a/spec/helpers/merge_request_helper_spec.rb b/spec/helpers/merge_request_helper_spec.rb index 34c2cd4907..39e14ae2fc 100644 --- a/spec/helpers/merge_request_helper_spec.rb +++ b/spec/helpers/merge_request_helper_spec.rb @@ -40,4 +40,61 @@ describe MergeRequestsHelper do it { is_expected.to eq('#JIRA-123, #JIRA-456, and #FOOBAR-7890') } end end + + describe 'render_items_list' do + it "returns one item in the list" do + expect(render_items_list(["user"])).to eq("user") + end + + it "returns two items in the list" do + expect(render_items_list(["user", "user1"])).to eq("user and user1") + end + + it "returns three items in the list" do + expect(render_items_list(["user", "user1", "user2"])).to eq("user, user1 and user2") + end + end + + describe 'render_require_section' do + it "returns correct value in case - one approval" do + project.update(approvals_before_merge: 1) + merge_request = create(:merge_request, target_project: project, source_project: project) + expect(render_require_section(merge_request)).to eq("Requires one more approval") + end + + it "returns correct value in case - two approval" do + project.update(approvals_before_merge: 2) + merge_request = create(:merge_request, target_project: project, source_project: project) + expect(render_require_section(merge_request)).to eq("Requires 2 more approvals") + end + + it "returns correct value in case - one approver" do + project.update(approvals_before_merge: 1) + merge_request = create(:merge_request, target_project: project, source_project: project) + user = create :user + merge_request.approvers.create(user_id: user.id) + + expect(render_require_section(merge_request)).to eq("Requires one more approval (from #{user.name})") + end + + it "returns correct value in case - one approver and one more" do + project.update(approvals_before_merge: 2) + merge_request = create(:merge_request, target_project: project, source_project: project) + user = create :user + merge_request.approvers.create(user_id: user.id) + + expect(render_require_section(merge_request)).to eq("Requires 2 more approvals (from #{user.name} and 1 more)") + end + + it "returns correct value in case - two approver and one more" do + project.update(approvals_before_merge: 3) + merge_request = create(:merge_request, target_project: project, source_project: project) + user = create :user + user1 = create :user + merge_request.approvers.create(user_id: user.id) + merge_request.approvers.create(user_id: user1.id) + + expect(render_require_section(merge_request)).to eq("Requires 3 more approvals (from #{user1.name}, #{user.name} and 1 more)") + end + end end diff --git a/spec/javascripts/fixtures/line_highlighter.html.haml b/spec/javascripts/fixtures/line_highlighter.html.haml index 15ad1d8968..da1ebcdb23 100644 --- a/spec/javascripts/fixtures/line_highlighter.html.haml +++ b/spec/javascripts/fixtures/line_highlighter.html.haml @@ -2,7 +2,9 @@ .file-content .line-numbers - 1.upto(25) do |i| - %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}= i + %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} + %i.fa.fa-link + = i %pre.code.highlight %code - 1.upto(25) do |i| diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee index 14fa487ff7..57453c716a 100644 --- a/spec/javascripts/line_highlighter_spec.js.coffee +++ b/spec/javascripts/line_highlighter_spec.js.coffee @@ -48,6 +48,14 @@ describe 'LineHighlighter', -> clickLine(13) expect(spy).toHaveBeenPrevented() + it 'handles clicking on a child icon element', -> + spy = spyOn(@class, 'setHash').and.callThrough() + + $('#L13 i').mousedown().click() + + expect(spy).toHaveBeenCalledWith(13) + expect($('#LC13')).toHaveClass(@css) + describe 'without shiftKey', -> it 'highlights one line when clicked', -> clickLine(13) diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index c53ddeb87b..f49cbb7f53 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -65,6 +65,7 @@ describe Gitlab::GoogleCodeImport::Importer do expect(issue.description).to include('all the best!') expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)') expect(issue.description).to include('![screenshot.png](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot.png)') + expect(issue.description).to include('![screenshot1.PNG](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot1.PNG)') end it "imports issue comments" do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c15dbd8e4e..f3621b122c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -175,6 +175,30 @@ describe MergeRequest do end end + describe "approvers_left" do + let(:merge_request) {create :merge_request} + + it "returns correct value" do + user = create(:user) + user1 = create(:user) + merge_request.target_project.approvers.create(user_id: user.id) + merge_request.target_project.approvers.create(user_id: user1.id) + merge_request.approvals.create(user_id: user1.id) + + expect(merge_request.approvers_left).to eq [user] + end + end + + describe "approvals_required" do + let(:merge_request) {create :merge_request} + + it "takes approvals_before_merge" do + merge_request.target_project.update(approvals_before_merge: 2) + + expect(merge_request.approvals_required).to eq 2 + end + end + it_behaves_like 'an editable mentionable' do subject { create(:merge_request, source_project: project) } diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index fedc37c9b9..a14384c87b 100644 --- a/spec/models/project_services/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -26,6 +26,33 @@ describe GitlabCiService do it { is_expected.to have_one(:service_hook) } end + describe 'validations' do + context 'active' do + before { allow(subject).to receive(:activated?).and_return(true) } + + it { is_expected.to validate_presence_of(:token) } + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) } + it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) } + it { is_expected.not_to allow_value('token with spaces').for(:token) } + it { is_expected.not_to allow_value('token/with%spaces').for(:token) } + it { is_expected.not_to allow_value('this is not url').for(:project_url) } + it { is_expected.not_to allow_value('http//noturl').for(:project_url) } + it { is_expected.not_to allow_value('ftp://ci.example.com/projects/3').for(:project_url) } + end + + context 'inactive' do + before { allow(subject).to receive(:activated?).and_return(false) } + + it { is_expected.not_to validate_presence_of(:token) } + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) } + it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) } + it { is_expected.to allow_value('token with spaces').for(:token) } + it { is_expected.to allow_value('ftp://ci.example.com/projects/3').for(:project_url) } + end + end + describe 'commits methods' do before do @service = GitlabCiService.new diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 63091e913f..17ae887eb1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -365,6 +365,23 @@ describe Project do end end + describe :execute_hooks do + it "triggers project and group hooks" do + group = create :group, name: 'gitlab' + project = create(:project, name: 'gitlabhq', namespace: group) + project_hook = create(:project_hook, push_events: true, project: project) + group_hook = create(:group_hook, push_events: true, group: group) + + stub_request(:post, project_hook.url) + stub_request(:post, group_hook.url) + + expect_any_instance_of(ProjectHook).to receive(:async_execute).and_return(true) + expect_any_instance_of(GroupHook).to receive(:async_execute).and_return(true) + + project.execute_hooks({}, :push_hooks) + end + end + describe :avatar_url do subject { project.avatar_url } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index cb6e5e8962..5c1b58535c 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -14,10 +14,13 @@ describe API::API, api: true do describe "GET /projects/:id/repository/branches" do it "should return an array of project branches" do + project.repository.expire_cache + get api("/projects/#{project.id}/repository/branches", user) expect(response.status).to eq(200) expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.repository.branch_names.first) + branch_names = json_response.map { |x| x['name'] } + expect(branch_names).to match_array(project.repository.branch_names) end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index bdb8b3d700..8bfd761ab2 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -89,7 +89,7 @@ describe API::API, api: true do it 'returns projects in the correct order when ci_enabled_first parameter is passed' do [project, project2, project3].each{ |project| project.build_missing_services } - project2.gitlab_ci_service.update(active: true, token: "token", project_url: "url") + project2.gitlab_ci_service.update(active: true, token: "token", project_url: "http://ci.example.com/projects/1") get api('/projects', user), { ci_enabled_first: 'true' } expect(response.status).to eq(200) expect(json_response).to be_an Array diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 51c543578d..6d29a28580 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -7,7 +7,7 @@ describe API::API, api: true do describe "POST /projects/:id/services/gitlab-ci" do it "should update gitlab-ci settings" do - put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secret-token', project_url: "http://ci.example.com/projects/1" + put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secrettoken', project_url: "http://ci.example.com/projects/1" expect(response.status).to eq(200) end @@ -17,6 +17,18 @@ describe API::API, api: true do expect(response.status).to eq(400) end + + it "should return if the format of token is invalid" do + put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "http://ci.example.com/projects/1", active: true + + expect(response.status).to eq(404) + end + + it "should return if the format of token is invalid" do + put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "ftp://ci.example/projects/1", active: true + + expect(response.status).to eq(404) + end end describe "DELETE /projects/:id/services/gitlab-ci" do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 99bab18e74..1d0b5c66c4 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -11,7 +11,7 @@ describe MergeRequests::RefreshService do group = create(:group) group.add_owner(@user) - @project = create(:project, namespace: group, approvals_before_merge: 1, reset_approvers_on_push: true) + @project = create(:project, namespace: group, approvals_before_merge: 1, reset_approvals_on_push: true) @fork_project = Projects::ForkService.new(@project, @user).execute @merge_request = create(:merge_request, source_project: @project, @@ -130,8 +130,8 @@ describe MergeRequests::RefreshService do expect(@merge_request.approvals).not_to be_empty end - it "does not reset approvals if reset_approvers_on_push si disabled" do - @project.update(reset_approvers_on_push: false) + it "does not reset approvals if reset_approvals_on_push si disabled" do + @project.update(reset_approvals_on_push: false) refresh_service = service.new(@project, @user) allow(refresh_service).to receive(:execute_hooks) refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb deleted file mode 100644 index a2f853e3e7..0000000000 --- a/spec/support/matchers.rb +++ /dev/null @@ -1,66 +0,0 @@ -RSpec::Matchers.define :be_valid_commit do - match do |actual| - actual && - actual.id == ValidCommit::ID && - actual.message == ValidCommit::MESSAGE && - actual.author_name == ValidCommit::AUTHOR_FULL_NAME - end -end - -def emulate_user(user) - user = case user - when :user then create(:user) - when :visitor then nil - when :admin then create(:admin) - else user - end - login_with(user) if user -end - -RSpec::Matchers.define :be_allowed_for do |user| - match do |url| - emulate_user(user) - visit url - status_code != 404 && current_path != new_user_session_path - end -end - -RSpec::Matchers.define :be_denied_for do |user| - match do |url| - emulate_user(user) - visit url - status_code == 404 || current_path == new_user_session_path - end -end - -RSpec::Matchers.define :be_not_found_for do |user| - match do |url| - emulate_user(user) - visit url - status_code == 404 - end -end - -RSpec::Matchers.define :include_module do |expected| - match do - described_class.included_modules.include?(expected) - end - - description do - "includes the #{expected} module" - end - - failure_message do - "expected #{described_class} to include the #{expected} module" - end -end - -# Extend shoulda-matchers -module Shoulda::Matchers::ActiveModel - class ValidateLengthOfMatcher - # Shortcut for is_at_least and is_at_most - def is_within(range) - is_at_least(range.min) && is_at_most(range.max) - end - end -end diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb new file mode 100644 index 0000000000..558e8b1612 --- /dev/null +++ b/spec/support/matchers/access_matchers.rb @@ -0,0 +1,54 @@ +# AccessMatchers +# +# The custom matchers contained in this module are used to test a user's access +# to a URL by emulating a specific user or type of user account, visiting the +# URL, and then checking the response status code and resulting path. +module AccessMatchers + extend RSpec::Matchers::DSL + include Warden::Test::Helpers + + def emulate_user(user) + case user + when :user + login_as(create(:user)) + when :visitor + logout + when :admin + login_as(create(:admin)) + when User + login_as(user) + else + raise ArgumentError, "cannot emulate user #{user}" + end + end + + def description_for(user, type) + if user.kind_of?(User) + # User#inspect displays too much information for RSpec's description + # messages + "be #{type} for supplied User" + else + "be #{type} for #{user}" + end + end + + matcher :be_allowed_for do |user| + match do |url| + emulate_user(user) + visit url + status_code != 404 && current_path != new_user_session_path + end + + description { description_for(user, 'allowed') } + end + + matcher :be_denied_for do |user| + match do |url| + emulate_user(user) + visit url + status_code == 404 || current_path == new_user_session_path + end + + description { description_for(user, 'denied') } + end +end diff --git a/spec/support/matchers/include_module.rb b/spec/support/matchers/include_module.rb new file mode 100644 index 0000000000..0a78af1e90 --- /dev/null +++ b/spec/support/matchers/include_module.rb @@ -0,0 +1,13 @@ +RSpec::Matchers.define :include_module do |expected| + match do + described_class.included_modules.include?(expected) + end + + description do + "includes the #{expected} module" + end + + failure_message do + "expected #{described_class} to include the #{expected} module" + end +end diff --git a/spec/support/matchers/is_within.rb b/spec/support/matchers/is_within.rb new file mode 100644 index 0000000000..0c35fc7e89 --- /dev/null +++ b/spec/support/matchers/is_within.rb @@ -0,0 +1,9 @@ +# Extend shoulda-matchers +module Shoulda::Matchers::ActiveModel + class ValidateLengthOfMatcher + # Shortcut for is_at_least and is_at_most + def is_within(range) + is_at_least(range.min) && is_at_most(range.max) + end + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 8bdd6b43cd..dcf2a9e2ce 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -5,6 +5,7 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { + 'empty-branch' => '7efb185', 'flatten-dir' => 'e56497b', 'feature' => '0b4bc9a', 'feature_conflict' => 'bb5206f', @@ -14,9 +15,13 @@ module TestEnv 'master' => '5937ac0' } - FORKED_BRANCH_SHA = BRANCH_SHA.merge({ - 'add-submodule-version-bump' => '3f547c08' - }) + # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily + # need to keep all the branches in sync. + # We currently only need a subset of the branches + FORKED_BRANCH_SHA = { + 'add-submodule-version-bump' => '3f547c08', + 'master' => '5937ac0' + } # Test environment #