Merge remote-tracking branch 'origin/master' into 22191-delete-dynamic-envs-mr

This commit is contained in:
Kamil Trzcinski 2016-10-18 17:48:27 +02:00
commit 19300a1a3d
38 changed files with 508 additions and 220 deletions

View File

@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date.
## 8.13.0 (2016-10-22) ## 8.13.0 (2016-10-22)
- Fix save button on project pipeline settings page. (!6955)
- Avoid race condition when asynchronously removing expired artifacts. (!6881)
- Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675)
- Respond with 404 Not Found for non-existent tags (Linus Thiel) - Respond with 404 Not Found for non-existent tags (Linus Thiel)
- Truncate long labels with ellipsis in labels page - Truncate long labels with ellipsis in labels page
@ -16,6 +18,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix centering of custom header logos (Ashley Dumaine) - Fix centering of custom header logos (Ashley Dumaine)
- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
- Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun)
- Cancelled pipelines could be retried. !6927
- Updating verbiage on git basics to be more intuitive - Updating verbiage on git basics to be more intuitive
- Clarify documentation for Runners API (Gennady Trafimenkov) - Clarify documentation for Runners API (Gennady Trafimenkov)
- The instrumentation for Banzai::Renderer has been restored - The instrumentation for Banzai::Renderer has been restored
@ -53,6 +56,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add Issue Board API support (andrebsguedes) - Add Issue Board API support (andrebsguedes)
- Allow the Koding integration to be configured through the API - Allow the Koding integration to be configured through the API
- Add new issue button to each list on Issues Board - Add new issue button to each list on Issues Board
- Execute specific named route method from toggle_award_url helper method
- Added soft wrap button to repository file/blob editor - Added soft wrap button to repository file/blob editor
- Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms)
- Show the time ago a merge request was deployed to an environment - Show the time ago a merge request was deployed to an environment
@ -62,6 +66,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps)
- Remove redundant mixins (ClemMakesApps) - Remove redundant mixins (ClemMakesApps)
- Added 'Download' button to the Snippets page (Justin DiPierro) - Added 'Download' button to the Snippets page (Justin DiPierro)
- Add visibility level to project repository
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Fix that manual jobs would no longer block jobs in the next stage. !6604 - Fix that manual jobs would no longer block jobs in the next stage. !6604
@ -104,6 +109,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Reduce queries needed to find users using their SSH keys when pushing commits - Reduce queries needed to find users using their SSH keys when pushing commits
- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska)
- Fix broken repository 500 errors in project list - Fix broken repository 500 errors in project list
- Fix the diff in the merge request view when converting a symlink to a regular file
- Fix Pipeline list commit column width should be adjusted - Fix Pipeline list commit column width should be adjusted
- Close todos when accepting merge requests via the API !6486 (tonygambone) - Close todos when accepting merge requests via the API !6486 (tonygambone)
- Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo)

View File

@ -4,9 +4,8 @@
this.ProjectNew = (function() { this.ProjectNew = (function() {
function ProjectNew() { function ProjectNew() {
this.toggleSettings = bind(this.toggleSettings, this); this.toggleSettings = bind(this.toggleSettings, this);
this.$selects = $('.features select').filter(function () { this.$selects = $('.features select');
return $(this).data('field'); this.$repoSelects = this.$selects.filter('.js-repo-select');
});
$('.project-edit-container').on('ajax:before', (function(_this) { $('.project-edit-container').on('ajax:before', (function(_this) {
return function() { return function() {
@ -16,6 +15,7 @@
})(this)); })(this));
this.toggleSettings(); this.toggleSettings();
this.toggleSettingsOnclick(); this.toggleSettingsOnclick();
this.toggleRepoVisibility();
} }
ProjectNew.prototype.toggleSettings = function() { ProjectNew.prototype.toggleSettings = function() {
@ -43,6 +43,38 @@
} }
}; };
ProjectNew.prototype.toggleRepoVisibility = function () {
var $repoAccessLevel = $('.js-repo-access-level select');
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
.nextAll()
.hide();
$repoAccessLevel.off('change')
.on('change', function () {
var selectedVal = parseInt($repoAccessLevel.val());
this.$repoSelects.each(function () {
var $this = $(this),
repoSelectVal = parseInt($this.val());
$this.find('option').show();
if (selectedVal < repoSelectVal) {
$this.val(selectedVal);
}
$this.find("option[value='" + selectedVal + "']").nextAll().hide();
});
if (selectedVal) {
this.$repoSelects.removeClass('disabled');
} else {
this.$repoSelects.addClass('disabled');
}
}.bind(this));
};
return ProjectNew; return ProjectNew;
})(); })();

