summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2016-12-27 16:34:20 +0000
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2016-12-27 16:34:20 +0000
commit6024697c100e003ec456b9ad2e9461d73250dbb8 (patch)
treebdcf59573e81e21b1d43b09a47b399f1028f3ee6
parent123bc1d5ebc102c75456b54cbce8f1ec1bfe0eff (diff)
parentf264ec6ee74a0263b4e5212e921c4638c25f8fcd (diff)
downloadgitlab-ce-6024697c100e003ec456b9ad2e9461d73250dbb8.tar.gz
Merge branch 'master' into 'dz-rename-reserved-project-names'
# Conflicts: # db/schema.rb
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--CONTRIBUTING.md5
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--README.md8
-rw-r--r--app/assets/javascripts/build.js4
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es62
-rw-r--r--app/assets/javascripts/issue.js3
-rw-r--r--app/assets/stylesheets/framework/lists.scss1
-rw-r--r--app/assets/stylesheets/pages/deploy_keys.scss13
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss8
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss7
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb4
-rw-r--r--app/controllers/admin/groups_controller.rb7
-rw-r--r--app/controllers/admin/projects_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb4
-rw-r--r--app/helpers/projects_helper.rb25
-rw-r--r--app/helpers/sorting_helper.rb11
-rw-r--r--app/helpers/storage_helper.rb7
-rw-r--r--app/models/ci/build.rb6
-rw-r--r--app/models/ci/pipeline.rb9
-rw-r--r--app/models/deploy_key.rb14
-rw-r--r--app/models/group.rb8
-rw-r--r--app/models/key.rb8
-rw-r--r--app/models/lfs_objects_project.rb9
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/namespace.rb13
-rw-r--r--app/models/project.rb22
-rw-r--r--app/models/project_statistics.rb43
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/git_tag_push_service.rb2
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/views/admin/deploy_keys/index.html.haml57
-rw-r--r--app/views/admin/deploy_keys/new.html.haml8
-rw-r--r--app/views/admin/groups/_group.html.haml3
-rw-r--r--app/views/admin/groups/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml20
-rw-r--r--app/views/admin/projects/index.html.haml4
-rw-r--r--app/views/admin/projects/show.html.haml13
-rw-r--r--app/views/groups/projects.html.haml4
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml3
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml9
-rw-r--r--app/views/projects/diffs/_file_header.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml3
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml4
-rw-r--r--app/views/projects/show.html.haml8
-rw-r--r--app/workers/project_cache_worker.rb23
-rw-r--r--changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml3
-rw-r--r--changelogs/unreleased/25909-fix-mr-list-timestamp-alignment.yml4
-rw-r--r--changelogs/unreleased/25931-gitlab-merge-request-view-crash-when-commiting-a-js-sourcemap-file.yml4
-rw-r--r--changelogs/unreleased/26026-pipeline-overflow-firefox.yml4
-rw-r--r--changelogs/unreleased/26038-fix-confedential-warning-text-alignment.yml4
-rw-r--r--changelogs/unreleased/26040-hide-scroll-top.yml4
-rw-r--r--changelogs/unreleased/auto-deploy-with-space.yml4
-rw-r--r--changelogs/unreleased/changelog-mr8322.yml4
-rw-r--r--changelogs/unreleased/dz-rename-invalid-users.yml4
-rw-r--r--changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml4
-rw-r--r--changelogs/unreleased/feature-more-storage-statistics.yml4
-rw-r--r--changelogs/unreleased/filename-to-file-path.yml3
-rw-r--r--changelogs/unreleased/fix-latest-pipeine-ordering.yml4
-rw-r--r--changelogs/unreleased/label-gfm-error-fix.yml4
-rw-r--r--changelogs/unreleased/re-style-issue-new-branch.yml3
-rw-r--r--changelogs/unreleased/resolve-note-svg-color.yml4
-rw-r--r--config/initializers/inflections.rb2
-rw-r--r--db/migrate/20160811172945_add_can_push_to_keys.rb14
-rw-r--r--db/migrate/20161201155511_create_project_statistics.rb20
-rw-r--r--db/migrate/20161201160452_migrate_project_statistics.rb23
-rw-r--r--db/migrate/20161226122833_remove_dot_git_from_usernames.rb87
-rw-r--r--db/post_migrate/20161221140236_remove_unneeded_services.rb2
-rw-r--r--db/schema.rb21
-rw-r--r--doc/administration/build_artifacts.md6
-rw-r--r--doc/api/deploy_keys.md19
-rw-r--r--doc/api/groups.md8
-rw-r--r--doc/api/projects.md4
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/autodeploy/img/auto_deploy_button.pngbin0 -> 43441 bytes
-rw-r--r--doc/ci/autodeploy/img/auto_deploy_dropdown.pngbin0 -> 44380 bytes
-rw-r--r--doc/ci/autodeploy/img/autodeploy_button.pngbin41799 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/img/autodeploy_dropdown.pngbin51420 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/index.md14
-rw-r--r--doc/development/ux_guide/copy.md8
-rw-r--r--doc/workflow/lfs/lfs_administration.md8
-rw-r--r--features/admin/deploy_keys.feature23
-rw-r--r--features/steps/admin/deploy_keys.rb59
-rw-r--r--lib/api/entities.rb41
-rw-r--r--lib/api/groups.rb31
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/projects.rb82
-rw-r--r--lib/gitlab/auth/result.rb3
-rw-r--r--lib/gitlab/checks/change_access.rb11
-rw-r--r--lib/gitlab/diff/file_collection/merge_request_diff.rb5
-rw-r--r--lib/gitlab/email/reply_parser.rb36
-rw-r--r--lib/gitlab/git_access.rb152
-rw-r--r--lib/gitlab/git_access_wiki.rb4
-rw-r--r--lib/gitlab/template/gitlab_ci_yml_template.rb4
-rw-r--r--lib/gitlab/user_access.rb16
-rw-r--r--lib/tasks/gitlab/import.rake3
-rw-r--r--lib/tasks/gitlab/update_commit_count.rake20
-rw-r--r--spec/factories/lfs_objects.rb2
-rw-r--r--spec/factories/project_statistics.rb6
-rw-r--r--spec/features/auto_deploy_spec.rb14
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb13
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb8
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb6
-rw-r--r--spec/helpers/storage_helper_spec.rb21
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb19
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_spec.rb40
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/migrations/remove_dot_git_from_usernames.rb29
-rw-r--r--spec/models/ci/build_spec.rb26
-rw-r--r--spec/models/ci/pipeline_spec.rb13
-rw-r--r--spec/models/deploy_key_spec.rb14
-rw-r--r--spec/models/key_spec.rb14
-rw-r--r--spec/models/lfs_objects_project_spec.rb36
-rw-r--r--spec/models/namespace_spec.rb45
-rw-r--r--spec/models/project_spec.rb21
-rw-r--r--spec/models/project_statistics_spec.rb160
-rw-r--r--spec/requests/api/groups_spec.rb33
-rw-r--r--spec/requests/api/projects_spec.rb56
-rw-r--r--spec/requests/git_http_spec.rb16
-rw-r--r--spec/services/git_push_service_spec.rb4
-rw-r--r--spec/services/merge_requests/merge_request_diff_cache_service_spec.rb1
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb22
-rw-r--r--spec/workers/project_cache_worker_spec.rb42
-rw-r--r--[l---------]vendor/gitignore/Clojure.gitignore14
130 files changed, 1476 insertions, 424 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b256e8a2a5f..e0e780e1e6b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -425,7 +425,7 @@ notify:slack:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
script:
- - ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
+ - ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>"
when: on_failure
only:
- master@gitlab-org/gitlab-ce
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 659871a06a4..b68c4a67826 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -217,8 +217,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests,
and/or documentation. The features we would really like a merge request for are
listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note
-that if an issue is marked for the current milestone either before or while you
-are working on it, a team member may take over the merge request in order to
+that if an issue is marked for the current milestone either before or while you
+are working on it, a team member may take over the merge request in order to
ensure the work is finished before the release date.
If you want to add a new feature that is not labeled it is best to first create
@@ -300,6 +300,7 @@ you start with a very simple UI? Can you do part of the refactor? The increased
reviewability of small MRs that leads to higher code quality is more important
to us than having a minimal commit log. The smaller an MR is the more likely it
is it will be merged (quickly). After that you can send more MRs to enhance it.
+The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this.
For examples of feedback on merge requests please look at already
[closed merge requests][closed-merge-requests]. If you would like quick feedback
diff --git a/Gemfile b/Gemfile
index 9dfaf7a48a2..fff808e9805 100644
--- a/Gemfile
+++ b/Gemfile
@@ -332,7 +332,7 @@ gem 'octokit', '~> 4.3.0'
gem 'mail_room', '~> 0.9.0'
-gem 'email_reply_parser', '~> 0.5.8'
+gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
gem 'ruby-prof', '~> 0.16.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 9f8367b420a..765d57c6238 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -167,7 +167,7 @@ GEM
railties (>= 4.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
- email_reply_parser (0.5.8)
+ email_reply_trimmer (0.1.6)
email_spec (1.6.0)
launchy (~> 2.1)
mail (~> 2.2)
@@ -839,7 +839,7 @@ DEPENDENCIES
diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
dropzonejs-rails (~> 0.7.1)
- email_reply_parser (~> 0.5.8)
+ email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0)
ffaker (~> 2.0.0)
diff --git a/README.md b/README.md
index 68b709b85d5..4e28f3aacfd 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,10 @@ To see how GitLab looks please see the [features page on our website](https://ab
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
-- Each project can also have an issue tracker and a wiki
+- Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
+- Each project can also have an issue tracker, issue board, and a wiki
- Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
- Completely free and open source (MIT Expat license)
-- Powered by [Ruby on Rails](https://github.com/rails/rails)
## Hiring
@@ -74,11 +74,11 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software:
-- Ubuntu/Debian/CentOS/RHEL
+- Ubuntu/Debian/CentOS/RHEL/OpenSUSE
- Ruby (MRI) 2.3
- Git 2.8.4+
- Redis 2.8+
-- MySQL or PostgreSQL
+- PostgreSQL (preferred) or MySQL
For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html).
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 5e449170cd3..bc13c46443a 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -92,8 +92,8 @@
success: function(buildData) {
$('.js-build-output').html(buildData.trace_html);
if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
- this.initScrollMonitor();
- return this.$buildRefreshAnimation.remove();
+ this.$buildRefreshAnimation.remove();
+ return this.initScrollMonitor();
}
}.bind(this)
});
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 12875eaa1c3..3857bbb743b 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -367,7 +367,7 @@
return $input.trigger('keyup');
},
isLoading(data) {
- if (!data) return false;
+ if (!data || !data.length) return false;
if (Array.isArray(data)) data = data[0];
return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0];
},
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 63b70d4be17..61e8531153b 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -139,15 +139,12 @@
return;
}
return $.getJSON($container.data('path')).error(function() {
- $container.find('.checking').hide();
$container.find('.unavailable').show();
return new Flash('Failed to check if a new branch can be created.', 'alert');
}).success(function(data) {
if (data.can_create_branch) {
- $container.find('.checking').hide();
$container.find('.available').show();
} else {
- $container.find('.checking').hide();
return $container.find('.unavailable').show();
}
});
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index e96cd671e34..28a0f9871a2 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -239,7 +239,6 @@ ul.content-list {
}
ul.controls {
- padding-top: 1px;
float: right;
list-style: none;
diff --git a/app/assets/stylesheets/pages/deploy_keys.scss b/app/assets/stylesheets/pages/deploy_keys.scss
new file mode 100644
index 00000000000..2fafe052106
--- /dev/null
+++ b/app/assets/stylesheets/pages/deploy_keys.scss
@@ -0,0 +1,13 @@
+.deploy-keys-list {
+ width: 100%;
+ overflow: auto;
+
+ table {
+ border: 1px solid $table-border-color;
+ }
+}
+
+.deploy-keys-title {
+ padding-bottom: 2px;
+ line-height: 2;
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 074abec7692..e54e12be82f 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -109,7 +109,7 @@
margin: auto;
margin-top: 0;
text-align: center;
- font-size: 13px;
+ font-size: 12px;
@media (max-width: $screen-sm-max) {
// On smaller devices the warning becomes the fourth item in the list,
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index b512da0939f..8f15775ee03 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -557,18 +557,14 @@ ul.notes {
&.is-active {
color: $gl-text-green;
- svg path {
+ svg {
fill: $gl-text-green;
}
}
svg {
position: relative;
- color: $gray-darkest;
-
- path {
- fill: $gray-darkest;
- }
+ fill: $gray-darkest;
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 5f8874289cc..c370b6247b0 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -335,7 +335,6 @@
width: 100%;
background-color: $gray-light;
padding: $gl-padding;
- overflow: auto;
white-space: nowrap;
transition: max-height 0.3s, padding 0.3s;
@@ -621,14 +620,14 @@
}
.dropdown-counter-badge {
- float: right;
color: $border-color;
font-weight: 100;
font-size: 15px;
- margin-right: 2px;
+ position: absolute;
+ right: 5px;
+ top: 8px;
}
-
.grouped-pipeline-dropdown {
padding: 0;
width: 191px;
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index 285e8495342..4f6a7e9e2cb 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -10,7 +10,7 @@ class Admin::DeployKeysController < Admin::ApplicationController
end
def create
- @deploy_key = deploy_keys.new(deploy_key_params)
+ @deploy_key = deploy_keys.new(deploy_key_params.merge(user: current_user))
if @deploy_key.save
redirect_to admin_deploy_keys_path
@@ -39,6 +39,6 @@ class Admin::DeployKeysController < Admin::ApplicationController
end
def deploy_key_params
- params.require(:deploy_key).permit(:key, :title)
+ params.require(:deploy_key).permit(:key, :title, :can_push)
end
end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 1e3d194e9f9..61a3a03182a 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -1,17 +1,18 @@
class Admin::GroupsController < Admin::ApplicationController
- before_action :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update]
+ before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index
- @groups = Group.all
+ @groups = Group.with_statistics
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page])
end
def show
+ @group = Group.with_statistics.find_by_full_path(params[:id])
@members = @group.members.order("access_level DESC").page(params[:members_page])
@requesters = AccessRequestsFinder.new(@group).execute(current_user)
- @projects = @group.projects.page(params[:projects_page])
+ @projects = @group.projects.with_statistics.page(params[:projects_page])
end
def new
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 1d963bdf7d5..b09ae423096 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -3,7 +3,7 @@ class Admin::ProjectsController < Admin::ApplicationController
before_action :group, only: [:show, :transfer]
def index
- @projects = Project.all
+ @projects = Project.with_statistics
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.with_push if params[:with_push].present?
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index efe9c001bcf..01c8fa2739f 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -75,7 +75,7 @@ class GroupsController < Groups::ApplicationController
end
def projects
- @projects = @group.projects.page(params[:page])
+ @projects = @group.projects.with_statistics.page(params[:page])
end
def update
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 529e0aa2d33..b094491e006 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -16,7 +16,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def create
- @key = DeployKey.new(deploy_key_params)
+ @key = DeployKey.new(deploy_key_params.merge(user: current_user))
set_index_vars
if @key.valid? && @project.deploy_keys << @key
@@ -53,6 +53,6 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def deploy_key_params
- params.require(:deploy_key).permit(:key, :title)
+ params.require(:deploy_key).permit(:key, :title, :can_push)
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7445f3c113c..4b5a2dc8782 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -90,10 +90,12 @@ module ProjectsHelper
end
def project_for_deploy_key(deploy_key)
- if deploy_key.projects.include?(@project)
+ if deploy_key.has_access_to?(@project)
@project
else
- deploy_key.projects.find { |project| can?(current_user, :read_project, project) }
+ deploy_key.projects.find do |project|
+ can?(current_user, :read_project, project)
+ end
end
end
@@ -246,11 +248,6 @@ module ProjectsHelper
end
end
- def repository_size(project = @project)
- size_in_bytes = project.repository_size * 1.megabyte
- number_to_human_size(size_in_bytes, delimiter: ',', precision: 2)
- end
-
def default_url_to_repo(project = @project)
case default_clone_protocol
when 'ssh'
@@ -398,20 +395,6 @@ module ProjectsHelper
[@project.path_with_namespace, 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
-
def current_ref
@ref || @repository.try(:root_ref)
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index f03c4627050..ff787fb4131 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -11,6 +11,7 @@ module SortingHelper
sort_value_due_date_soon => sort_title_due_date_soon,
sort_value_due_date_later => sort_title_due_date_later,
sort_value_largest_repo => sort_title_largest_repo,
+ sort_value_largest_group => sort_title_largest_group,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_downvotes => sort_title_downvotes,
@@ -92,6 +93,10 @@ module SortingHelper
'Largest repository'
end
+ def sort_title_largest_group
+ 'Largest group'
+ end
+
def sort_title_recently_signin
'Recent sign in'
end
@@ -193,7 +198,11 @@ module SortingHelper
end
def sort_value_largest_repo
- 'repository_size_desc'
+ 'storage_size_desc'
+ end
+
+ def sort_value_largest_group
+ 'storage_size_desc'
end
def sort_value_recently_signin
diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb
new file mode 100644
index 00000000000..e19c67a37ca
--- /dev/null
+++ b/app/helpers/storage_helper.rb
@@ -0,0 +1,7 @@
+module StorageHelper
+ def storage_counter(size_in_bytes)
+ precision = size_in_bytes < 1.megabyte ? 0 : 1
+
+ number_to_human_size(size_in_bytes, delimiter: ',', precision: precision, significant: false)
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index cb76cdf5981..27042798741 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -43,6 +43,8 @@ module Ci
before_destroy { project }
after_create :execute_hooks
+ after_save :update_project_statistics, if: :artifacts_size_changed?
+ after_destroy :update_project_statistics
class << self
def first_pending
@@ -584,5 +586,9 @@ module Ci
Ci::MaskSecret.mask!(trace, token)
trace
end
+
+ def update_project_statistics
+ ProjectCacheWorker.perform_async(project_id, [], [:build_artifacts_size])
+ end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index f2f6453b3b9..abbbddaa4f6 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -93,11 +93,8 @@ module Ci
.select("max(#{quoted_table_name}.id)")
.group(:ref, :sha)
- if ref
- where(id: max_id, ref: ref)
- else
- where(id: max_id)
- end
+ relation = ref ? where(ref: ref) : self
+ relation.where(id: max_id)
end
def self.latest_status(ref = nil)
@@ -105,7 +102,7 @@ module Ci
end
def self.latest_successful_for(ref)
- success.latest(ref).first
+ success.latest(ref).order(id: :desc).first
end
def self.truncate_sha(sha)
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 2c525d4cd7a..053f2a11aa0 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -20,4 +20,18 @@ class DeployKey < Key
def destroyed_when_orphaned?
self.private?
end
+
+ def has_access_to?(project)
+ projects.include?(project)
+ end
+
+ def can_push_to?(project)
+ can_push? && has_access_to?(project)
+ end
+
+ private
+
+ # we don't want to notify the user for deploy keys
+ def notify_user
+ end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index ac8a82c8c1e..85696ad9747 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -48,7 +48,13 @@ class Group < Namespace
end
def sort(method)
- order_by(method)
+ if method == 'storage_size_desc'
+ # storage_size is a virtual column so we need to
+ # pass a string to avoid AR adding the table name
+ reorder('storage_size DESC, namespaces.id DESC')
+ else
+ order_by(method)
+ end
end
def reference_prefix
diff --git a/app/models/key.rb b/app/models/key.rb
index a5d25409730..6f377f0e8ae 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -57,10 +57,6 @@ class Key < ActiveRecord::Base
)
end
- def notify_user
- run_after_commit { NotificationService.new.new_key(self) }
- end
-
def post_create_hook
SystemHooksService.new.execute_hooks_for(self, :create)
end
@@ -86,4 +82,8 @@ class Key < ActiveRecord::Base
self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint
end
+
+ def notify_user
+ run_after_commit { NotificationService.new.new_key(self) }
+ end
end
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 0fd5f089db9..007eed5600a 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -5,4 +5,13 @@ class LfsObjectsProject < ActiveRecord::Base
validates :lfs_object_id, presence: true
validates :lfs_object_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_id, presence: true
+
+ after_create :update_project_statistics
+ after_destroy :update_project_statistics
+
+ private
+
+ def update_project_statistics
+ ProjectCacheWorker.perform_async(project_id, [], [:lfs_objects_size])
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5ce20cc43d6..22490d121c7 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -198,7 +198,9 @@ class MergeRequest < ActiveRecord::Base
end
def diff_size
- diffs(diff_options).size
+ opts = diff_options || {}
+
+ raw_diffs(opts).size
end
def diff_base_commit
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b52f08c7081..d41833de66f 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -9,6 +9,7 @@ class Namespace < ActiveRecord::Base
cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy
+ has_many :project_statistics
belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace"
@@ -38,6 +39,18 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') }
+ scope :with_statistics, -> do
+ joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
+ .group('namespaces.id')
+ .select(
+ 'namespaces.*',
+ 'COALESCE(SUM(ps.storage_size), 0) AS storage_size',
+ 'COALESCE(SUM(ps.repository_size), 0) AS repository_size',
+ 'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size',
+ 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size',
+ )
+ end
+
class << self
def by_path(path)
find_by('lower(path) = :value', value: path.downcase)
diff --git a/app/models/project.rb b/app/models/project.rb
index 5fd6c86fd70..e0ffa7e7af7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -44,6 +44,7 @@ class Project < ActiveRecord::Base
after_create :ensure_dir_exist
after_create :create_project_feature, unless: :project_feature
after_save :ensure_dir_exist, if: :namespace_id_changed?
+ after_save :update_project_statistics, if: :namespace_id_changed?
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
@@ -151,6 +152,7 @@ class Project < ActiveRecord::Base
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :project_feature, dependent: :destroy
+ has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete
has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
@@ -220,6 +222,7 @@ class Project < ActiveRecord::Base
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
+ scope :with_statistics, -> { includes(:statistics) }
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
@@ -332,8 +335,10 @@ class Project < ActiveRecord::Base
end
def sort(method)
- if method == 'repository_size_desc'
- reorder(repository_size: :desc, id: :desc)
+ if method == 'storage_size_desc'
+ # storage_size is a joined column so we need to
+ # pass a string to avoid AR adding the table name
+ reorder('project_statistics.storage_size DESC, projects.id DESC')
else
order_by(method)
end
@@ -1036,14 +1041,6 @@ class Project < ActiveRecord::Base
forked? && project == forked_from_project
end
- def update_repository_size
- update_attribute(:repository_size, repository.size)
- end
-
- def update_commit_count
- update_attribute(:commit_count, repository.commit_count)
- end
-
def forks_count
forks.count
end
@@ -1322,4 +1319,9 @@ class Project < ActiveRecord::Base
def full_path_changed?
path_changed? || namespace_id_changed?
end
+
+ def update_project_statistics
+ stats = statistics || build_statistics
+ stats.update(namespace_id: namespace_id)
+ end
end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
new file mode 100644
index 00000000000..2270ac75071
--- /dev/null
+++ b/app/models/project_statistics.rb
@@ -0,0 +1,43 @@
+class ProjectStatistics < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :namespace
+
+ before_save :update_storage_size
+
+ STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size]
+ STATISTICS_COLUMNS = [:commit_count] + STORAGE_COLUMNS
+
+ def total_repository_size
+ repository_size + lfs_objects_size
+ end
+
+ def refresh!(only: nil)
+ STATISTICS_COLUMNS.each do |column, generator|
+ if only.blank? || only.include?(column)
+ public_send("update_#{column}")
+ end
+ end
+
+ save!
+ end
+
+ def update_commit_count
+ self.commit_count = project.repository.commit_count
+ end
+
+ def update_repository_size
+ self.repository_size = project.repository.size
+ end
+
+ def update_lfs_objects_size
+ self.lfs_objects_size = project.lfs_objects.sum(:size)
+ end
+
+ def update_build_artifacts_size
+ self.build_artifacts_size = project.builds.sum(:artifacts_size)
+ end
+
+ def update_storage_size
+ self.storage_size = STORAGE_COLUMNS.sum(&method(:read_attribute))
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 6bbc3a9d9ff..dbe2fda27b5 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -77,7 +77,7 @@ class GitPushService < BaseService
types = []
end
- ProjectCacheWorker.perform_async(@project.id, types)
+ ProjectCacheWorker.perform_async(@project.id, types, [:commit_count, :repository_size])
end
# Schedules processing of commit messages.
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 20a4445bddf..96432837481 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -12,7 +12,7 @@ class GitTagPushService < BaseService
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
Ci::CreatePipelineService.new(project, current_user, @push_data).execute
- ProjectCacheWorker.perform_async(project.id)
+ ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
true
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index d75592e31f3..1beca9f4109 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -41,7 +41,7 @@ module Notes
# We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
- note.errors.add(:commands_only, 'Your commands have been executed!')
+ note.errors.add(:commands_only, 'Commands applied')
end
note.commands_changes = command_params.keys
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 149593e7f46..7b71bb5b287 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -1,27 +1,34 @@
- page_title "Deploy Keys"
-.panel.panel-default.prepend-top-default
- .panel-heading
- Public deploy keys (#{@deploy_keys.count})
- .controls
- = link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- - if @deploy_keys.any?
- .table-holder
- %table.table
- %thead.panel-heading
+
+%h3.page-title.deploy-keys-title
+ Public deploy keys (#{@deploy_keys.count})
+ .pull-right
+ = link_to 'New Deploy Key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted'
+
+- if @deploy_keys.any?
+ .table-holder.deploy-keys-list
+ %table.table
+ %thead
+ %tr
+ %th.col-sm-2 Title
+ %th.col-sm-4 Fingerprint
+ %th.col-sm-2 Write access allowed
+ %th.col-sm-2 Added at
+ %th.col-sm-2
+ %tbody
+ - @deploy_keys.each do |deploy_key|
%tr
- %th Title
- %th Fingerprint
- %th Added at
- %th
- %tbody
- - @deploy_keys.each do |deploy_key|
- %tr
- %td
- %strong= deploy_key.title
- %td
- %code.key-fingerprint= deploy_key.fingerprint
- %td
- %span.cgray
- added #{time_ago_with_tooltip(deploy_key.created_at)}
- %td
- = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
+ %td
+ %strong= deploy_key.title
+ %td
+ %code.key-fingerprint= deploy_key.fingerprint
+ %td
+ - if deploy_key.can_push?
+ Yes
+ - else
+ No
+ %td
+ %span.cgray
+ added #{time_ago_with_tooltip(deploy_key.created_at)}
+ %td
+ = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove delete-key pull-right'
diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml
index 5c410a695bf..a064efc231f 100644
--- a/app/views/admin/deploy_keys/new.html.haml
+++ b/app/views/admin/deploy_keys/new.html.haml
@@ -16,6 +16,14 @@
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh/README")
= f.text_area :key, class: "form-control thin_area", rows: 5
+ .form-group
+ .control-label
+ .col-sm-10
+ = f.label :can_push do
+ = f.check_box :can_push
+ %strong Write access allowed
+ %p.light.append-bottom-0
+ Allow this key to push to repository as well? (Default only allows pull access.)
.form-actions
= f.submit 'Create', class: "btn-create btn"
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index cf28f92853e..6fc212119c4 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -5,6 +5,9 @@
= link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
= link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove'
.stats
+ %span.badge
+ = storage_counter(group.storage_size)
+
%span
= icon('bookmark')
= number_with_delimiter(group.projects.count)
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 794f910a61f..07775247cfd 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -27,6 +27,8 @@
= sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated, name: project_name) do
= sort_title_oldest_updated
+ = link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do
+ = sort_title_largest_group
= link_to new_admin_group_path, class: "btn btn-new" do
New Group
%ul.content-list
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 7b0175af214..ab9c79f6add 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -39,6 +39,18 @@
= @group.created_at.to_s(:medium)
%li
+ %span.light Storage:
+ %strong= storage_counter(@group.storage_size)
+ (
+ = storage_counter(@group.repository_size)
+ repositories,
+ = storage_counter(@group.build_artifacts_size)
+ build artifacts,
+ = storage_counter(@group.lfs_objects_size)
+ LFS
+ )
+
+ %li
%span.light Group Git LFS status:
%strong
= group_lfs_status(@group)
@@ -55,8 +67,8 @@
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
- %span.label.label-gray
- = repository_size(project)
+ %span.badge
+ = storage_counter(project.statistics.storage_size)
%span.pull-right.light
%span.monospace= project.path_with_namespace + ".git"
.panel-footer
@@ -73,8 +85,8 @@
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
- %span.label.label-gray
- = repository_size(project)
+ %span.badge
+ = storage_counter(project.statistics.storage_size)
%span.pull-right.light
%span.monospace= project.path_with_namespace + ".git"
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 8bc7dc7dd51..2e6f03fcde0 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -69,8 +69,8 @@
.controls
- if project.archived
%span.label.label-warning archived
- %span.label.label-gray
- = repository_size(project)
+ %span.badge
+ = storage_counter(project.statistics.storage_size)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
.title
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 6c7c3c48604..2967da6e692 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -65,9 +65,16 @@
= @project.repository.path_to_repo
%li
- %span.light Size
- %strong
- = repository_size(@project)
+ %span.light Storage:
+ %strong= storage_counter(@project.statistics.storage_size)
+ (
+ = storage_counter(@project.statistics.repository_size)
+ repository,
+ = storage_counter(@project.statistics.build_artifacts_size)
+ build artifacts,
+ = storage_counter(@project.statistics.lfs_objects_size)
+ LFS
+ )
%li
%span.light last commit:
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 33fee334d93..2e7e5e5c309 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -18,8 +18,8 @@
.pull-right
- if project.archived
%span.label.label-warning archived
- %span.label.label-gray
- = repository_size(project)
+ %span.badge
+ = storage_counter(project.statistics.storage_size)
= link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index 450aaeb367c..d1e3cb14022 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -6,6 +6,9 @@
= deploy_key.title
.description
= deploy_key.fingerprint
+ - if deploy_key.can_push?
+ .write-access-allowed
+ Write access allowed
.deploy-key-content.prepend-left-default.deploy-key-projects
- deploy_key.projects.each do |project|
- if can?(current_user, :read_project, project)
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 901605f7ca3..c91bb9c255a 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -10,4 +10,13 @@
%p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh/README")
+ .form-group
+ .checkbox
+ = f.label :can_push do
+ = f.check_box :can_push
+ %strong Write access allowed
+ .form-group
+ %p.light.append-bottom-0
+ Allow this key to push to repository as well? (Default only allows pull access.)
+
= f.submit "Add key", class: "btn-create btn"
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index d3ed8e1bf38..90c9a0c6c2b 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -21,7 +21,7 @@
- if diff_file.deleted_file
deleted
- = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy filename to clipboard')
+ = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
- if diff_file.mode_changed?
%small
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index c56b6cc11f5..4589e112279 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,9 +1,6 @@
- if can?(current_user, :push_code, @project)
.pull-right
#new-branch.new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
- = link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do
- = icon('spinner spin')
- Checking branches
= link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid),
method: :post, class: 'btn btn-new btn-inverted btn-grouped has-tooltip available hide', title: @issue.to_branch_name do
New branch
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index 7980f7c9a72..24e86b8497f 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -8,7 +8,9 @@
= @teams.one? ? 'The team' : 'Select the team'
where the slash commands will be used in
- selected_id = @teams.keys.first if @teams.one?
- = f.select(:team_id, mattermost_teams_options(@teams), {}, { class: 'form-control', selected: "#{selected_id}", disabled: @teams.one? })
+ - options = mattermost_teams_options(@teams)
+ - options = options_for_select(options, selected_id)
+ = f.select(:team_id, options, {}, { class: 'form-control', selected: "#{selected_id}" })
.help-block
- if @teams.one?
This is the only team where you are an administrator.
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index a915c159cb4..80d4081dd7b 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -17,10 +17,10 @@
%ul.nav
%li
= link_to project_files_path(@project) do
- Files (#{repository_size})
+ Files (#{storage_counter(@project.statistics.total_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)})
+ #{'Commit'.pluralize(@project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)})
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
@@ -70,8 +70,8 @@
= link_to 'Set up Koding', add_koding_stack_path(@project)
- if @repository.gitlab_ci_yml.blank? && @project.deployment_service.present?
%li.missing
- = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up autodeploy', target_branch: 'autodeploy', context: 'autodeploy') do
- Set up autodeploy
+ = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', target_branch: 'auto-deploy', context: 'autodeploy') do
+ Set up auto deploy
- if @repository.commit
.project-last-commit{ class: container_class }
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 27d7e652721..8ff9d07860f 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -6,26 +6,27 @@ class ProjectCacheWorker
LEASE_TIMEOUT = 15.minutes.to_i
# project_id - The ID of the project for which to flush the cache.
- # refresh - An Array containing extra types of data to refresh such as
- # `:readme` to flush the README and `:changelog` to flush the
- # CHANGELOG.
- def perform(project_id, refresh = [])
+ # files - An Array containing extra types of files to refresh such as
+ # `:readme` to flush the README and `:changelog` to flush the
+ # CHANGELOG.
+ # statistics - An Array containing columns from ProjectStatistics to
+ # refresh, if empty all columns will be refreshed
+ def perform(project_id, files = [], statistics = [])
project = Project.find_by(id: project_id)
return unless project && project.repository.exists?
- update_repository_size(project)
- project.update_commit_count
+ update_statistics(project, statistics.map(&:to_sym))
- project.repository.refresh_method_caches(refresh.map(&:to_sym))
+ project.repository.refresh_method_caches(files.map(&:to_sym))
end
- def update_repository_size(project)
- return unless try_obtain_lease_for(project.id, :update_repository_size)
+ def update_statistics(project, statistics = [])
+ return unless try_obtain_lease_for(project.id, :update_statistics)
- Rails.logger.info("Updating repository size for project #{project.id}")
+ Rails.logger.info("Updating statistics for project #{project.id}")
- project.update_repository_size
+ project.statistics.refresh!(only: statistics)
end
private
diff --git a/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml b/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml
new file mode 100644
index 00000000000..850e98518a6
--- /dev/null
+++ b/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml
@@ -0,0 +1,3 @@
+---
+title: Replace wording for slash command confirmation message
+merge_request: 8123
diff --git a/changelogs/unreleased/25909-fix-mr-list-timestamp-alignment.yml b/changelogs/unreleased/25909-fix-mr-list-timestamp-alignment.yml
new file mode 100644
index 00000000000..454d8799432
--- /dev/null
+++ b/changelogs/unreleased/25909-fix-mr-list-timestamp-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: Fix mr list timestamp alignment
+merge_request: 8271
+author:
diff --git a/changelogs/unreleased/25931-gitlab-merge-request-view-crash-when-commiting-a-js-sourcemap-file.yml b/changelogs/unreleased/25931-gitlab-merge-request-view-crash-when-commiting-a-js-sourcemap-file.yml
new file mode 100644
index 00000000000..023ec1abcfa
--- /dev/null
+++ b/changelogs/unreleased/25931-gitlab-merge-request-view-crash-when-commiting-a-js-sourcemap-file.yml
@@ -0,0 +1,4 @@
+---
+title: Fix timeout when MR contains large files marked as binary by .gitattributes
+merge_request:
+author:
diff --git a/changelogs/unreleased/26026-pipeline-overflow-firefox.yml b/changelogs/unreleased/26026-pipeline-overflow-firefox.yml
new file mode 100644
index 00000000000..bf11d877c7e
--- /dev/null
+++ b/changelogs/unreleased/26026-pipeline-overflow-firefox.yml
@@ -0,0 +1,4 @@
+---
+title: Fix line breaking in nodes of the pipeline graph in firefox
+merge_request: 8292
+author:
diff --git a/changelogs/unreleased/26038-fix-confedential-warning-text-alignment.yml b/changelogs/unreleased/26038-fix-confedential-warning-text-alignment.yml
new file mode 100644
index 00000000000..420be5a3b10
--- /dev/null
+++ b/changelogs/unreleased/26038-fix-confedential-warning-text-alignment.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes confendential warning text alignment
+merge_request: 8293
+author:
diff --git a/changelogs/unreleased/26040-hide-scroll-top.yml b/changelogs/unreleased/26040-hide-scroll-top.yml
new file mode 100644
index 00000000000..f68cb1dd51d
--- /dev/null
+++ b/changelogs/unreleased/26040-hide-scroll-top.yml
@@ -0,0 +1,4 @@
+---
+title: Hide Scroll Top button for failed build page
+merge_request: 8295
+author:
diff --git a/changelogs/unreleased/auto-deploy-with-space.yml b/changelogs/unreleased/auto-deploy-with-space.yml
new file mode 100644
index 00000000000..a206e3ae85c
--- /dev/null
+++ b/changelogs/unreleased/auto-deploy-with-space.yml
@@ -0,0 +1,4 @@
+---
+title: Rename "autodeploy" to "auto deploy"
+merge_request:
+author:
diff --git a/changelogs/unreleased/changelog-mr8322.yml b/changelogs/unreleased/changelog-mr8322.yml
new file mode 100644
index 00000000000..c645ed418af
--- /dev/null
+++ b/changelogs/unreleased/changelog-mr8322.yml
@@ -0,0 +1,4 @@
+---
+title: Disable PostgreSQL statement timeouts when removing unneeded services
+merge_request: 8322
+author:
diff --git a/changelogs/unreleased/dz-rename-invalid-users.yml b/changelogs/unreleased/dz-rename-invalid-users.yml
new file mode 100644
index 00000000000..f420b069531
--- /dev/null
+++ b/changelogs/unreleased/dz-rename-invalid-users.yml
@@ -0,0 +1,4 @@
+---
+title: Rename users with namespace ending with .git
+merge_request: 8309
+author:
diff --git a/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml b/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml
new file mode 100644
index 00000000000..0fd590a877b
--- /dev/null
+++ b/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml
@@ -0,0 +1,4 @@
+---
+title: Allow to add deploy keys with write-access
+merge_request: 5807
+author: Ali Ibrahim
diff --git a/changelogs/unreleased/feature-more-storage-statistics.yml b/changelogs/unreleased/feature-more-storage-statistics.yml
new file mode 100644
index 00000000000..824fd36dc34
--- /dev/null
+++ b/changelogs/unreleased/feature-more-storage-statistics.yml
@@ -0,0 +1,4 @@
+---
+title: Add more storage statistics
+merge_request: 7754
+author: Markus Koller
diff --git a/changelogs/unreleased/filename-to-file-path.yml b/changelogs/unreleased/filename-to-file-path.yml
new file mode 100644
index 00000000000..3c6c838595a
--- /dev/null
+++ b/changelogs/unreleased/filename-to-file-path.yml
@@ -0,0 +1,3 @@
+---
+title: Rename filename to file path in tooltip of file header in merge request diff
+merge_request: 8314 \ No newline at end of file
diff --git a/changelogs/unreleased/fix-latest-pipeine-ordering.yml b/changelogs/unreleased/fix-latest-pipeine-ordering.yml
new file mode 100644
index 00000000000..b155d51741b
--- /dev/null
+++ b/changelogs/unreleased/fix-latest-pipeine-ordering.yml
@@ -0,0 +1,4 @@
+---
+title: Fix finding the latest pipeline
+merge_request: 8301
+author:
diff --git a/changelogs/unreleased/label-gfm-error-fix.yml b/changelogs/unreleased/label-gfm-error-fix.yml
new file mode 100644
index 00000000000..37f311d4790
--- /dev/null
+++ b/changelogs/unreleased/label-gfm-error-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed GFM autocomplete error when no data exists
+merge_request:
+author:
diff --git a/changelogs/unreleased/re-style-issue-new-branch.yml b/changelogs/unreleased/re-style-issue-new-branch.yml
new file mode 100644
index 00000000000..977a54ff2ae
--- /dev/null
+++ b/changelogs/unreleased/re-style-issue-new-branch.yml
@@ -0,0 +1,3 @@
+---
+title: Remove checking branches state in issue new branch button
+merge_request: 8023
diff --git a/changelogs/unreleased/resolve-note-svg-color.yml b/changelogs/unreleased/resolve-note-svg-color.yml
new file mode 100644
index 00000000000..500d4e9d5b1
--- /dev/null
+++ b/changelogs/unreleased/resolve-note-svg-color.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed resolve discussion note button color
+merge_request:
+author:
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index 3d1a41a4652..d4197da3fa9 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -10,5 +10,5 @@
# end
#
ActiveSupport::Inflector.inflections do |inflect|
- inflect.uncountable %w(award_emoji)
+ inflect.uncountable %w(award_emoji project_statistics)
end
diff --git a/db/migrate/20160811172945_add_can_push_to_keys.rb b/db/migrate/20160811172945_add_can_push_to_keys.rb
new file mode 100644
index 00000000000..5fd303fe8fb
--- /dev/null
+++ b/db/migrate/20160811172945_add_can_push_to_keys.rb
@@ -0,0 +1,14 @@
+class AddCanPushToKeys < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:keys, :can_push, :boolean, default: false, allow_null: false)
+ end
+
+ def down
+ remove_column(:keys, :can_push)
+ end
+end
diff --git a/db/migrate/20161201155511_create_project_statistics.rb b/db/migrate/20161201155511_create_project_statistics.rb
new file mode 100644
index 00000000000..26e6d3623eb
--- /dev/null
+++ b/db/migrate/20161201155511_create_project_statistics.rb
@@ -0,0 +1,20 @@
+class CreateProjectStatistics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ # use bigint columns to support values >2GB
+ counter_column = { limit: 8, null: false, default: 0 }
+
+ create_table :project_statistics do |t|
+ t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
+ t.references :namespace, null: false, index: true
+ t.integer :commit_count, counter_column
+ t.integer :storage_size, counter_column
+ t.integer :repository_size, counter_column
+ t.integer :lfs_objects_size, counter_column
+ t.integer :build_artifacts_size, counter_column
+ end
+ end
+end
diff --git a/db/migrate/20161201160452_migrate_project_statistics.rb b/db/migrate/20161201160452_migrate_project_statistics.rb
new file mode 100644
index 00000000000..3ae3f2c159b
--- /dev/null
+++ b/db/migrate/20161201160452_migrate_project_statistics.rb
@@ -0,0 +1,23 @@
+class MigrateProjectStatistics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = true
+ DOWNTIME_REASON = 'Removes two columns from the projects table'
+
+ def up
+ # convert repository_size in float (megabytes) to integer (bytes),
+ # initialize total storage_size with repository_size
+ execute <<-EOF
+ INSERT INTO project_statistics (project_id, namespace_id, commit_count, storage_size, repository_size)
+ SELECT id, namespace_id, commit_count, (repository_size * 1024 * 1024), (repository_size * 1024 * 1024) FROM projects
+ EOF
+
+ remove_column :projects, :repository_size
+ remove_column :projects, :commit_count
+ end
+
+ def down
+ add_column_with_default :projects, :repository_size, :float, default: 0.0
+ add_column_with_default :projects, :commit_count, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
new file mode 100644
index 00000000000..809b09feb84
--- /dev/null
+++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
@@ -0,0 +1,87 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDotGitFromUsernames < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::ShellAdapter
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ invalid_users.each do |user|
+ id = user['id']
+ namespace_id = user['namespace_id']
+ path_was = user['username']
+ path_was_wildcard = quote_string("#{path_was}/%")
+ path = quote_string(rename_path(path_was))
+
+ move_namespace(namespace_id, path_was, path)
+
+ execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{namespace_id}"
+ execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{namespace_id}"
+ execute "UPDATE users SET username = '#{path}' WHERE id = #{id}"
+
+ select_all("SELECT id, path FROM routes WHERE path LIKE '#{path_was_wildcard}'").each do |route|
+ new_path = "#{path}/#{route['path'].split('/').last}"
+ execute "UPDATE routes SET path = '#{new_path}' WHERE id = #{route['id']}"
+ end
+ end
+ end
+
+ def down
+ # nothing to do here
+ end
+
+ private
+
+ def invalid_users
+ select_all("SELECT u.id, u.username, n.path AS namespace_path, n.id AS namespace_id FROM users u
+ INNER JOIN namespaces n ON n.owner_id = u.id
+ WHERE n.type is NULL AND n.path LIKE '%.git'")
+ end
+
+ def route_exists?(path)
+ select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present?
+ end
+
+ # Accepts invalid path like test.git and returns test_git or
+ # test_git1 if test_git already taken
+ def rename_path(path)
+ # To stay closer with original name and reduce risk of duplicates
+ # we rename suffix instead of removing it
+ path = path.sub(/\.git\z/, '_git')
+
+ counter = 0
+ base = path
+
+ while route_exists?(path)
+ counter += 1
+ path = "#{base}#{counter}"
+ end
+
+ path
+ end
+
+ def move_namespace(namespace_id, path_was, path)
+ repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row|
+ Gitlab.config.repositories.storages[row['repository_storage']]
+ end.compact
+
+ # Move the namespace directory in all storages paths used by member projects
+ repository_storage_paths.each do |repository_storage_path|
+ # Ensure old directory exists before moving it
+ gitlab_shell.add_namespace(repository_storage_path, path_was)
+
+ unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
+ Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}"
+
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Exception.new('namespace directory cannot be moved')
+ end
+ end
+
+ Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
+ end
+end
diff --git a/db/post_migrate/20161221140236_remove_unneeded_services.rb b/db/post_migrate/20161221140236_remove_unneeded_services.rb
index a94ccc43a41..6b7e94c8641 100644
--- a/db/post_migrate/20161221140236_remove_unneeded_services.rb
+++ b/db/post_migrate/20161221140236_remove_unneeded_services.rb
@@ -4,6 +4,8 @@ class RemoveUnneededServices < ActiveRecord::Migration
DOWNTIME = false
def up
+ disable_statement_timeout
+
execute("DELETE FROM services WHERE active = false AND properties = '{}';")
end
diff --git a/db/schema.rb b/db/schema.rb
index e48ce9f4ca5..e1e72bdae8f 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: 20161221153951) do
+ActiveRecord::Schema.define(version: 20161226122833) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -527,6 +527,7 @@ ActiveRecord::Schema.define(version: 20161221153951) do
t.string "type"
t.string "fingerprint"
t.boolean "public", default: false, null: false
+ t.boolean "can_push", default: false, null: false
end
add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree
@@ -901,6 +902,19 @@ ActiveRecord::Schema.define(version: 20161221153951) do
add_index "project_import_data", ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
+ create_table "project_statistics", force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.integer "namespace_id", null: false
+ t.integer "commit_count", limit: 8, default: 0, null: false
+ t.integer "storage_size", limit: 8, default: 0, null: false
+ t.integer "repository_size", limit: 8, default: 0, null: false
+ t.integer "lfs_objects_size", limit: 8, default: 0, null: false
+ t.integer "build_artifacts_size", limit: 8, default: 0, null: false
+ end
+
+ add_index "project_statistics", ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree
+ add_index "project_statistics", ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree
+
create_table "projects", force: :cascade do |t|
t.string "name"
t.string "path"
@@ -915,11 +929,9 @@ ActiveRecord::Schema.define(version: 20161221153951) do
t.boolean "archived", default: false, null: false
t.string "avatar"
t.string "import_status"
- t.float "repository_size", default: 0.0
t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
- t.integer "commit_count", default: 0
t.text "import_error"
t.integer "ci_id"
t.boolean "shared_runners_enabled", default: true, null: false
@@ -1288,9 +1300,10 @@ ActiveRecord::Schema.define(version: 20161221153951) do
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
+ add_foreign_key "project_statistics", "projects", on_delete: :cascade
add_foreign_key "protected_branch_merge_access_levels", "protected_branches"
add_foreign_key "protected_branch_push_access_levels", "protected_branches"
add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
-end
+end \ No newline at end of file
diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md
index 3ba8387c7f0..cca422892ec 100644
--- a/doc/administration/build_artifacts.md
+++ b/doc/administration/build_artifacts.md
@@ -88,3 +88,9 @@ artifacts through the [Admin area settings](../user/admin_area/settings/continuo
[reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
[restart gitlab]: restart_gitlab.md "How to restart GitLab"
+
+## Storage statistics
+
+You can see the total storage used for build artifacts on groups and projects
+in the administration area, as well as through the [groups](../api/groups.md)
+and [projects APIs](../api/projects.md).
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 5f248ab6f91..284d5f88c55 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -20,12 +20,14 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "can_push": false,
"created_at": "2013-10-02T10:12:29Z"
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "can_push": true,
"created_at": "2013-10-02T11:12:29Z"
}
]
@@ -55,12 +57,14 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "can_push": false,
"created_at": "2013-10-02T10:12:29Z"
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "can_push": false,
"created_at": "2013-10-02T11:12:29Z"
}
]
@@ -92,6 +96,7 @@ Example response:
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "can_push": false,
"created_at": "2013-10-02T10:12:29Z"
}
```
@@ -107,14 +112,15 @@ project only if original one was is accessible by the same user.
POST /projects/:id/deploy_keys
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
-| `title` | string | yes | New deploy key's title |
-| `key` | string | yes | New deploy key |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `title` | string | yes | New deploy key's title |
+| `key` | string | yes | New deploy key |
+| `can_push` | boolean | no | Can deploy key push to the project's repository |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA...", "can_push": "true"}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/"
```
Example response:
@@ -124,6 +130,7 @@ Example response:
"key" : "ssh-rsa AAAA...",
"id" : 12,
"title" : "My deploy key",
+ "can_push": true,
"created_at" : "2015-08-29T12:44:31.550Z"
}
```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 134d7bda22f..bc737bff8ee 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -13,6 +13,7 @@ Parameters:
| `search` | string | no | Return list of authorized groups matching the search criteria |
| `order_by` | string | no | Order groups by `name` or `path`. Default is `name` |
| `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` |
+| `statistics` | boolean | no | Include group statistics (admins only) |
```
GET /groups
@@ -31,7 +32,6 @@ GET /groups
You can search for groups by name or path, see below.
-=======
## List owned groups
Get a list of groups which are owned by the authenticated user.
@@ -40,6 +40,12 @@ Get a list of groups which are owned by the authenticated user.
GET /groups/owned
```
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `statistics` | boolean | no | Include group statistics |
+
## List a group's projects
Get a list of projects in this group.
diff --git a/doc/api/projects.md b/doc/api/projects.md
index edffad555a5..122075bbd11 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -307,6 +307,8 @@ Parameters:
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
+| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
+| `statistics` | boolean | no | Include project statistics |
### List starred projects
@@ -325,6 +327,7 @@ Parameters:
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
+| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
### List ALL projects
@@ -343,6 +346,7 @@ Parameters:
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of authorized projects matching the search criteria |
+| `statistics` | boolean | no | Include project statistics |
### Get single project
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 6a9495f8892..dd14698e9cd 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -23,7 +23,7 @@
- [CI/CD pipelines settings](../user/project/pipelines/settings.md)
- [Review Apps](review_apps/index.md)
- [Git submodules](git_submodules.md) Using Git submodules in your CI jobs
-- [Autodeploy](autodeploy/index.md)
+- [Auto deploy](autodeploy/index.md)
## Breaking changes
diff --git a/doc/ci/autodeploy/img/auto_deploy_button.png b/doc/ci/autodeploy/img/auto_deploy_button.png
new file mode 100644
index 00000000000..423e76a6cda
--- /dev/null
+++ b/doc/ci/autodeploy/img/auto_deploy_button.png
Binary files differ
diff --git a/doc/ci/autodeploy/img/auto_deploy_dropdown.png b/doc/ci/autodeploy/img/auto_deploy_dropdown.png
new file mode 100644
index 00000000000..957870ec8c7
--- /dev/null
+++ b/doc/ci/autodeploy/img/auto_deploy_dropdown.png
Binary files differ
diff --git a/doc/ci/autodeploy/img/autodeploy_button.png b/doc/ci/autodeploy/img/autodeploy_button.png
deleted file mode 100644
index 9e2cd57a0ba..00000000000
--- a/doc/ci/autodeploy/img/autodeploy_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/img/autodeploy_dropdown.png b/doc/ci/autodeploy/img/autodeploy_dropdown.png
deleted file mode 100644
index d2733de83df..00000000000
--- a/doc/ci/autodeploy/img/autodeploy_dropdown.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md
index 9c79d8c457e..630207ffa09 100644
--- a/doc/ci/autodeploy/index.md
+++ b/doc/ci/autodeploy/index.md
@@ -1,8 +1,8 @@
-# Autodeploy
+# Auto deploy
> [Introduced][mr-8135] in GitLab 8.15.
-Autodeploy is an easy way to configure GitLab CI for the deployment of your
+Auto deploy is an easy way to configure GitLab CI for the deployment of your
application. GitLab Community maintains a list of `.gitlab-ci.yml`
templates for various infrastructure providers and deployment scripts
powering them. These scripts are responsible for packaging your application,
@@ -15,7 +15,7 @@ deployment.
## Supported templates
-The list of supported autodeploy templates is available [here][autodeploy-templates].
+The list of supported auto deploy templates is available [here][auto-deploy-templates].
## Configuration
@@ -24,17 +24,17 @@ credentials. For example, if you want to deploy to OpenShift you have to
enable [Kubernetes service][kubernetes-service].
1. Configure GitLab Runner to use Docker or Kubernetes executor with
[privileged mode enabled][docker-in-docker].
-1. Navigate to the "Project" tab and click "Set up autodeploy" button.
- ![Autodeploy button](img/autodeploy_button.png)
+1. Navigate to the "Project" tab and click "Set up auto deploy" button.
+ ![Auto deploy button](img/auto_deploy_button.png)
1. Select a template.
- ![Dropdown with autodeploy templates](img/autodeploy_dropdown.png)
+ ![Dropdown with auto deploy templates](img/auto_deploy_dropdown.png)
1. Commit your changes and create a merge request.
1. Test your deployment configuration using a [Review App][review-app] that was
created automatically for you.
[mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135
[project-services]: ../../project_services/project_services.md
-[autodeploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy
+[auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy
[kubernetes-service]: ../../project_services/kubernetes.md
[docker-in-docker]: ../docker/using_docker_build.md#use-docker-in-docker-executor
[review-app]: ../review_apps/index.md
diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md
index 8896d450f14..e48897426cb 100644
--- a/doc/development/ux_guide/copy.md
+++ b/doc/development/ux_guide/copy.md
@@ -101,3 +101,11 @@ The form should be titled `Edit issue`. The submit button should be labeled `Sav
| Approve | Approve an open merge request ||
| Remove approval, unapproved | Remove approval of an open merge request | Do not use `unapprove` as that is not an English word|
| Merge | Merge an open merge request ||
+
+### Comments & Discussions
+
+#### Comment
+A **comment** is a written piece of text that users of GitLab can create. Comments have the meta data of author and time stamp. Comments can be added in a variety of contexts, such as issues, merge requests, and discussions.
+
+#### Dicussion
+A **discussion** is a group of 1 or more comments. A discussion can include sub discussions. Some discussions have the special capability of being able to be **resolved**. Both the comments in the discussion and the discussion itself can be resolved. \ No newline at end of file
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index b3c73e947f0..5f6a718135d 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -40,6 +40,12 @@ In `config/gitlab.yml`:
storage_path: /mnt/storage/lfs-objects
```
+## Storage statistics
+
+You can see the total storage used for LFS objects on groups and projects
+in the administration area, as well as through the [groups](../api/groups.md)
+and [projects APIs](../api/projects.md).
+
## Known limitations
* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
@@ -47,3 +53,5 @@ In `config/gitlab.yml`:
* Currently, removing LFS objects from GitLab Git LFS storage is not supported
* LFS authentications via SSH was added with GitLab 8.12
* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
+* The storage statistics currently count each LFS object multiple times for
+ every project linking to it
diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature
new file mode 100644
index 00000000000..95ac77cddd2
--- /dev/null
+++ b/features/admin/deploy_keys.feature
@@ -0,0 +1,23 @@
+@admin
+Feature: Admin Deploy Keys
+ Background:
+ Given I sign in as an admin
+ And there are public deploy keys in system
+
+ Scenario: Deploy Keys list
+ When I visit admin deploy keys page
+ Then I should see all public deploy keys
+
+ Scenario: Deploy Keys new
+ When I visit admin deploy keys page
+ And I click 'New Deploy Key'
+ And I submit new deploy key
+ Then I should be on admin deploy keys page
+ And I should see newly created deploy key without write access
+
+ Scenario: Deploy Keys new with write access
+ When I visit admin deploy keys page
+ And I click 'New Deploy Key'
+ And I submit new deploy key with write access
+ Then I should be on admin deploy keys page
+ And I should see newly created deploy key with write access
diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb
new file mode 100644
index 00000000000..79312a5d1c5
--- /dev/null
+++ b/features/steps/admin/deploy_keys.rb
@@ -0,0 +1,59 @@
+class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'there are public deploy keys in system' do
+ create(:deploy_key, public: true)
+ create(:another_deploy_key, public: true)
+ end
+
+ step 'I should see all public deploy keys' do
+ DeployKey.are_public.each do |p|
+ expect(page).to have_content p.title
+ end
+ end
+
+ step 'I visit admin deploy key page' do
+ visit admin_deploy_key_path(deploy_key)
+ end
+
+ step 'I visit admin deploy keys page' do
+ visit admin_deploy_keys_path
+ end
+
+ step 'I click \'New Deploy Key\'' do
+ click_link 'New Deploy Key'
+ end
+
+ step 'I submit new deploy key' do
+ fill_in "deploy_key_title", with: "laptop"
+ fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
+ click_button "Create"
+ end
+
+ step 'I submit new deploy key with write access' do
+ fill_in "deploy_key_title", with: "server"
+ fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
+ check "deploy_key_can_push"
+ click_button "Create"
+ end
+
+ step 'I should be on admin deploy keys page' do
+ expect(current_path).to eq admin_deploy_keys_path
+ end
+
+ step 'I should see newly created deploy key without write access' do
+ expect(page).to have_content(deploy_key.title)
+ expect(page).to have_content('No')
+ end
+
+ step 'I should see newly created deploy key with write access' do
+ expect(page).to have_content(deploy_key.title)
+ expect(page).to have_content('Yes')
+ end
+
+ def deploy_key
+ @deploy_key ||= DeployKey.are_public.first
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index dfbb3ab86dd..d2fadf6a3d0 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -78,21 +78,21 @@ module API
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
- expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:user]) }
- expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:user]) }
- expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:user]) }
- expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:user]) }
- expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) }
+ expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
+ expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
+ expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
+ expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
+ expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
expose :created_at, :last_activity_at
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
- expose :namespace
+ expose :namespace, using: 'API::Entities::Namespace'
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
expose :avatar_url
expose :star_count, :forks_count
- expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:user]) && project.default_issues_tracker? }
+ expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds
expose :shared_with_groups do |project, options|
@@ -101,6 +101,16 @@ module API
expose :only_allow_merge_if_build_succeeds
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
+
+ expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
+ end
+
+ class ProjectStatistics < Grape::Entity
+ expose :commit_count
+ expose :storage_size
+ expose :repository_size
+ expose :lfs_objects_size
+ expose :build_artifacts_size
end
class Member < UserBasic
@@ -127,6 +137,15 @@ module API
expose :avatar_url
expose :web_url
expose :request_access_enabled
+
+ expose :statistics, if: :statistics do
+ with_options format_with: -> (value) { value.to_i } do
+ expose :storage_size
+ expose :repository_size
+ expose :lfs_objects_size
+ expose :build_artifacts_size
+ end
+ end
end
class GroupDetail < Group
@@ -298,7 +317,7 @@ module API
end
class SSHKey < Grape::Entity
- expose :id, :title, :key, :created_at
+ expose :id, :title, :key, :created_at, :can_push
end
class SSHKeyWithUser < SSHKey
@@ -391,7 +410,7 @@ module API
end
class Namespace < Grape::Entity
- expose :id, :path, :kind
+ expose :id, :name, :path, :kind
end
class MemberAccess < Grape::Entity
@@ -440,12 +459,12 @@ module API
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
- project.project_members.find_by(user_id: options[:user].id)
+ project.project_members.find_by(user_id: options[:current_user].id)
end
expose :group_access, using: Entities::GroupAccess do |project, options|
if project.group
- project.group.group_members.find_by(user_id: options[:user].id)
+ project.group.group_members.find_by(user_id: options[:current_user].id)
end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9b9d3df7435..e04d2e40fb6 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -11,6 +11,20 @@ module API
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
end
+
+ params :statistics_params do
+ optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
+ end
+
+ def present_groups(groups, options = {})
+ options = options.reverse_merge(
+ with: Entities::Group,
+ current_user: current_user,
+ )
+
+ groups = groups.with_statistics if options[:statistics]
+ present paginate(groups), options
+ end
end
resource :groups do
@@ -18,6 +32,7 @@ module API
success Entities::Group
end
params do
+ use :statistics_params
optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
optional :search, type: String, desc: 'Search for a specific group'
@@ -38,7 +53,7 @@ module API
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
- present paginate(groups), with: Entities::Group
+ present_groups groups, statistics: params[:statistics] && current_user.is_admin?
end
desc 'Get list of owned groups for authenticated user' do
@@ -46,10 +61,10 @@ module API
end
params do
use :pagination
+ use :statistics_params
end
get '/owned' do
- groups = current_user.owned_groups
- present paginate(groups), with: Entities::Group, user: current_user
+ present_groups current_user.owned_groups, statistics: params[:statistics]
end
desc 'Create a group. Available only for users who can create groups.' do
@@ -66,7 +81,7 @@ module API
group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
if group.persisted?
- present group, with: Entities::Group
+ present group, with: Entities::Group, current_user: current_user
else
render_api_error!("Failed to save group #{group.errors.messages}", 400)
end
@@ -92,7 +107,7 @@ module API
authorize! :admin_group, group
if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
- present group, with: Entities::GroupDetail
+ present group, with: Entities::GroupDetail, current_user: current_user
else
render_validation_error!(group)
end
@@ -103,7 +118,7 @@ module API
end
get ":id" do
group = find_group!(params[:id])
- present group, with: Entities::GroupDetail
+ present group, with: Entities::GroupDetail, current_user: current_user
end
desc 'Remove a group.'
@@ -134,7 +149,7 @@ module API
projects = GroupProjectsFinder.new(group).execute(current_user)
projects = filter_projects(projects)
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project
- present paginate(projects), with: entity, user: current_user
+ present paginate(projects), with: entity, current_user: current_user
end
desc 'Transfer a project to the group namespace. Available only for admin.' do
@@ -150,7 +165,7 @@ module API
result = ::Projects::TransferService.new(project, current_user).execute(group)
if result
- present group, with: Entities::GroupDetail
+ present group, with: Entities::GroupDetail, current_user: current_user
else
render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 4be659fc20b..fe00c83bff3 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -248,7 +248,7 @@ module API
rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
end
- # Projects helpers
+ # project helpers
def filter_projects(projects)
if params[:search].present?
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 2929d2157dc..3be14e8eb76 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -40,6 +40,15 @@ module API
resource :projects do
helpers do
+ params :collection_params do
+ use :sort_params
+ use :filter_params
+ use :pagination
+
+ optional :simple, type: Boolean, default: false,
+ desc: 'Return only the ID, URL, name, and path of each project'
+ end
+
params :sort_params do
optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
default: 'created_at', desc: 'Return projects ordered by field'
@@ -52,97 +61,94 @@ module API
optional :visibility, type: String, values: %w[public internal private],
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
- use :sort_params
+ end
+
+ params :statistics_params do
+ optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
params :create_params do
optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
optional :import_url, type: String, desc: 'URL from which the project is imported'
end
+
+ def present_projects(projects, options = {})
+ options = options.reverse_merge(
+ with: Entities::Project,
+ current_user: current_user,
+ simple: params[:simple],
+ )
+
+ projects = filter_projects(projects)
+ projects = projects.with_statistics if options[:statistics]
+ options[:with] = Entities::BasicProjectDetails if options[:simple]
+
+ present paginate(projects), options
+ end
end
desc 'Get a list of visible projects for authenticated user' do
success Entities::BasicProjectDetails
end
params do
- optional :simple, type: Boolean, default: false,
- desc: 'Return only the ID, URL, name, and path of each project'
- use :filter_params
- use :pagination
+ use :collection_params
end
get '/visible' do
- projects = ProjectsFinder.new.execute(current_user)
- projects = filter_projects(projects)
- entity = params[:simple] || !current_user ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
-
- present paginate(projects), with: entity, user: current_user
+ entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
+ present_projects ProjectsFinder.new.execute(current_user), with: entity
end
desc 'Get a projects list for authenticated user' do
success Entities::BasicProjectDetails
end
params do
- optional :simple, type: Boolean, default: false,
- desc: 'Return only the ID, URL, name, and path of each project'
- use :filter_params
- use :pagination
+ use :collection_params
end
get do
authenticate!
- projects = current_user.authorized_projects
- projects = filter_projects(projects)
- entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
-
- present paginate(projects), with: entity, user: current_user
+ present_projects current_user.authorized_projects,
+ with: Entities::ProjectWithAccess
end
desc 'Get an owned projects list for authenticated user' do
success Entities::BasicProjectDetails
end
params do
- use :filter_params
- use :pagination
+ use :collection_params
+ use :statistics_params
end
get '/owned' do
authenticate!
- projects = current_user.owned_projects
- projects = filter_projects(projects)
-
- present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
+ present_projects current_user.owned_projects,
+ with: Entities::ProjectWithAccess,
+ statistics: params[:statistics]
end
desc 'Gets starred project for the authenticated user' do
success Entities::BasicProjectDetails
end
params do
- use :filter_params
- use :pagination
+ use :collection_params
end
get '/starred' do
authenticate!
- projects = current_user.viewable_starred_projects
- projects = filter_projects(projects)
-
- present paginate(projects), with: Entities::Project, user: current_user
+ present_projects current_user.viewable_starred_projects
end
desc 'Get all projects for admin user' do
success Entities::BasicProjectDetails
end
params do
- use :filter_params
- use :pagination
+ use :collection_params
+ use :statistics_params
end
get '/all' do
authenticated_as_admin!
- projects = Project.all
- projects = filter_projects(projects)
-
- present paginate(projects), with: Entities::ProjectWithAccess, user: current_user
+ present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics]
end
desc 'Search for projects the current user has access to' do
@@ -221,7 +227,7 @@ module API
end
get ":id" do
entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails
- present user_project, with: entity, user: current_user,
+ present user_project, with: entity, current_user: current_user,
user_can_admin_project: can?(current_user, :admin_project, user_project)
end
diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb
index 6be7f690676..39b86c61a18 100644
--- a/lib/gitlab/auth/result.rb
+++ b/lib/gitlab/auth/result.rb
@@ -9,8 +9,7 @@ module Gitlab
def lfs_deploy_token?(for_project)
type == :lfs_deploy_token &&
- actor &&
- actor.projects.include?(for_project)
+ actor.try(:has_access_to?, for_project)
end
def success?
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 3d203017d9f..9c391fa92a3 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -1,14 +1,16 @@
module Gitlab
module Checks
class ChangeAccess
- attr_reader :user_access, :project
+ attr_reader :user_access, :project, :skip_authorization
- def initialize(change, user_access:, project:, env: {})
+ def initialize(
+ change, user_access:, project:, env: {}, skip_authorization: false)
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
@branch_name = Gitlab::Git.branch_name(@ref)
@user_access = user_access
@project = project
@env = env
+ @skip_authorization = skip_authorization
end
def exec
@@ -24,6 +26,7 @@ module Gitlab
protected
def protected_branch_checks
+ return if skip_authorization
return unless @branch_name
return unless project.protected_branch?(@branch_name)
@@ -49,6 +52,8 @@ module Gitlab
end
def tag_checks
+ return if skip_authorization
+
tag_ref = Gitlab::Git.tag_name(@ref)
if tag_ref && protected_tag?(tag_ref) && user_access.cannot_do_action?(:admin_project)
@@ -57,6 +62,8 @@ module Gitlab
end
def push_checks
+ return if skip_authorization
+
if user_access.cannot_do_action?(:push_code)
"You are not allowed to push code to this project."
end
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb
index 56530448f36..329d12f13d1 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb
@@ -61,7 +61,10 @@ module Gitlab
end
def cacheable?(diff_file)
- @merge_request_diff.present? && diff_file.blob && diff_file.blob.text?
+ @merge_request_diff.present? &&
+ diff_file.blob &&
+ diff_file.blob.text? &&
+ @project.repository.diffable?(diff_file.blob)
end
def cache_key
diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb
index f586c5ab062..8c8dd1b9cef 100644
--- a/lib/gitlab/email/reply_parser.rb
+++ b/lib/gitlab/email/reply_parser.rb
@@ -13,9 +13,17 @@ module Gitlab
encoding = body.encoding
- body = discourse_email_trimmer(body)
+ body = EmailReplyTrimmer.trim(body)
- body = EmailReplyParser.parse_reply(body)
+ return '' unless body
+
+ # not using /\s+$/ here because that deletes empty lines
+ body = body.gsub(/[ \t]$/, '')
+
+ # NOTE: We currently don't support empty quotes.
+ # EmailReplyTrimmer allows this as a special case,
+ # so we detect it manually here.
+ return "" if body.lines.all? { |l| l.strip.empty? || l.start_with?('>') }
body.force_encoding(encoding).encode("UTF-8")
end
@@ -57,30 +65,6 @@ module Gitlab
rescue
nil
end
-
- REPLYING_HEADER_LABELS = %w(From Sent To Subject Reply To Cc Bcc Date)
- REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |label| "#{label}:" })
-
- def discourse_email_trimmer(body)
- lines = body.scrub.lines.to_a
- range_end = 0
-
- lines.each_with_index do |l, idx|
- # This one might be controversial but so many reply lines have years, times and end with a colon.
- # Let's try it and see how well it works.
- break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) ||
- (l =~ /On \w+ \d+,? \d+,?.*wrote:/)
-
- # Headers on subsequent lines
- break if (0..2).all? { |off| lines[idx + off] =~ REPLYING_HEADER_REGEX }
- # Headers on the same line
- break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3
-
- range_end = idx
- end
-
- lines[0..range_end].join.strip
- end
end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index c6b6efda360..7e1484613f2 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -7,7 +7,8 @@ module Gitlab
ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.',
- deploy_key: 'Deploy keys are not allowed to push code.',
+ deploy_key_upload:
+ 'This deploy key does not have write access to this project.',
no_repo: 'A repository for this project does not exist yet.'
}
@@ -31,12 +32,13 @@ module Gitlab
check_active_user!
check_project_accessibility!
check_command_existence!(cmd)
+ check_repository_existence!
case cmd
when *DOWNLOAD_COMMANDS
- download_access_check
+ check_download_access!
when *PUSH_COMMANDS
- push_access_check(changes)
+ check_push_access!(changes)
end
build_status_object(true)
@@ -44,32 +46,10 @@ module Gitlab
build_status_object(false, ex.message)
end
- def download_access_check
- if user
- user_download_access_check
- elsif deploy_key.nil? && !guest_can_downlod_code?
- raise UnauthorizedError, ERROR_MESSAGES[:download]
- end
- end
-
- def push_access_check(changes)
- if user
- user_push_access_check(changes)
- else
- raise UnauthorizedError, ERROR_MESSAGES[deploy_key ? :deploy_key : :upload]
- end
- end
-
- def guest_can_downlod_code?
+ def guest_can_download_code?
Guest.can?(:download_code, project)
end
- def user_download_access_check
- unless user_can_download_code? || build_can_download_code?
- raise UnauthorizedError, ERROR_MESSAGES[:download]
- end
- end
-
def user_can_download_code?
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
end
@@ -78,35 +58,6 @@ module Gitlab
authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
end
- def user_push_access_check(changes)
- unless authentication_abilities.include?(:push_code)
- raise UnauthorizedError, ERROR_MESSAGES[:upload]
- end
-
- if changes.blank?
- return # Allow access.
- end
-
- unless project.repository.exists?
- raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
- end
-
- changes_list = Gitlab::ChangesList.new(changes)
-
- # Iterate over all changes to find if user allowed all of them to be applied
- changes_list.each do |change|
- status = change_access_check(change)
- unless status.allowed?
- # If user does not have access to make at least one change - cancel all push
- raise UnauthorizedError, status.message
- end
- end
- end
-
- def change_access_check(change)
- Checks::ChangeAccess.new(change, user_access: user_access, project: project, env: @env).exec
- end
-
def protocol_allowed?
Gitlab::ProtocolAccess.allowed?(protocol)
end
@@ -120,6 +71,8 @@ module Gitlab
end
def check_active_user!
+ return if deploy_key?
+
if user && !user_access.allowed?
raise UnauthorizedError, "Your account has been blocked."
end
@@ -137,33 +90,92 @@ module Gitlab
end
end
- def matching_merge_request?(newrev, branch_name)
- Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
+ def check_repository_existence!
+ unless project.repository.exists?
+ raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
+ end
end
- def deploy_key
- actor if actor.is_a?(DeployKey)
+ def check_download_access!
+ return if deploy_key?
+
+ passed = user_can_download_code? ||
+ build_can_download_code? ||
+ guest_can_download_code?
+
+ unless passed
+ raise UnauthorizedError, ERROR_MESSAGES[:download]
+ end
end
- def deploy_key_can_read_project?
+ def check_push_access!(changes)
if deploy_key
- return true if project.public?
- deploy_key.projects.include?(project)
+ check_deploy_key_push_access!
+ elsif user
+ check_user_push_access!
else
- false
+ raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
+
+ return if changes.blank? # Allow access.
+
+ check_change_access!(changes)
end
- def can_read_project?
- if user
- user_access.can_read_project?
- elsif deploy_key
- deploy_key_can_read_project?
- else
- Guest.can?(:read_project, project)
+ def check_user_push_access!
+ unless authentication_abilities.include?(:push_code)
+ raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
end
+ def check_deploy_key_push_access!
+ unless deploy_key.can_push_to?(project)
+ raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload]
+ end
+ end
+
+ def check_change_access!(changes)
+ changes_list = Gitlab::ChangesList.new(changes)
+
+ # Iterate over all changes to find if user allowed all of them to be applied
+ changes_list.each do |change|
+ status = check_single_change_access(change)
+ unless status.allowed?
+ # If user does not have access to make at least one change - cancel all push
+ raise UnauthorizedError, status.message
+ end
+ end
+ end
+
+ def check_single_change_access(change)
+ Checks::ChangeAccess.new(
+ change,
+ user_access: user_access,
+ project: project,
+ env: @env,
+ skip_authorization: deploy_key?).exec
+ end
+
+ def matching_merge_request?(newrev, branch_name)
+ Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
+ end
+
+ def deploy_key
+ actor if deploy_key?
+ end
+
+ def deploy_key?
+ actor.is_a?(DeployKey)
+ end
+
+ def can_read_project?
+ if deploy_key
+ deploy_key.has_access_to?(project)
+ elsif user
+ user.can?(:read_project, project)
+ end || Guest.can?(:read_project, project)
+ end
+
protected
def user
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 2c06c4ff1ef..67eaa5e088d 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,6 +1,6 @@
module Gitlab
class GitAccessWiki < GitAccess
- def guest_can_downlod_code?
+ def guest_can_download_code?
Guest.can?(:download_wiki_code, project)
end
@@ -8,7 +8,7 @@ module Gitlab
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code)
end
- def change_access_check(change)
+ def check_single_change_access(change)
if user_access.can_do_action?(:create_wiki)
build_status_object(true)
else
diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb
index d19b0a52043..9d2ecee9756 100644
--- a/lib/gitlab/template/gitlab_ci_yml_template.rb
+++ b/lib/gitlab/template/gitlab_ci_yml_template.rb
@@ -15,7 +15,7 @@ module Gitlab
{
'General' => '',
'Pages' => 'Pages',
- 'Autodeploy' => 'autodeploy'
+ 'Auto deploy' => 'autodeploy'
}
end
@@ -28,7 +28,7 @@ module Gitlab
end
def dropdown_names(context)
- categories = context == 'autodeploy' ? ['Autodeploy'] : ['General', 'Pages']
+ categories = context == 'autodeploy' ? ['Auto deploy'] : ['General', 'Pages']
super().slice(*categories)
end
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 9858d2e7d83..6c7e673fb9f 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -8,6 +8,8 @@ module Gitlab
end
def can_do_action?(action)
+ return false if no_user_or_blocked?
+
@permission_cache ||= {}
@permission_cache[action] ||= user.can?(action, project)
end
@@ -17,7 +19,7 @@ module Gitlab
end
def allowed?
- return false if user.blank? || user.blocked?
+ return false if no_user_or_blocked?
if user.requires_ldap_check? && user.try_obtain_ldap_lease
return false unless Gitlab::LDAP::Access.allowed?(user)
@@ -27,7 +29,7 @@ module Gitlab
end
def can_push_to_branch?(ref)
- return false unless user
+ return false if no_user_or_blocked?
if project.protected_branch?(ref)
return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
@@ -40,7 +42,7 @@ module Gitlab
end
def can_merge_to_branch?(ref)
- return false unless user
+ return false if no_user_or_blocked?
if project.protected_branch?(ref)
access_levels = project.protected_branches.matching(ref).map(&:merge_access_levels).flatten
@@ -51,9 +53,15 @@ module Gitlab
end
def can_read_project?
- return false unless user
+ return false if no_user_or_blocked?
user.can?(:read_project, project)
end
+
+ private
+
+ def no_user_or_blocked?
+ user.nil? || user.blocked?
+ end
end
end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index dbdd4e977e8..a2eca74a3c8 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -63,8 +63,7 @@ namespace :gitlab do
if project.persisted?
puts " * Created #{project.name} (#{repo_path})".color(:green)
- project.update_repository_size
- project.update_commit_count
+ ProjectCacheWorker.perform(project.id)
else
puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red)
puts " Errors: #{project.errors.messages}".color(:red)
diff --git a/lib/tasks/gitlab/update_commit_count.rake b/lib/tasks/gitlab/update_commit_count.rake
deleted file mode 100644
index 3bd10b0208b..00000000000
--- a/lib/tasks/gitlab/update_commit_count.rake
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace :gitlab do
- desc "GitLab | Update commit count for projects"
- task update_commit_count: :environment do
- projects = Project.where(commit_count: 0)
- puts "#{projects.size} projects need to be updated. This might take a while."
- ask_to_continue unless ENV['force'] == 'yes'
-
- projects.find_each(batch_size: 100) do |project|
- print "#{project.name_with_namespace.color(:yellow)} ... "
-
- unless project.repo_exists?
- puts "skipping, because the repo is empty".color(:magenta)
- next
- end
-
- project.update_commit_count
- puts project.commit_count.to_s.color(:green)
- end
- end
-end
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index a81645acd2b..477fab9e964 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -2,7 +2,7 @@ include ActionDispatch::TestProcess
FactoryGirl.define do
factory :lfs_object do
- oid "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80"
+ sequence(:oid) { |n| "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a%05x" % n }
size 499013
end
diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb
new file mode 100644
index 00000000000..72d43096216
--- /dev/null
+++ b/spec/factories/project_statistics.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :project_statistics do
+ project { create :project }
+ namespace { project.namespace }
+ end
+end
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index 92f1ab90881..ea7a97d1d4f 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -26,7 +26,7 @@ describe 'Auto deploy' do
it 'does not show a button to set up auto deploy' do
visit namespace_project_path(project.namespace, project)
- expect(page).to have_no_content('Set up autodeploy')
+ expect(page).to have_no_content('Set up auto deploy')
end
end
@@ -37,11 +37,11 @@ describe 'Auto deploy' do
end
it 'shows a button to set up auto deploy' do
- expect(page).to have_link('Set up autodeploy')
+ expect(page).to have_link('Set up auto deploy')
end
- it 'includes Kubernetes as an available template', js: true do
- click_link 'Set up autodeploy'
+ it 'includes OpenShift as an available template', js: true do
+ click_link 'Set up auto deploy'
click_button 'Choose a GitLab CI Yaml template'
within '.gitlab-ci-yml-selector' do
@@ -49,8 +49,8 @@ describe 'Auto deploy' do
end
end
- it 'creates a merge request using "autodeploy" branch', js: true do
- click_link 'Set up autodeploy'
+ it 'creates a merge request using "auto-deploy" branch', js: true do
+ click_link 'Set up auto deploy'
click_button 'Choose a GitLab CI Yaml template'
within '.gitlab-ci-yml-selector' do
click_on 'OpenShift'
@@ -58,7 +58,7 @@ describe 'Auto deploy' do
wait_for_ajax
click_button 'Commit Changes'
- expect(page).to have_content('New Merge Request From autodeploy into master')
+ expect(page).to have_content('New Merge Request From auto-deploy into master')
end
end
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index d0294908d2c..3489331a1b6 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -47,7 +47,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect_to_wrap(true, label_item, note, label.title)
end
- it "does not show drpdown when preceded with a special character" do
+ it "does not show dropdown when preceded with a special character" do
note = find('#note_note')
page.within '.timeline-content-form' do
note.native.send_keys('')
@@ -65,6 +65,17 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container', visible: false)
end
+ it "does not throw an error if no labels exist" do
+ note = find('#note_note')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('')
+ note.native.send_keys('~')
+ note.click
+ end
+
+ expect(page).to have_selector('.atwho-container', visible: false)
+ end
+
it 'doesn\'t wrap for assignee values' do
note = find('#note_note')
page.within '.timeline-content-form' do
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 3f2da1c380c..31f75512f4a 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -30,7 +30,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
write_note("/due 2016-08-28")
expect(page).not_to have_content '/due 2016-08-28'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
issue.reload
@@ -51,7 +51,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
write_note("/due 2016-08-28")
expect(page).to have_content '/due 2016-08-28'
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Commands applied'
issue.reload
@@ -70,7 +70,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
write_note("/remove_due_date")
expect(page).not_to have_content '/remove_due_date'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
issue.reload
@@ -91,7 +91,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
write_note("/remove_due_date")
expect(page).to have_content '/remove_due_date'
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Commands applied'
issue.reload
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index 7b8af555f0e..b1b3a47a1ce 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -31,7 +31,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
write_note("/wip")
expect(page).not_to have_content '/wip'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(merge_request.reload.work_in_progress?).to eq true
end
@@ -42,7 +42,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
write_note("/wip")
expect(page).not_to have_content '/wip'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(merge_request.reload.work_in_progress?).to eq false
end
@@ -61,7 +61,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
write_note("/wip")
expect(page).not_to have_content '/wip'
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Commands applied'
expect(merge_request.reload.work_in_progress?).to eq false
end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
new file mode 100644
index 00000000000..4627a1e1872
--- /dev/null
+++ b/spec/helpers/storage_helper_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe StorageHelper do
+ describe '#storage_counter' do
+ it 'formats bytes to one decimal place' do
+ expect(helper.storage_counter(1.23.megabytes)).to eq '1.2 MB'
+ end
+
+ it 'does not add decimals for sizes < 1 MB' do
+ expect(helper.storage_counter(23.5.kilobytes)).to eq '24 KB'
+ end
+
+ it 'does not add decimals for zeroes' do
+ expect(helper.storage_counter(2.megabytes)).to eq '2 MB'
+ end
+
+ it 'uses commas as thousands separator' do
+ expect(helper.storage_counter(100_000_000_000_000_000)).to eq '90,949.5 TB'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index 2a680f03476..f2bc15d39d7 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -1,21 +1,30 @@
require 'spec_helper'
describe Gitlab::Diff::FileCollection::MergeRequestDiff do
- let(:merge_request) { create :merge_request }
+ let(:merge_request) { create(:merge_request) }
+ let(:diff_files) { described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files }
- it 'does not hightlight binary files' do
+ it 'does not highlight binary files' do
allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => false))
expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
- described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files
+ diff_files
end
- it 'does not hightlight file if blob is not accessable' do
+ it 'does not highlight file if blob is not accessable' do
allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil)
expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
- described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files
+ diff_files
+ end
+
+ it 'does not files marked as undiffable in .gitattributes' do
+ allow_any_instance_of(Repository).to receive(:diffable?).and_return(false)
+
+ expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
+
+ diff_files
end
end
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index c7a0139d32a..28698e89c33 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -88,8 +88,6 @@ describe Gitlab::Email::ReplyParser, lib: true do
expect(test_parse_body(fixture_file("emails/inline_reply.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
- On Wed, Oct 8, 2014 at 11:12 AM, techAPJ <info@unconfigured.discourse.org> wrote:
-
> techAPJ <https://meta.discourse.org/users/techapj>
> November 28
>
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index f1d0a190002..44b84afde47 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -50,7 +50,7 @@ describe Gitlab::GitAccess, lib: true do
end
end
- describe 'download_access_check' do
+ describe '#check_download_access!' do
subject { access.check('git-upload-pack', '_any') }
describe 'master permissions' do
@@ -82,7 +82,7 @@ describe Gitlab::GitAccess, lib: true do
end
end
- describe 'without acccess to project' do
+ describe 'without access to project' do
context 'pull code' do
it { expect(subject.allowed?).to be_falsey }
end
@@ -112,7 +112,7 @@ describe Gitlab::GitAccess, lib: true do
end
describe 'deploy key permissions' do
- let(:key) { create(:deploy_key) }
+ let(:key) { create(:deploy_key, user: user) }
let(:actor) { key }
context 'pull code' do
@@ -136,7 +136,7 @@ describe Gitlab::GitAccess, lib: true do
end
context 'from private project' do
- let(:project) { create(:project, :internal) }
+ let(:project) { create(:project, :private) }
it { expect(subject).not_to be_allowed }
end
@@ -183,7 +183,7 @@ describe Gitlab::GitAccess, lib: true do
end
end
- describe 'push_access_check' do
+ describe '#check_push_access!' do
before { merge_into_protected_branch }
let(:unprotected_branch) { FFaker::Internet.user_name }
@@ -231,7 +231,7 @@ describe Gitlab::GitAccess, lib: true do
permissions_matrix[role].each do |action, allowed|
context action do
- subject { access.push_access_check(changes[action]) }
+ subject { access.send(:check_push_access!, changes[action]) }
it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
end
end
@@ -353,13 +353,13 @@ describe Gitlab::GitAccess, lib: true do
end
end
- shared_examples 'can not push code' do
+ shared_examples 'pushing code' do |can|
subject { access.check('git-receive-pack', '_any') }
context 'when project is authorized' do
before { authorize }
- it { expect(subject).not_to be_allowed }
+ it { expect(subject).public_send(can, be_allowed) }
end
context 'when unauthorized' do
@@ -386,7 +386,7 @@ describe Gitlab::GitAccess, lib: true do
describe 'build authentication abilities' do
let(:authentication_abilities) { build_authentication_abilities }
- it_behaves_like 'can not push code' do
+ it_behaves_like 'pushing code', :not_to do
def authorize
project.team << [user, :reporter]
end
@@ -394,12 +394,26 @@ describe Gitlab::GitAccess, lib: true do
end
describe 'deploy key permissions' do
- let(:key) { create(:deploy_key) }
+ let(:key) { create(:deploy_key, user: user, can_push: can_push) }
let(:actor) { key }
- it_behaves_like 'can not push code' do
- def authorize
- key.projects << project
+ context 'when deploy_key can push' do
+ let(:can_push) { true }
+
+ it_behaves_like 'pushing code', :to do
+ def authorize
+ key.projects << project
+ end
+ end
+ end
+
+ context 'when deploy_key cannot push' do
+ let(:can_push) { false }
+
+ it_behaves_like 'pushing code', :not_to do
+ def authorize
+ key.projects << project
+ end
end
end
end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 578db51631e..a5d172233cc 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::GitAccessWiki, lib: true do
['6f6d7e7ed 570e7b2ab refs/heads/master']
end
- describe '#download_access_check' do
+ describe '#access_check_download!' do
subject { access.check('git-upload-pack', '_any') }
before do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index f420d71dee2..ceed9c942c1 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -192,6 +192,7 @@ project:
- authorized_users
- project_authorizations
- route
+- statistics
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 78d6b2c5032..ac26c831fd0 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -247,6 +247,7 @@ DeployKey:
- type
- fingerprint
- public
+- can_push
Service:
- id
- type
diff --git a/spec/migrations/remove_dot_git_from_usernames.rb b/spec/migrations/remove_dot_git_from_usernames.rb
new file mode 100644
index 00000000000..1b1d2adc463
--- /dev/null
+++ b/spec/migrations/remove_dot_git_from_usernames.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb')
+
+describe RemoveDotGitFromUsernames do
+ let(:user) { create(:user) }
+
+ describe '#up' do
+ let(:migration) { described_class.new }
+
+ before do
+ namespace = user.namespace
+ namespace.path = 'test.git'
+ namespace.save!(validate: false)
+
+ user.username = 'test.git'
+ user.save!(validate: false)
+ end
+
+ it 'renames user with .git in username' do
+ migration.up
+
+ expect(user.reload.username).to eq('test_git')
+ expect(user.namespace.reload.path).to eq('test_git')
+ expect(user.namespace.route.path).to eq('test_git')
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index a7e90c8a381..7e1d1126b97 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -85,4 +85,30 @@ describe Ci::Build, models: true do
it { expect(build.trace_file_path).to eq(build.old_path_to_trace) }
end
end
+
+ describe '#update_project_statistics' do
+ let!(:build) { create(:ci_build, artifacts_size: 23) }
+
+ it 'updates project statistics when the artifact size changes' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(build.project_id, [], [:build_artifacts_size])
+
+ build.artifacts_size = 42
+ build.save!
+ end
+
+ it 'does not update project statistics when the artifact size stays the same' do
+ expect(ProjectCacheWorker).not_to receive(:perform_async)
+
+ build.name = 'changed'
+ build.save!
+ end
+
+ it 'updates project statistics when the build is destroyed' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(build.project_id, [], [:build_artifacts_size])
+
+ build.destroy
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index dc377d15f15..cebaa157ef3 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -464,6 +464,19 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '.latest_successful_for' do
+ include_context 'with some outdated pipelines'
+
+ let!(:latest_successful_pipeline) do
+ create_pipeline(:success, 'ref', 'D')
+ end
+
+ it 'returns the latest successful pipeline' do
+ expect(described_class.latest_successful_for('ref')).
+ to eq(latest_successful_pipeline)
+ end
+ end
+
describe '#status' do
let!(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') }
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 93623e8e99b..8ef8218cf74 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -1,8 +1,22 @@
require 'spec_helper'
describe DeployKey, models: true do
+ include EmailHelpers
+
describe "Associations" do
it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:projects) }
end
+
+ describe 'notification' do
+ let(:user) { create(:user) }
+
+ it 'does not send a notification' do
+ perform_enqueued_jobs do
+ create(:deploy_key, user: user)
+ end
+
+ should_not_email(user)
+ end
+ end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 2a33d819138..7758b7ffa97 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Key, models: true do
+ include EmailHelpers
+
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
@@ -96,4 +98,16 @@ describe Key, models: true do
expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key)
end
end
+
+ describe 'notification' do
+ let(:user) { create(:user) }
+
+ it 'sends a notification' do
+ perform_enqueued_jobs do
+ create(:key, user: user)
+ end
+
+ should_email(user)
+ end
+ end
end
diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb
new file mode 100644
index 00000000000..7bc278e350f
--- /dev/null
+++ b/spec/models/lfs_objects_project_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe LfsObjectsProject, models: true do
+ subject { create(:lfs_objects_project, project: project) }
+ let(:project) { create(:empty_project) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:lfs_object) }
+ end
+
+ describe 'validation' do
+ it { is_expected.to validate_presence_of(:lfs_object_id) }
+ it { is_expected.to validate_uniqueness_of(:lfs_object_id).scoped_to(:project_id).with_message("already exists in project") }
+
+ it { is_expected.to validate_presence_of(:project_id) }
+ end
+
+ describe '#update_project_statistics' do
+ it 'updates project statistics when the object is added' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:lfs_objects_size])
+
+ subject.save!
+ end
+
+ it 'updates project statistics when the object is removed' do
+ subject.save!
+
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:lfs_objects_size])
+
+ subject.destroy
+ end
+ end
+end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 9fd06bb6b23..600538ff5f4 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -4,6 +4,7 @@ describe Namespace, models: true do
let!(:namespace) { create(:namespace) }
it { is_expected.to have_many :projects }
+ it { is_expected.to have_many :project_statistics }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
@@ -57,6 +58,50 @@ describe Namespace, models: true do
end
end
+ describe '.with_statistics' do
+ let(:namespace) { create :namespace }
+
+ let(:project1) do
+ create(:empty_project,
+ namespace: namespace,
+ statistics: build(:project_statistics,
+ storage_size: 606,
+ repository_size: 101,
+ lfs_objects_size: 202,
+ build_artifacts_size: 303))
+ end
+
+ let(:project2) do
+ create(:empty_project,
+ namespace: namespace,
+ statistics: build(:project_statistics,
+ storage_size: 60,
+ repository_size: 10,
+ lfs_objects_size: 20,
+ build_artifacts_size: 30))
+ end
+
+ it "sums all project storage counters in the namespace" do
+ project1
+ project2
+ statistics = Namespace.with_statistics.find(namespace.id)
+
+ expect(statistics.storage_size).to eq 666
+ expect(statistics.repository_size).to eq 111
+ expect(statistics.lfs_objects_size).to eq 222
+ expect(statistics.build_artifacts_size).to eq 333
+ end
+
+ it "correctly handles namespaces without projects" do
+ statistics = Namespace.with_statistics.find(namespace.id)
+
+ expect(statistics.storage_size).to eq 0
+ expect(statistics.repository_size).to eq 0
+ expect(statistics.lfs_objects_size).to eq 0
+ expect(statistics.build_artifacts_size).to eq 0
+ end
+ end
+
describe '#move_dir' do
before do
@namespace = create :namespace
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 88d5d14f855..fb225eb7625 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -49,6 +49,7 @@ describe Project, models: true do
it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) }
it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) }
it { is_expected.to have_one(:project_feature).dependent(:destroy) }
+ it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) }
it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) }
it { is_expected.to have_one(:last_event).class_name('Event') }
it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
@@ -1729,6 +1730,26 @@ describe Project, models: true do
end
end
+ describe '#update_project_statistics' do
+ let(:project) { create(:empty_project) }
+
+ it "is called after creation" do
+ expect(project.statistics).to be_a ProjectStatistics
+ expect(project.statistics).to be_persisted
+ end
+
+ it "copies the namespace_id" do
+ expect(project.statistics.namespace_id).to eq project.namespace_id
+ end
+
+ it "updates the namespace_id when changed" do
+ namespace = create(:namespace)
+ project.update(namespace: namespace)
+
+ expect(project.statistics.namespace_id).to eq namespace.id
+ end
+ end
+
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
new file mode 100644
index 00000000000..77403cc9eb0
--- /dev/null
+++ b/spec/models/project_statistics_spec.rb
@@ -0,0 +1,160 @@
+require 'rails_helper'
+
+describe ProjectStatistics, models: true do
+ let(:project) { create :empty_project }
+ let(:statistics) { project.statistics }
+
+ describe 'constants' do
+ describe 'STORAGE_COLUMNS' do
+ it 'is an array of symbols' do
+ expect(described_class::STORAGE_COLUMNS).to be_kind_of Array
+ expect(described_class::STORAGE_COLUMNS.map(&:class).uniq).to eq [Symbol]
+ end
+ end
+
+ describe 'STATISTICS_COLUMNS' do
+ it 'is an array of symbols' do
+ expect(described_class::STATISTICS_COLUMNS).to be_kind_of Array
+ expect(described_class::STATISTICS_COLUMNS.map(&:class).uniq).to eq [Symbol]
+ end
+
+ it 'includes all storage columns' do
+ expect(described_class::STATISTICS_COLUMNS & described_class::STORAGE_COLUMNS).to eq described_class::STORAGE_COLUMNS
+ end
+ end
+ end
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:namespace) }
+ end
+
+ describe 'statistics columns' do
+ it "support values up to 8 exabytes" do
+ statistics.update!(
+ commit_count: 8.exabytes - 1,
+ repository_size: 2.exabytes,
+ lfs_objects_size: 2.exabytes,
+ build_artifacts_size: 4.exabytes - 1,
+ )
+
+ statistics.reload
+
+ expect(statistics.commit_count).to eq(8.exabytes - 1)
+ expect(statistics.repository_size).to eq(2.exabytes)
+ expect(statistics.lfs_objects_size).to eq(2.exabytes)
+ expect(statistics.build_artifacts_size).to eq(4.exabytes - 1)
+ expect(statistics.storage_size).to eq(8.exabytes - 1)
+ end
+ end
+
+ describe '#total_repository_size' do
+ it "sums repository and LFS object size" do
+ statistics.repository_size = 2
+ statistics.lfs_objects_size = 3
+ statistics.build_artifacts_size = 4
+
+ expect(statistics.total_repository_size).to eq 5
+ end
+ end
+
+ describe '#refresh!' do
+ before do
+ allow(statistics).to receive(:update_commit_count)
+ allow(statistics).to receive(:update_repository_size)
+ allow(statistics).to receive(:update_lfs_objects_size)
+ allow(statistics).to receive(:update_build_artifacts_size)
+ allow(statistics).to receive(:update_storage_size)
+ end
+
+ context "without arguments" do
+ before do
+ statistics.refresh!
+ end
+
+ it "sums all counters" do
+ expect(statistics).to have_received(:update_commit_count)
+ expect(statistics).to have_received(:update_repository_size)
+ expect(statistics).to have_received(:update_lfs_objects_size)
+ expect(statistics).to have_received(:update_build_artifacts_size)
+ end
+ end
+
+ context "when passing an only: argument" do
+ before do
+ statistics.refresh! only: [:lfs_objects_size]
+ end
+
+ it "only updates the given columns" do
+ expect(statistics).to have_received(:update_lfs_objects_size)
+ expect(statistics).not_to have_received(:update_commit_count)
+ expect(statistics).not_to have_received(:update_repository_size)
+ expect(statistics).not_to have_received(:update_build_artifacts_size)
+ end
+ end
+ end
+
+ describe '#update_commit_count' do
+ before do
+ allow(project.repository).to receive(:commit_count).and_return(23)
+ statistics.update_commit_count
+ end
+
+ it "stores the number of commits in the repository" do
+ expect(statistics.commit_count).to eq 23
+ end
+ end
+
+ describe '#update_repository_size' do
+ before do
+ allow(project.repository).to receive(:size).and_return(12.megabytes)
+ statistics.update_repository_size
+ end
+
+ it "stores the size of the repository" do
+ expect(statistics.repository_size).to eq 12.megabytes
+ end
+ end
+
+ describe '#update_lfs_objects_size' do
+ let!(:lfs_object1) { create(:lfs_object, size: 23.megabytes) }
+ let!(:lfs_object2) { create(:lfs_object, size: 34.megabytes) }
+ let!(:lfs_objects_project1) { create(:lfs_objects_project, project: project, lfs_object: lfs_object1) }
+ let!(:lfs_objects_project2) { create(:lfs_objects_project, project: project, lfs_object: lfs_object2) }
+
+ before do
+ statistics.update_lfs_objects_size
+ end
+
+ it "stores the size of related LFS objects" do
+ expect(statistics.lfs_objects_size).to eq 57.megabytes
+ end
+ end
+
+ describe '#update_build_artifacts_size' do
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:build1) { create(:ci_build, pipeline: pipeline, artifacts_size: 45.megabytes) }
+ let!(:build2) { create(:ci_build, pipeline: pipeline, artifacts_size: 56.megabytes) }
+
+ before do
+ statistics.update_build_artifacts_size
+ end
+
+ it "stores the size of related build artifacts" do
+ expect(statistics.build_artifacts_size).to eq 101.megabytes
+ end
+ end
+
+ describe '#update_storage_size' do
+ it "sums all storage counters" do
+ statistics.update!(
+ repository_size: 2,
+ lfs_objects_size: 3,
+ )
+
+ statistics.reload
+
+ expect(statistics.storage_size).to eq 5
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index cdeb965b413..0e8d6faea27 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -35,6 +35,14 @@ describe API::Groups, api: true do
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(group1.name)
end
+
+ it "does not include statistics" do
+ get api("/groups", user1), statistics: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first).not_to include 'statistics'
+ end
end
context "when authenticated as admin" do
@@ -44,6 +52,31 @@ describe API::Groups, api: true do
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
+
+ it "does not include statistics by default" do
+ get api("/groups", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first).not_to include('statistics')
+ end
+
+ it "includes statistics if requested" do
+ attributes = {
+ storage_size: 702,
+ repository_size: 123,
+ lfs_objects_size: 234,
+ build_artifacts_size: 345,
+ }
+
+ project1.statistics.update!(attributes)
+
+ get api("/groups", admin), statistics: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['statistics']).to eq attributes.stringify_keys
+ end
end
context "when using skip_groups in request" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 8304c408064..f5788d15f93 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -49,7 +49,7 @@ describe API::Projects, api: true do
end
end
- context 'when authenticated' do
+ context 'when authenticated as regular user' do
it 'returns an array of projects' do
get api('/projects', user)
expect(response).to have_http_status(200)
@@ -172,6 +172,22 @@ describe API::Projects, api: true do
end
end
end
+
+ it "does not include statistics by default" do
+ get api('/projects/all', admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first).not_to include('statistics')
+ end
+
+ it "includes statistics if requested" do
+ get api('/projects/all', admin), statistics: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first).to include 'statistics'
+ end
end
end
@@ -196,6 +212,32 @@ describe API::Projects, api: true do
expect(json_response.first['name']).to eq(project4.name)
expect(json_response.first['owner']['username']).to eq(user4.username)
end
+
+ it "does not include statistics by default" do
+ get api('/projects/owned', user4)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first).not_to include('statistics')
+ end
+
+ it "includes statistics if requested" do
+ attributes = {
+ commit_count: 23,
+ storage_size: 702,
+ repository_size: 123,
+ lfs_objects_size: 234,
+ build_artifacts_size: 345,
+ }
+
+ project4.statistics.update!(attributes)
+
+ get api('/projects/owned', user4), statistics: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['statistics']).to eq attributes.stringify_keys
+ end
end
end
@@ -630,6 +672,18 @@ describe API::Projects, api: true do
expect(json_response['name']).to eq(project.name)
end
+ it 'exposes namespace fields' do
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['namespace']).to eq({
+ 'id' => user.namespace.id,
+ 'name' => user.namespace.name,
+ 'path' => user.namespace.path,
+ 'kind' => user.namespace.kind,
+ })
+ end
+
describe 'permissions' do
context 'all projects' do
before { project.team << [user, :master] }
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index d71bb08c218..5abda28e26f 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -371,12 +371,26 @@ describe 'Git HTTP requests', lib: true do
shared_examples 'can download code only' do
it 'downloads get status 200' do
- clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+ allow_any_instance_of(Repository).
+ to receive(:exists?).and_return(true)
+
+ clone_get "#{project.path_with_namespace}.git",
+ user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
+ it 'downloads from non-existing repository and gets 403' do
+ allow_any_instance_of(Repository).
+ to receive(:exists?).and_return(false)
+
+ clone_get "#{project.path_with_namespace}.git",
+ user: 'gitlab-ci-token', password: build.token
+
+ expect(response).to have_http_status(403)
+ end
+
it 'uploads get status 403' do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 3303e808a9c..2a0f00ce937 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -583,7 +583,7 @@ describe GitPushService, services: true do
service.push_commits = [commit]
expect(ProjectCacheWorker).to receive(:perform_async).
- with(project.id, %i(readme))
+ with(project.id, %i(readme), %i(commit_count repository_size))
service.update_caches
end
@@ -596,7 +596,7 @@ describe GitPushService, services: true do
it 'does not flush any conditional caches' do
expect(ProjectCacheWorker).to receive(:perform_async).
- with(project.id, []).
+ with(project.id, [], %i(commit_count repository_size)).
and_call_original
service.update_caches
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
index 05cdbe5287a..35804d41b46 100644
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -11,6 +11,7 @@ describe MergeRequests::MergeRequestDiffCacheService do
expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
expect(Rails.cache).to receive(:write).with(cache_key, anything)
allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => true))
+ allow_any_instance_of(Repository).to receive(:diffable?).and_return(true)
subject.execute(merge_request)
end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 194620d0a68..a4713e53f63 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -76,7 +76,7 @@ shared_examples 'issuable record that supports slash commands in its description
expect(page).not_to have_content '/assign @bob'
expect(page).not_to have_content '/label ~bug'
expect(page).not_to have_content '/milestone %"ASAP"'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
issuable.reload
@@ -97,7 +97,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/close")
expect(page).not_to have_content '/close'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(issuable.reload).to be_closed
end
@@ -114,7 +114,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/close")
expect(page).not_to have_content '/close'
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_open
end
@@ -132,7 +132,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/reopen")
expect(page).not_to have_content '/reopen'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(issuable.reload).to be_open
end
@@ -149,7 +149,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/reopen")
expect(page).not_to have_content '/reopen'
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_closed
end
@@ -162,7 +162,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/title Awesome new title")
expect(page).not_to have_content '/title'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(issuable.reload.title).to eq 'Awesome new title'
end
@@ -179,7 +179,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/title Awesome new title")
expect(page).not_to have_content '/title'
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Commands applied'
expect(issuable.reload.title).not_to eq 'Awesome new title'
end
@@ -191,7 +191,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/todo")
expect(page).not_to have_content '/todo'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
todos = TodosFinder.new(master).execute
todo = todos.first
@@ -222,7 +222,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/done")
expect(page).not_to have_content '/done'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(todo.reload).to be_done
end
@@ -235,7 +235,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/subscribe")
expect(page).not_to have_content '/subscribe'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(issuable.subscribed?(master, project)).to be_truthy
end
@@ -252,7 +252,7 @@ shared_examples 'issuable record that supports slash commands in its description
write_note("/unsubscribe")
expect(page).not_to have_content '/unsubscribe'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content 'Commands applied'
expect(issuable.subscribed?(master, project)).to be_falsy
end
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index 855c28b584e..f4f63b57a5f 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
describe ProjectCacheWorker do
- let(:project) { create(:project) }
let(:worker) { described_class.new }
+ let(:project) { create(:project) }
+ let(:statistics) { project.statistics }
describe '#perform' do
before do
@@ -12,7 +13,7 @@ describe ProjectCacheWorker do
context 'with a non-existing project' do
it 'does nothing' do
- expect(worker).not_to receive(:update_repository_size)
+ expect(worker).not_to receive(:update_statistics)
worker.perform(-1)
end
@@ -22,24 +23,19 @@ describe ProjectCacheWorker do
it 'does nothing' do
allow_any_instance_of(Repository).to receive(:exists?).and_return(false)
- expect(worker).not_to receive(:update_repository_size)
+ expect(worker).not_to receive(:update_statistics)
worker.perform(project.id)
end
end
context 'with an existing project' do
- it 'updates the repository size' do
- expect(worker).to receive(:update_repository_size).and_call_original
-
- worker.perform(project.id)
- end
-
- it 'updates the commit count' do
- expect_any_instance_of(Project).to receive(:update_commit_count).
- and_call_original
+ it 'updates the project statistics' do
+ expect(worker).to receive(:update_statistics)
+ .with(kind_of(Project), %i(repository_size))
+ .and_call_original
- worker.perform(project.id)
+ worker.perform(project.id, [], %w(repository_size))
end
it 'refreshes the method caches' do
@@ -47,33 +43,35 @@ describe ProjectCacheWorker do
with(%i(readme)).
and_call_original
- worker.perform(project.id, %i(readme))
+ worker.perform(project.id, %w(readme))
end
end
end
- describe '#update_repository_size' do
+ describe '#update_statistics' do
context 'when a lease could not be obtained' do
it 'does not update the repository size' do
allow(worker).to receive(:try_obtain_lease_for).
- with(project.id, :update_repository_size).
+ with(project.id, :update_statistics).
and_return(false)
- expect(project).not_to receive(:update_repository_size)
+ expect(statistics).not_to receive(:refresh!)
- worker.update_repository_size(project)
+ worker.update_statistics(project)
end
end
context 'when a lease could be obtained' do
- it 'updates the repository size' do
+ it 'updates the project statistics' do
allow(worker).to receive(:try_obtain_lease_for).
- with(project.id, :update_repository_size).
+ with(project.id, :update_statistics).
and_return(true)
- expect(project).to receive(:update_repository_size).and_call_original
+ expect(statistics).to receive(:refresh!)
+ .with(only: %i(repository_size))
+ .and_call_original
- worker.update_repository_size(project)
+ worker.update_statistics(project, %i(repository_size))
end
end
end
diff --git a/vendor/gitignore/Clojure.gitignore b/vendor/gitignore/Clojure.gitignore
index 7657a270c45..a9fe6fba80d 120000..100644
--- a/vendor/gitignore/Clojure.gitignore
+++ b/vendor/gitignore/Clojure.gitignore
@@ -1 +1,13 @@
-Leiningen.gitignore \ No newline at end of file
+pom.xml
+pom.xml.asc
+*.jar
+*.class
+/lib/
+/classes/
+/target/
+/checkouts/
+.lein-deps-sum
+.lein-repl-history
+.lein-plugins/
+.lein-failures
+.nrepl-port