View File

@ -761,62 +761,6 @@ pre.light-well {
.dropdown-menu { .dropdown-menu {
width: 300px; width: 300px;
} }
&.from .compare-dropdown-toggle {
width: 237px;
}
&.to .compare-dropdown-toggle {
width: 254px;
}
.dropdown-toggle-text {
display: block;
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
}
.compare-ellipsis {
display: inline;
}
@media (max-width: $screen-xs-max) {
.compare-form-group {
.input-group {
width: 100%;
& > .compare-dropdown-toggle {
width: 100%;
}
}
.dropdown-menu {
width: 100%;
}
}
.compare-switch-container {
text-align: center;
padding: 0 0 $gl-padding;
.commits-compare-switch {
float: none;
}
}
.compare-ellipsis {
display: block;
text-align: center;
padding: 0 0 $gl-padding;
}
.commits-compare-btn {
width: 100%;
}
} }
.clearable-input { .clearable-input {
@ -855,3 +799,30 @@ pre.light-well {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
} }
.project-home-empty {
border-top: 0;
.container-fluid {
background: none;
}
p {
margin-left: auto;
margin-right: auto;
max-width: 650px;
}
}
.project-feature-nested {
@media (min-width: $screen-sm-min) {
padding-left: 45px;
}
}
.project-repo-select {
&.disabled {
opacity: 0.5;
pointer-events: none;
}
}

View File

@ -1,4 +1,5 @@
class ProjectsController < Projects::ApplicationController class ProjectsController < Projects::ApplicationController
include IssuableCollections
include ExtractsPath include ExtractsPath
before_action :authenticate_user!, except: [:show, :activity, :refs] before_action :authenticate_user!, except: [:show, :activity, :refs]
@ -103,16 +104,7 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
@notification_setting = current_user.notification_settings_for(@project) if current_user @notification_setting = current_user.notification_settings_for(@project) if current_user
render_landing_page
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
render :show
end
else
render 'projects/no_repo'
end
end end
format.atom do format.atom do
@ -285,6 +277,26 @@ class ProjectsController < Projects::ApplicationController
private private
# Render project landing depending of which features are available
# So if page is not availble in the list it renders the next page
#
# pages list order: repository readme, wiki home, issues list, customize workflow
def render_landing_page
if @project.feature_available?(:repository, current_user)
return render 'projects/no_repo' unless @project.repository_exists?
render 'projects/empty' if @project.empty_repo?
else
if @project.wiki_enabled?
@wiki_home = @project.wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
@issues = issues_collection
@issues = @issues.page(params[:page])
end
render :show
end
end
def determine_layout def determine_layout
if [:new, :create].include?(action_name.to_sym) if [:new, :create].include?(action_name.to_sym)
'application' 'application'
@ -308,7 +320,8 @@ class ProjectsController < Projects::ApplicationController
project_feature_attributes: project_feature_attributes:
[ [
:issues_access_level, :builds_access_level, :issues_access_level, :builds_access_level,
:wiki_access_level, :merge_requests_access_level, :snippets_access_level :wiki_access_level, :merge_requests_access_level,
:snippets_access_level, :repository_access_level
] ]
} }

View File

@ -1,9 +1,12 @@
module AwardEmojiHelper module AwardEmojiHelper
def toggle_award_url(awardable) def toggle_award_url(awardable)
if @project return url_for([:toggle_award_emoji, awardable]) unless @project
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
if awardable.is_a?(Note)
# We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (6.5x)
toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace_id, project_id: @project.id, id: awardable.id)
else else
url_for([:toggle_award_emoji, awardable]) url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
end end
end end
end end

View File

@ -50,6 +50,20 @@ module PreferencesHelper
end end
def default_project_view def default_project_view
current_user ? current_user.project_view : 'readme' return 'readme' unless current_user
user_view = current_user.project_view
if @project.feature_available?(:repository, current_user)
user_view
elsif user_view == "activity"
"activity"
elsif @project.wiki_enabled?
"wiki"
elsif @project.feature_available?(:issues, current_user)
"projects/issues/issues"
else
"customize_workflow"
end
end end
end end

View File

@ -134,16 +134,35 @@ module ProjectsHelper
options = project_feature_options options = project_feature_options
if @project.private? if @project.private?
level = @project.project_feature.send(field)
options.delete('Everyone with access') options.delete('Everyone with access')
highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED highest_available_option = options.values.max if level == ProjectFeature::ENABLED
end end
options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe
content_tag(
:select,
options,
name: "project[project_feature_attributes][#{field}]",
id: "project_project_feature_attributes_#{field}",
class: "pull-right form-control #{repo_children_classes(field)}",
data: { field: field }
).html_safe
end end
private private
def repo_children_classes(field)
needs_repo_check = [:merge_requests_access_level, :builds_access_level]
return unless needs_repo_check.include?(field)
classes = "project-repo-select js-repo-select"
classes << " disabled" unless @project.feature_available?(:repository, current_user)
classes
end
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
nav_tabs = [:home] nav_tabs = [:home]
@ -155,12 +174,8 @@ module ProjectsHelper
nav_tabs << :merge_requests nav_tabs << :merge_requests
end end
if can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines
end
if can?(current_user, :read_build, project) if can?(current_user, :read_build, project)
nav_tabs << :builds nav_tabs << :pipelines
end end
if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
@ -435,4 +450,8 @@ module ProjectsHelper
'Everyone with access' => ProjectFeature::ENABLED 'Everyone with access' => ProjectFeature::ENABLED
} }
end end
def project_child_container_class(view_path)
view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
end
end end

View File

@ -154,7 +154,7 @@ module Ci
def retryable? def retryable?
builds.latest.any? do |build| builds.latest.any? do |build|
build.failed? && build.retryable? (build.failed? || build.canceled?) && build.retryable?
end end
end end

View File

@ -13,23 +13,26 @@ class ProjectFeature < ActiveRecord::Base
# Enabled: enabled for everyone able to access the project # Enabled: enabled for everyone able to access the project
# #
# Permision levels # Permission levels
DISABLED = 0 DISABLED = 0
PRIVATE = 10 PRIVATE = 10
ENABLED = 20 ENABLED = 20
FEATURES = %i(issues merge_requests wiki snippets builds) FEATURES = %i(issues merge_requests wiki snippets builds repository)
# Default scopes force us to unscope here since a service may need to check # Default scopes force us to unscope here since a service may need to check
# permissions for a project in pending_delete # permissions for a project in pending_delete
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to # http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
belongs_to :project, -> { unscope(where: :pending_delete) } belongs_to :project, -> { unscope(where: :pending_delete) }
validate :repository_children_level
default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false default_value_for :issues_access_level, value: ENABLED, allows_nil: false
default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
default_value_for :snippets_access_level, value: ENABLED, allows_nil: false default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
def feature_available?(feature, user) def feature_available?(feature, user)
raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature) raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
@ -57,6 +60,18 @@ class ProjectFeature < ActiveRecord::Base
private private
# Validates builds and merge requests access level
# which cannot be higher than repository access level
def repository_children_level
validator = lambda do |field|
level = public_send(field) || ProjectFeature::ENABLED
not_allowed = level > repository_access_level
self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed
end
%i(merge_requests_access_level builds_access_level).each(&validator)
end
def get_permission(user, level) def get_permission(user, level)
case level case level
when DISABLED when DISABLED

View File

@ -162,11 +162,13 @@ class ProjectPolicy < BasePolicy
end end
def disabled_features! def disabled_features!
repository_enabled = project.feature_available?(:repository, user)
unless project.feature_available?(:issues, user) unless project.feature_available?(:issues, user)
cannot!(*named_abilities(:issue)) cannot!(*named_abilities(:issue))
end end
unless project.feature_available?(:merge_requests, user) unless project.feature_available?(:merge_requests, user) && repository_enabled
cannot!(*named_abilities(:merge_request)) cannot!(*named_abilities(:merge_request))
end end
@ -183,13 +185,21 @@ class ProjectPolicy < BasePolicy
cannot!(*named_abilities(:wiki)) cannot!(*named_abilities(:wiki))
end end
unless project.feature_available?(:builds, user) unless project.feature_available?(:builds, user) && repository_enabled
cannot!(*named_abilities(:build)) cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline)) cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:environment)) cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment)) cannot!(*named_abilities(:deployment))
end end
unless repository_enabled
cannot! :push_code
cannot! :push_code_to_protected_branches
cannot! :download_code
cannot! :fork_project
cannot! :read_commit_status
end
unless project.container_registry_enabled unless project.container_registry_enabled
cannot!(*named_abilities(:container_image)) cannot!(*named_abilities(:container_image))
end end

View File

@ -0,0 +1,8 @@
.row-content-block.project-home-empty
%div.text-center{ class: container_class }
%h4
Customize your workflow!
%p
Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production!
- if can?(current_user, :admin_project, @project)
= link_to "Get started", edit_project_path(@project), class: "btn btn-success"

View File

@ -22,5 +22,6 @@
= render 'projects/buttons/star' = render 'projects/buttons/star'
= render 'projects/buttons/fork' = render 'projects/buttons/fork'
.project-clone-holder - if @project.feature_available?(:repository, current_user)
= render "shared/clone_panel" .project-clone-holder
= render "shared/clone_panel"

View File

@ -0,0 +1,19 @@
- if @wiki_home.present?
%div{ class: container_class }
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
= preserve do
= render_wiki_content(@wiki_home)
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
.project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] }
%div.text-center{ class: container_class }
%h4
This project does not have a wiki homepage yet
- if can_create_wiki
%p
Add a homepage to your wiki that contains information about your project
%p
We recommend you
= link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home)
to your project's wiki and GitLab will show it here instead of this message.

View File

@ -50,31 +50,25 @@
.form_group.prepend-top-20 .form_group.prepend-top-20
.row .row
.col-md-9 .col-md-9
= feature_fields.label :issues_access_level, "Issues", class: 'label-light' = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
%span.help-block Lightweight issue tracking system for this project %span.help-block Push files to be stored in this project
.col-md-3 .col-md-3.js-repo-access-level
= project_feature_access_select(:issues_access_level) = project_feature_access_select(:repository_access_level)
.row .col-sm-12
.col-md-9 .row
= feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' .col-md-9.project-feature-nested
%span.help-block Submit changes to be merged upstream = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
.col-md-3 %span.help-block Submit changes to be merged upstream
= project_feature_access_select(:merge_requests_access_level) .col-md-3
= project_feature_access_select(:merge_requests_access_level)
.row .row
.col-md-9 .col-md-9.project-feature-nested
= feature_fields.label :builds_access_level, "Builds", class: 'label-light' = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
%span.help-block Submit Test and deploy your changes before merge %span.help-block Submit, test and deploy your changes before merge
.col-md-3 .col-md-3
= project_feature_access_select(:builds_access_level) = project_feature_access_select(:builds_access_level)
.row
.col-md-9
= feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
%span.help-block Pages for project documentation
.col-md-3
= project_feature_access_select(:wiki_access_level)
.row .row
.col-md-9 .col-md-9
@ -83,17 +77,31 @@
.col-md-3 .col-md-3
= project_feature_access_select(:snippets_access_level) = project_feature_access_select(:snippets_access_level)
.row
.col-md-9
= feature_fields.label :issues_access_level, "Issues", class: 'label-light'
%span.help-block Lightweight issue tracking system for this project
.col-md-3
= project_feature_access_select(:issues_access_level)
.row
.col-md-9
= feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
%span.help-block Pages for project documentation
.col-md-3
= project_feature_access_select(:wiki_access_level)
- if Gitlab.config.lfs.enabled && current_user.admin? - if Gitlab.config.lfs.enabled && current_user.admin?
.row .checkbox
.col-md-9 = f.label :lfs_enabled do
= f.label :lfs_enabled, 'LFS', class: 'label-light' = f.check_box :lfs_enabled
%span.help-block %strong LFS
%br
%span.descr
Git Large File Storage Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' }
- if Gitlab.config.registry.enabled - if Gitlab.config.lfs.enabled && current_user.admin?
.form-group .form-group
.checkbox .checkbox
= f.label :container_registry_enabled do = f.label :container_registry_enabled do

View File

@ -1,5 +1,5 @@
%ul.content-list.issues-list.issuable-list %ul.content-list.issues-list.issuable-list
= render @issues = render partial: "projects/issues/issue", collection: @issues
- if @issues.blank? - if @issues.blank?
%li %li
.nothing-here-block No issues to show .nothing-here-block No issues to show

View File

@ -7,7 +7,7 @@
.col-lg-9 .col-lg-9
%h5.prepend-top-0 %h5.prepend-top-0
Pipelines Pipelines
= form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project), remote: true, authenticity_token: true do |f| = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
%fieldset.builds-feature %fieldset.builds-feature
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
.form-group .form-group

View File

@ -12,72 +12,74 @@
= render 'projects/last_push' = render 'projects/last_push'
= render "home_panel" = render "home_panel"
%nav.project-stats{ class: (container_class) } - if @project.feature_available?(:repository, current_user)
%ul.nav %nav.project-stats{ class: container_class }
%li %ul.nav
= link_to project_files_path(@project) do
Files (#{repository_size})
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
#{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
#{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if default_project_view != 'readme' && @repository.readme
%li %li
= link_to 'Readme', readme_path(@project) = link_to project_files_path(@project) do
Files (#{repository_size})
- if @repository.changelog
%li %li
= link_to 'Changelog', changelog_path(@project) = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
#{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
- if @repository.license_blob
%li %li
= link_to license_short_name(@project), license_path(@project) = link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
- if @repository.contribution_guide
%li %li
= link_to 'Contribution guide', contribution_guide_path(@project) = link_to namespace_project_tags_path(@project.namespace, @project) do
#{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if @repository.gitlab_ci_yml - if default_project_view != 'readme' && @repository.readme
%li %li
= link_to 'CI configuration', ci_configuration_path(@project) = link_to 'Readme', readme_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch) - if @repository.changelog
- unless @repository.changelog %li
%li.missing = link_to 'Changelog', changelog_path(@project)
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- unless @repository.license_blob
%li.missing
= link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- unless @repository.gitlab_ci_yml
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
%li.project-repo-buttons-right - if @repository.license_blob
.project-repo-buttons.project-right-buttons %li
- if current_user = link_to license_short_name(@project), license_path(@project)
= render 'shared/members/access_request_buttons', source: @project
= render "projects/buttons/koding"
= render 'projects/buttons/download', project: @project, ref: @ref - if @repository.contribution_guide
= render 'projects/buttons/dropdown' %li
= link_to 'Contribution guide', contribution_guide_path(@project)
= render 'shared/notifications/button', notification_setting: @notification_setting - if @repository.gitlab_ci_yml
- if @repository.commit %li
.project-last-commit{ class: container_class } = link_to 'CI configuration', ci_configuration_path(@project)
= render 'projects/last_commit', commit: @repository.commit, project: @project
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- unless @repository.license_blob
%li.missing
= link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- unless @repository.gitlab_ci_yml
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
%li.project-repo-buttons-right
.project-repo-buttons.project-right-buttons
- if current_user
= render 'shared/members/access_request_buttons', source: @project
= render "projects/buttons/koding"
.btn-group.project-repo-btn-group
= render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
.project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, project: @project
%div{ class: container_class } %div{ class: container_class }
- if @project.archived? - if @project.archived?
@ -86,5 +88,7 @@
= icon("exclamation-triangle fw") = icon("exclamation-triangle fw")
Archived project! Repository is read-only Archived project! Repository is read-only
%div{class: "project-show-#{default_project_view}"} - view_path = default_project_view
= render default_project_view
%div{ class: project_child_container_class(view_path) }
= render view_path

View File

@ -2,10 +2,14 @@ class ExpireBuildInstanceArtifactsWorker
include Sidekiq::Worker include Sidekiq::Worker
def perform(build_id) def perform(build_id)
build = Ci::Build.with_expired_artifacts.reorder(nil).find_by(id: build_id) build = Ci::Build
return unless build .with_expired_artifacts
.reorder(nil)
.find_by(id: build_id)
Rails.logger.info "Removing artifacts build #{build.id}..." return unless build.try(:project)
Rails.logger.info "Removing artifacts for build #{build.id}..."
build.erase_artifacts! build.erase_artifacts!
end end
end end

View File

@ -0,0 +1,14 @@
class AddRepositoryAccessLevelToProjectFeature < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default(:project_features, :repository_access_level, :integer, default: ProjectFeature::ENABLED)
end
def down
remove_column :project_features, :repository_access_level
end
end

View File

@ -832,6 +832,7 @@ ActiveRecord::Schema.define(version: 20161017095000) do
t.integer "builds_access_level" t.integer "builds_access_level"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
end end
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree

View File

@ -125,6 +125,10 @@ module Gitlab
repository.blob_at(commit.id, file_path) repository.blob_at(commit.id, file_path)
end end
def cache_key
"#{file_path}-#{new_file}-#{deleted_file}-#{renamed_file}"
end
end end
end end
end end

View File

@ -35,16 +35,16 @@ module Gitlab
# for the highlighted ones, so we just skip their execution. # for the highlighted ones, so we just skip their execution.
# If the highlighted diff files lines are not cached we calculate and cache them. # If the highlighted diff files lines are not cached we calculate and cache them.
# #
# The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of # The content of the cache is a Hash where the key identifies the file and the values are Arrays of
# hashes that represent serialized diff lines. # hashes that represent serialized diff lines.
# #
def cache_highlight!(diff_file) def cache_highlight!(diff_file)
file_path = diff_file.file_path item_key = diff_file.cache_key
if highlight_cache[file_path] if highlight_cache[item_key]
highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path]) highlight_diff_file_from_cache!(diff_file, highlight_cache[item_key])
else else
highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) highlight_cache[item_key] = diff_file.highlighted_diff_lines.map(&:to_hash)
end end
end end

View File

@ -41,6 +41,46 @@ describe ProjectsController do
end end
end end
end end
describe "when project repository is disabled" do
render_views
before do
project.team << [user, :developer]
project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
end
it 'shows wiki homepage' do
get :show, namespace_id: project.namespace.path, id: project.path
expect(response).to render_template('projects/_wiki')
end
it 'shows issues list page if wiki is disabled' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
get :show, namespace_id: project.namespace.path, id: project.path
expect(response).to render_template('projects/issues/_issues')
end
it 'shows customize workflow page if wiki and issues are disabled' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
get :show, namespace_id: project.namespace.path, id: project.path
expect(response).to render_template("projects/_customize_workflow")
end
it 'shows activity if enabled by user' do
user.update_attribute(:project_view, 'activity')
get :show, namespace_id: project.namespace.path, id: project.path
expect(response).to render_template("projects/_activity")
end
end
end end
context "project with empty repo" do context "project with empty repo" do

View File

@ -45,6 +45,7 @@ FactoryGirl.define do
snippets_access_level ProjectFeature::ENABLED snippets_access_level ProjectFeature::ENABLED
issues_access_level ProjectFeature::ENABLED issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
end end
after(:create) do |project, evaluator| after(:create) do |project, evaluator|
@ -55,6 +56,7 @@ FactoryGirl.define do
snippets_access_level: evaluator.snippets_access_level, snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level, issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: evaluator.merge_requests_access_level, merge_requests_access_level: evaluator.merge_requests_access_level,
repository_access_level: evaluator.repository_access_level
) )
end end
end end

View File

@ -2,8 +2,11 @@ require 'spec_helper'
include WaitForAjax include WaitForAjax
describe 'Edit Project Settings', feature: true do describe 'Edit Project Settings', feature: true do
include WaitForAjax
let(:member) { create(:user) } let(:member) { create(:user) }
let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') } let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') }
let!(:issue) { create(:issue, project: project) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
describe 'project features visibility selectors', js: true do describe 'project features visibility selectors', js: true do
@ -119,4 +122,31 @@ describe 'Edit Project Settings', feature: true do
end end
end end
end end
describe 'repository visibility', js: true do
before do
project.team << [member, :master]
login_as(member)
visit edit_namespace_project_path(project.namespace, project)
end
it "disables repository related features" do
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
expect(find(".edit-project")).to have_selector("select.disabled", count: 2)
end
it "shows empty features project homepage" do
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
select "Disabled", from: "project_project_feature_attributes_issues_access_level"
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
click_button "Save changes"
wait_for_ajax
visit namespace_project_path(project.namespace, project)
expect(page).to have_content "Customize your workflow!"
end
end
end end

View File

@ -24,11 +24,12 @@ feature "Pipelines settings", feature: true do
context 'for master' do context 'for master' do
given(:role) { :master } given(:role) { :master }
scenario 'be allowed to change' do scenario 'be allowed to change', js: true do
fill_in('Test coverage parsing', with: 'coverage_regex') fill_in('Test coverage parsing', with: 'coverage_regex')
click_on 'Save changes' click_on 'Save changes'
expect(page.status_code).to eq(200) expect(page.status_code).to eq(200)
expect(page).to have_button('Save changes', disabled: false)
expect(page).to have_field('Test coverage parsing', with: 'coverage_regex') expect(page).to have_field('Test coverage parsing', with: 'coverage_regex')
end end
end end

View File

@ -307,6 +307,7 @@ ProjectFeature:
- wiki_access_level - wiki_access_level
- snippets_access_level - snippets_access_level
- builds_access_level - builds_access_level
- repository_access_level
- created_at - created_at
- updated_at - updated_at
ProtectedBranch::MergeAccessLevel: ProtectedBranch::MergeAccessLevel:

View File

@ -88,24 +88,38 @@ describe Ci::Pipeline, models: true do
context 'no failed builds' do context 'no failed builds' do
before do before do
FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'success' create_build('rspec', 'success')
end end
it 'be not retryable' do it 'is not retryable' do
is_expected.to be_falsey is_expected.to be_falsey
end end
context 'one canceled job' do
before do
create_build('rubocop', 'canceled')
end
it 'is retryable' do
is_expected.to be_truthy
end
end
end end
context 'with failed builds' do context 'with failed builds' do
before do before do
FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'running' create_build('rspec', 'running')
FactoryGirl.create :ci_build, name: "rubocop", pipeline: pipeline, status: 'failed' create_build('rubocop', 'failed')
end end
it 'be retryable' do it 'is retryable' do
is_expected.to be_truthy is_expected.to be_truthy
end end
end end
def create_build(name, status)
create(:ci_build, name: name, status: status, pipeline: pipeline)
end
end end
describe '#stages' do describe '#stages' do

View File

@ -13,7 +13,7 @@ describe Issue::Metrics, models: true do
metrics = subject.metrics metrics = subject.metrics
expect(metrics).to be_present expect(metrics).to be_present
expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time) expect(metrics.first_associated_with_milestone_at).to be_like_time(time)
end end
it "does not record the second time an issue is associated with a milestone" do it "does not record the second time an issue is associated with a milestone" do
@ -24,7 +24,7 @@ describe Issue::Metrics, models: true do
metrics = subject.metrics metrics = subject.metrics
expect(metrics).to be_present expect(metrics).to be_present
expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time) expect(metrics.first_associated_with_milestone_at).to be_like_time(time)
end end
end end
@ -36,7 +36,7 @@ describe Issue::Metrics, models: true do
metrics = subject.metrics metrics = subject.metrics
expect(metrics).to be_present expect(metrics).to be_present
expect(metrics.first_added_to_board_at).to be_within(1.second).of(time) expect(metrics.first_added_to_board_at).to be_like_time(time)
end end
it "does not record the second time an issue is associated with a list label" do it "does not record the second time an issue is associated with a list label" do
@ -48,7 +48,7 @@ describe Issue::Metrics, models: true do
metrics = subject.metrics metrics = subject.metrics
expect(metrics).to be_present expect(metrics).to be_present
expect(metrics.first_added_to_board_at).to be_within(1.second).of(time) expect(metrics.first_added_to_board_at).to be_like_time(time)
end end
end end
end end

View File

@ -12,7 +12,7 @@ describe MergeRequest::Metrics, models: true do
metrics = subject.metrics metrics = subject.metrics
expect(metrics).to be_present expect(metrics).to be_present
expect(metrics.merged_at).to be_within(1.second).of(time) expect(metrics.merged_at).to be_like_time(time)
end end
end end
end end

View File

@ -5,7 +5,7 @@ describe ProjectFeature do
let(:user) { create(:user) } let(:user) { create(:user) }
describe '#feature_available?' do describe '#feature_available?' do
let(:features) { %w(issues wiki builds merge_requests snippets) } let(:features) { %w(issues wiki builds merge_requests snippets repository) }
context 'when features are disabled' do context 'when features are disabled' do
it "returns false" do it "returns false" do
@ -64,6 +64,27 @@ describe ProjectFeature do
end end
end end
context 'repository related features' do
before do
project.project_feature.update_attributes(
merge_requests_access_level: ProjectFeature::DISABLED,
builds_access_level: ProjectFeature::DISABLED,
repository_access_level: ProjectFeature::PRIVATE
)
end
it "does not allow repository related features have higher level" do
features = %w(builds merge_requests)
project_feature = project.project_feature
features.each do |feature|
field = "#{feature}_access_level".to_sym
project_feature.update_attribute(field, ProjectFeature::ENABLED)
expect(project_feature.valid?).to be_falsy
end
end
end
describe '#*_enabled?' do describe '#*_enabled?' do
let(:features) { %w(wiki builds merge_requests) } let(:features) { %w(wiki builds merge_requests) }

View File

@ -694,7 +694,7 @@ describe API::API, api: true do
title: 'new issue', labels: 'label, label2', created_at: creation_time title: 'new issue', labels: 'label, label2', created_at: creation_time
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
end end
end end
end end
@ -895,7 +895,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['labels']).to include 'label3' expect(json_response['labels']).to include 'label3'
expect(Time.parse(json_response['updated_at'])).to be_within(1.second).of(update_time) expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
end end
end end
end end

View File

@ -217,7 +217,7 @@ describe API::API, api: true do
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['body']).to eq('hi!') expect(json_response['body']).to eq('hi!')
expect(json_response['author']['username']).to eq(user.username) expect(json_response['author']['username']).to eq(user.username)
expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
end end
end end

View File

@ -262,7 +262,7 @@ describe CreateDeploymentService, services: true do
time = Time.now time = Time.now
Timecop.freeze(time) { service.execute } Timecop.freeze(time) { service.execute }
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time)
end end
it "doesn't set the time if the deploy's environment is not 'production'" do it "doesn't set the time if the deploy's environment is not 'production'" do
@ -288,13 +288,13 @@ describe CreateDeploymentService, services: true do
time = Time.now time = Time.now
Timecop.freeze(time) { service.execute } Timecop.freeze(time) { service.execute }
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time)
# Current deploy # Current deploy
service = described_class.new(project, user, params) service = described_class.new(project, user, params)
Timecop.freeze(time + 12.hours) { service.execute } Timecop.freeze(time + 12.hours) { service.execute }
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time)
end end
end end

View File

@ -364,7 +364,7 @@ describe GitPushService, services: true do
it 'sets the metric for referenced issues' do it 'sets the metric for referenced issues' do
execute_service(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref)
expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_within(1.second).of(commit_time) expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_like_time(commit_time)
end end
it 'does not set the metric for non-referenced issues' do it 'does not set the metric for non-referenced issues' do

View File

@ -0,0 +1,13 @@
RSpec::Matchers.define :be_like_time do |expected|
match do |actual|
expect(actual).to be_within(1.second).of(expected)
end
description do
"within one second of #{expected}"
end
failure_message do |actual|
"expected #{actual} to be within one second of #{expected}"
end
end

View File

@ -6,28 +6,48 @@ describe ExpireBuildInstanceArtifactsWorker do
let(:worker) { described_class.new } let(:worker) { described_class.new }
describe '#perform' do describe '#perform' do
before { build } before do
worker.perform(build.id)
subject! { worker.perform(build.id) } end
context 'with expired artifacts' do context 'with expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } let(:artifacts_expiry) { { artifacts_expire_at: Time.now - 7.days } }
it 'does expire' do context 'when associated project is valid' do
expect(build.reload.artifacts_expired?).to be_truthy let(:build) do
create(:ci_build, :artifacts, artifacts_expiry)
end
it 'does expire' do
expect(build.reload.artifacts_expired?).to be_truthy
end
it 'does remove files' do
expect(build.reload.artifacts_file.exists?).to be_falsey
end
it 'does nullify artifacts_file column' do
expect(build.reload.artifacts_file_identifier).to be_nil
end
end end
it 'does remove files' do context 'when associated project was removed' do
expect(build.reload.artifacts_file.exists?).to be_falsey let(:build) do
end create(:ci_build, :artifacts, artifacts_expiry) do |build|
build.project.delete
end
end
it 'does nullify artifacts_file column' do it 'does not remove artifacts' do
expect(build.reload.artifacts_file_identifier).to be_nil expect(build.reload.artifacts_file.exists?).to be_truthy
end
end end
end end
context 'with not yet expired artifacts' do context 'with not yet expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } let(:build) do
create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days)
end
it 'does not expire' do it 'does not expire' do
expect(build.reload.artifacts_expired?).to be_falsey expect(build.reload.artifacts_expired?).to be_falsey

View File

@ -23,7 +23,7 @@ describe PipelineMetricsWorker do
it 'records the build start time' do it 'records the build start time' do
subject subject
expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(pipeline.started_at) expect(merge_request.reload.metrics.latest_build_started_at).to be_like_time(pipeline.started_at)
end end
it 'clears the build end time' do it 'clears the build end time' do
@ -39,7 +39,7 @@ describe PipelineMetricsWorker do
it 'records the build end time' do it 'records the build end time' do
subject subject
expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(pipeline.finished_at) expect(merge_request.reload.metrics.latest_build_finished_at).to be_like_time(pipeline.finished_at)
end end
end end
end end