diff options
author | Rémy Coutable <remy@rymai.me> | 2016-10-11 21:54:42 +0300 |
---|---|---|
committer | Rémy Coutable <remy@rymai.me> | 2016-10-11 21:54:42 +0300 |
commit | 6d3c7ebabad6a42e7b502b320ad185c51fb59f67 (patch) | |
tree | 7417afdea5323c3c7fa964fa1140a9bc44fe6e50 | |
parent | d4e84e443cdc311ec93f14b9eda67641cf510aaa (diff) | |
parent | 670b2eb5c05a721f810a5b248612cadde0eaf2ce (diff) |
Merge remote-tracking branch 'origin/master' into 8-13-stable
238 files changed, 3250 insertions, 2294 deletions
diff --git a/.vagrant_enabled b/.vagrant_enabled deleted file mode 100644 index e69de29bb2d..00000000000 --- a/.vagrant_enabled +++ /dev/null diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..37cbfeff01a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,21 +1,29 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - - Improve issue load time performance by avoiding ORDER BY in find_by call - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Updating verbiage on git basics to be more intuitive + - Clarify documentation for Runners API (Gennady Trafimenkov) + - Change user & group landing page routing from /u/:username to /:username + - Prevent running GfmAutocomplete setup for each diff note !6569 - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data - Speed-up group milestones show page + - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment + - Allow browsing branches that end with '.atom' - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) - Update Gitlab Shell to fix some problems with moving projects between storages @@ -27,6 +35,7 @@ v 8.13.0 (unreleased) - Revert "Label list shows all issues (opened or closed) with that label" - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments + - Added copy file path button to merge request diff files - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Add Issue Board API support (andrebsguedes) - Allow the Koding integration to be configured through the API @@ -36,6 +45,7 @@ v 8.13.0 (unreleased) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) + - Remove redundant mixins (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 @@ -78,14 +88,25 @@ v 8.13.0 (unreleased) - Fix broken repository 500 errors in project list - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Retouch environments list and deployments list + - Add multiple command support for all label related slash commands !6780 (barthc) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fix a typo in doc/api/labels.md + - API: all unknown routing will be handled with 404 Not Found + - Make guests unable to view MRs on private projects -v 8.12.5 (unreleased) +v 8.12.5 + - Switch from request to env in ::API::Helpers. !6615 + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 + - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access v 8.12.4 - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) @@ -100,7 +121,6 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only - - Change user & group landing page routing from /u/:username to /:username v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves @@ -120,7 +140,6 @@ v 8.12.2 - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path - Refactor remnants of CoffeeScript destructured opts and super !6261 - - Prevent running GfmAutocomplete setup for each diff note !6569 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST @@ -314,6 +333,10 @@ v 8.12.0 - Fix non-master branch readme display in tree view - Add UX improvements for merge request version diffs +v 8.11.9 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.11.8 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests @@ -324,7 +347,6 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. - - Updating verbiage on git basics to be more intuitive v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 @@ -485,7 +507,6 @@ v 8.11.0 - Add pipeline events hook - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) - - Clarify documentation for Runners API (Gennady Trafimenkov) - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial - Convert image diff background image to CSS (ClemMakesApps) @@ -541,6 +562,10 @@ v 8.11.0 - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.12 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.10.11 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests @@ -324,7 +324,7 @@ gem 'newrelic_rpm', '~> 3.16' gem 'octokit', '~> 4.3.0' -gem 'mail_room', '~> 0.8' +gem 'mail_room', '~> 0.8.1' gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index b98c3acf948..2feec4c4eb5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,7 +399,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.8.0) + mail_room (0.8.1) method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) @@ -889,7 +889,7 @@ DEPENDENCIES license_finder (~> 2.1.0) licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.8) + mail_room (~> 0.8.1) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) diff --git a/README.md b/README.md index 8236f986b56..a6b30aff5a0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GitLab -[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) -[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image_file.js index e893491b19b..e893491b19b 100644 --- a/app/assets/javascripts/commit/image-file.js +++ b/app/assets/javascripts/commit/image_file.js diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6 index cd9886ba58d..cd9886ba58d 100644 --- a/app/assets/javascripts/cycle-analytics.js.es6 +++ b/app/assets/javascripts/cycle_analytics.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 8d99b12102d..adff73af79c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -126,6 +126,9 @@ new TreeView(); } break; + case 'projects:pipelines:show': + new gl.Pipelines(); + break; case 'groups:activity': new Activities(); break; diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6 index 0808f538f01..0808f538f01 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js.es6 +++ b/app/assets/javascripts/issues_bulk_assignment.js.es6 diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/label_manager.js.es6 index bc68e53504f..bc68e53504f 100644 --- a/app/assets/javascripts/LabelManager.js.es6 +++ b/app/assets/javascripts/label_manager.js.es6 diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch_graph.js index 91132af273a..91132af273a 100644 --- a/app/assets/javascripts/network/branch-graph.js +++ b/app/assets/javascripts/network/branch_graph.js diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 8813bb5dfef..6bf63ee6979 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,24 +1,40 @@ -(function() { - function toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const $icon = $(this).find('.fa'); - - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - const expandIcon = 'fa-caret-down'; - const hideIcon = 'fa-caret-up'; - - if(graphCollapsed) { - $btnText.text('Expand'); - $icon.removeClass(hideIcon).addClass(expandIcon); - } else { - $btnText.text('Hide'); - $icon.removeClass(expandIcon).addClass(hideIcon); +((global) => { + + class Pipelines { + constructor() { + $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + this.addMarginToBuildColumns(); + } + + toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); + + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); + + + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } + + addMarginToBuildColumns() { + const $secondChildBuildNode = $('.build:nth-child(2)'); + if ($secondChildBuildNode.length) { + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); + const $previousColumn = $multiBuildColumn.prev('.stage-column'); + $multiBuildColumn.addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); + $previousColumn.each(function() { + $this = $(this); + if ($('.build', $this).length === 1) $this.addClass('no-margin'); + }); + } + $('.pipeline-graph').removeClass('hidden'); } } - $(document).on('click', '.toggle-pipeline-btn', toggleGraph); -})(); + global.Pipelines = Pipelines; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index c79b22d4d21..98e301d3799 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -4,7 +4,7 @@ width: 40px; height: 40px; padding: 0; - @include border-radius($avatar_radius); + border-radius: $avatar_radius; border: 1px solid rgba(0, 0, 0, .1); &.avatar-inline { @@ -17,7 +17,7 @@ } &.avatar-tile { - @include border-radius(0); + border-radius: 0; border: none; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index d315db4cb32..8002e56724b 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -133,7 +133,7 @@ } .identicon { - @include border-radius(50%); + border-radius: 50%; } } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index d11b2fe7ec2..a7c8d782e9b 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,5 +1,5 @@ @mixin btn-default { - @include border-radius(3px); + border-radius: 3px; font-size: $gl-font-size; font-weight: 500; padding: $gl-vert-padding $gl-btn-padding; @@ -8,7 +8,7 @@ &:active { outline: none; background-color: $btn-active-gray; - @include box-shadow($gl-btn-active-background); + box-shadow: $gl-btn-active-background; } } @@ -43,7 +43,7 @@ &:active, &.active { - @include box-shadow ($gl-btn-active-background); + box-shadow: $gl-btn-active-background; background-color: $dark; border-color: $border-dark; @@ -279,7 +279,7 @@ } .active { - @include box-shadow($gl-btn-active-background); + box-shadow: $gl-btn-active-background; border: 1px solid #c6cacf !important; background-color: #e4e7ed !important; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index a67d31de2f7..05e8ee0190d 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -73,7 +73,7 @@ label { } .form-control { - @include box-shadow(none); + box-shadow: none; border-radius: 3px; padding: $gl-vert-padding $gl-input-padding; } diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 8bfc0d583c5..ba3930e03bd 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -16,7 +16,7 @@ margin-top: 5px; } - @include border-radius(3px); + border-radius: 3px; display: block; float: left; margin-right: 10px; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index edea4ad00eb..6d28d98b283 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -86,7 +86,7 @@ } .markdown-area { - @include border-radius(0); + border-radius: 0; background: #fff; border: 1px solid #ddd; min-height: 140px; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1ec08cdef23..7c207969b0a 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -1,15 +1,4 @@ /** - * Generic mixins - */ -@mixin box-shadow($shadow) { - box-shadow: $shadow; -} - -@mixin border-radius($radius) { - border-radius: $radius; -} - -/** * Prefilled mixins * Mixins with fixed values */ diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 76b93b23b95..9fe390eb09d 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -133,5 +133,5 @@ font-size: 20px; color: #777; z-index: 100; - @include box-shadow(0 1px 2px #ddd); + box-shadow: 0 1px 2px #ddd; } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index bcd60391543..79cd26714a3 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -46,8 +46,8 @@ } .select2-drop { - @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0); - @include border-radius ($border-radius-default); + box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0; + border-radius: $border-radius-default; border: none; min-width: 175px; } @@ -72,7 +72,7 @@ .select2-container-active { .select2-choice, .select2-choices { - @include box-shadow(none); + box-shadow: none; } } @@ -82,13 +82,13 @@ outline: 0; background-image: none; background-color: $white-dark; - @include box-shadow($gl-btn-active-gradient); + box-shadow: $gl-btn-active-gradient; } } .select2-container-multi { .select2-choices { - @include border-radius($border-radius-default); + border-radius: $border-radius-default; border-color: $input-border; background: none; @@ -123,7 +123,7 @@ &.select2-container-active .select2-choices, &.select2-dropdown-open .select2-choices { border-color: $border-white-normal; - @include box-shadow($gl-btn-active-gradient); + box-shadow: $gl-btn-active-gradient; } } @@ -157,7 +157,7 @@ background-repeat: no-repeat; background-position: right 0 bottom 6px; border: 1px solid $input-border; - @include border-radius($border-radius-default); + border-radius: $border-radius-default; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; &:focus { diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 557ef7291cf..ec52f326eb9 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -4,7 +4,7 @@ &.page-sidebar-pinned { .sidebar-wrapper { - @include box-shadow(none); + box-shadow: none; } } @@ -17,7 +17,7 @@ width: 0; overflow: hidden; transition: width $sidebar-transition-duration; - @include box-shadow(2px 0 16px 0 $black-transparent); + box-shadow: 2px 0 16px 0 $black-transparent; } } @@ -100,7 +100,7 @@ .count { float: right; padding: 0 8px; - @include border-radius(6px); + border-radius: 6px; } } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 9f2d53d5206..d099a884f54 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -116,7 +116,7 @@ font-size: 13px; line-height: 1.6em; overflow-x: auto; - @include border-radius(2px); + border-radius: 2px; } p > code { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 14ec310de2d..4c34ed3ebf7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -17,8 +17,10 @@ $white-normal: #ededed; $white-dark: #ececec; $gray-light: #fafafa; +$gray-lighter: #f9f9f9; $gray-normal: #f5f5f5; $gray-dark: #ededed; +$gray-darker: #eee; $gray-darkest: #c9c9c9; $green-light: #38ae67; @@ -33,6 +35,8 @@ $blue-medium-light: #3498cb; $blue-medium: #2f8ebf; $blue-medium-dark: #2d86b4; +$blue-light-transparent: rgba(44, 159, 216, 0.05); + $orange-light: #fc8a51; $orange-normal: #e75e40; $orange-dark: #ce5237; @@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f; $gl-font-size: 15px; $gl-title-color: #333; $gl-text-color: #5c5c5c; +$gl-text-color-light: #8c8c8c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 778471a34d7..d732008de3d 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -50,7 +50,7 @@ .bordered-box { border: 1px solid $border-color; - @include border-radius($border-radius-default); + border-radius: $border-radius-default; } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index e1304335271..fcc5f32c738 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -1,7 +1,7 @@ .file-editor { #editor { border: none; - @include border-radius(0); + border-radius: 0; height: 500px; margin: 0; padding: 0; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 1d00da1266c..789d6237df8 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -91,7 +91,7 @@ float: right; border: 1px solid #eee; padding: 5px; - @include border-radius(5px); + border-radius: 5px; background: $gray-light; margin-left: 10px; top: -6px; diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 822830706a5..701c29a3986 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -1,7 +1,7 @@ .suggest-colors { margin-top: 5px; a { - @include border-radius(4px); + border-radius: 4px; width: 30px; height: 30px; display: inline-block; @@ -17,7 +17,7 @@ overflow: hidden; a { - @include border-radius(0); + border-radius: 0; width: (100% / 7); margin-right: 0; margin-bottom: -5px; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 403171d4532..a5ca509163d 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -73,12 +73,12 @@ height: auto; &.top { - @include border-radius(5px 5px 0 0); + border-radius: 5px 5px 0 0; margin-bottom: 0; } &.bottom { - @include border-radius(0 0 5px 5px); + border-radius: 0 0 5px 5px; border-top: 0; margin-bottom: 20px; } @@ -86,7 +86,7 @@ &.middle { border-top: 0; margin-bottom: 0; - @include border-radius(0); + border-radius: 0; } &:active, &:focus { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..1006b3e62e8 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -6,7 +6,7 @@ background: $background-color; color: $gl-gray; border: 1px solid $border-color; - @include border-radius(2px); + border-radius: 2px; form { margin-bottom: 0; @@ -204,6 +204,18 @@ word-break: break-all; } +.commits-empty { + text-align: center; + + h4 { + padding-top: 20px; + padding-bottom: 10px; + } + svg { + width: 230px; + } +} + .mr-list { .merge-request { padding: 10px 15px; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 54124a3d658..d399f84a2ff 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -334,7 +334,7 @@ ul.notes { .add-diff-note { margin-top: -4px; - @include border-radius(40px); + border-radius: 40px; background: #fff; padding: 4px; font-size: 16px; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..05f59279637 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,16 +303,41 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 65px; + + &:not(:last-child) { + margin-right: 44px; + } + + &.left-margin { + &:not(:first-child) { + margin-left: 44px; + + .left-connector { + &::before { + content: ''; + position: absolute; + top: 48%; + left: -48px; + border-top: 2px solid $border-color; + width: 48px; + height: 1px; + } + } + } + } + + &.no-margin { + margin: 0; + } li { list-style: none; } .stage-name { - margin-bottom: 15px; + margin: 0 0 15px 10px; font-weight: bold; - width: 150px; + width: 176px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -321,17 +346,23 @@ .build { border: 1px solid $border-color; position: relative; - padding: 6px 10px; + padding: 7px 10px 8px; border-radius: 30px; - width: 150px; + width: 186px; margin-bottom: 10px; + &:hover { + background-color: $gray-lighter; + .dropdown-menu-toggle { + background-color: transparent; + } + } + &.playable { - background-color: $gray-light; svg { - height: 12px; - width: 12px; + height: 13px; + width: 20px; position: relative; top: 1px; @@ -342,10 +373,20 @@ } .build-content { - width: 130px; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + width: 164px; + + .ci-status-icon { + svg { + height: 20px; + width: 20px; + } + } .ci-status-text { - width: 110px; + width: 135px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -356,44 +397,53 @@ } a { - color: $layout-link-gray; + color: $gl-text-color-light; text-decoration: none; - - &:hover { - .ci-status-text { - text-decoration: underline; - } - } } .dropdown-menu-toggle { border: none; width: auto; padding: 0; - color: $layout-link-gray; + color: $gl-text-color-light; + flex-grow: 1; .ci-status-text { - width: 80px; + max-width: 112px; + width: auto; } } .grouped-pipeline-dropdown { padding: 8px 0; - width: 200px; + width: 186px; left: auto; - right: -214px; + right: -197px; top: -9px; max-height: 245px; overflow-y: scroll; - a:hover { - .ci-status-text { - text-decoration: none; + a { + color: $gl-text-color; + padding: 7px 8px 8px; + + &:hover { + background-color: $blue-light-transparent; + border-radius: 3px; + + .ci-status-text { + text-decoration: none; + } } } + svg { + width: 14px; + height: 14px; + } + .ci-status-text { - width: 145px; + width: 112px; } .arrow { @@ -426,9 +476,10 @@ } .badge { - background-color: $gray-dark; - color: $layout-link-gray; + background-color: $gray-darker; + color: $gl-text-color-light; font-weight: normal; + margin-left: $btn-xs-side-margin; } } @@ -442,10 +493,10 @@ &::after { content: ''; position: absolute; - top: 50%; - right: -69px; + top: 48%; + right: -48px; border-top: 2px solid $border-color; - width: 69px; + width: 48px; height: 1px; } } @@ -454,25 +505,25 @@ &:not(:first-child) { &::after, &::before { content: ''; - top: -47px; + top: -49px; position: absolute; border-bottom: 2px solid $border-color; - width: 20px; - height: 65px; + width: 25px; + height: 69px; } // Right connecting curves &::after { - right: -20px; + right: -25px; border-right: 2px solid $border-color; - border-radius: 0 0 15px; + border-radius: 0 0 20px; } // Left connecting curves &::before { - left: -20px; + left: -25px; border-left: 2px solid $border-color; - border-radius: 0 0 0 15px; + border-radius: 0 0 0 20px; } } @@ -480,7 +531,7 @@ &:nth-child(2) { &::after, &::before { height: 29px; - top: -10px; + top: -9px; } .curve { display: block; @@ -538,20 +589,20 @@ width: 21px; height: 25px; position: absolute; - top: -29px; + top: -32px; border-top: 2px solid $border-color; } &::after { - left: -39px; + left: -44px; border-right: 2px solid $border-color; - border-radius: 0 15px; + border-radius: 0 20px; } &::before { - right: -39px; + right: -44px; border-left: 2px solid $border-color; - border-radius: 15px 0 0; + border-radius: 20px 0 0; } } } diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index e5859fe7384..f8da0983b77 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -4,7 +4,7 @@ text-align: center; .preview { - @include border-radius(4px); + border-radius: 4px; height: 80px; margin-bottom: 10px; @@ -47,7 +47,7 @@ width: 160px; img { - @include border-radius(4px); + border-radius: 4px; max-width: 100%; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 87548dcb590..530fb0c0d05 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -354,7 +354,7 @@ a.deploy-project-label { justify-content: flex-start; .fork-thumbnail { - @include border-radius($border-radius-base); + border-radius: $border-radius-base; background-color: $white-light; border: 1px solid $border-white-light; height: 202px; @@ -371,7 +371,7 @@ a.deploy-project-label { background-color: $gray-light; border: 1px solid $gray-dark; margin: 0 auto; - @include border-radius(50%); + border-radius: 50%; i { font-size: 100px; color: $gray-dark; @@ -390,7 +390,7 @@ a.deploy-project-label { } img { - @include border-radius(50%); + border-radius: 50%; max-width: 100px; } } @@ -496,7 +496,7 @@ pre.light-well { } .light-well { - @include border-radius (2px); + border-radius: 2px; color: #5b6169; font-size: 13px; diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 0ee7ceecae5..c05f3d5ff32 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -4,7 +4,7 @@ margin-right: 10px; border: 1px solid #eee; white-space: nowrap; - @include border-radius(4px); + border-radius: 4px; &:hover { text-decoration: none; diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 38e5943eb76..a62c6211372 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = TrendingProjectsFinder.new.execute - @projects = filter_projects(@projects) + @projects = filter_projects(Project.trending) @projects = @projects.page(params[:page]) respond_to do |format| diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb deleted file mode 100644 index 83eec1bf4a2..00000000000 --- a/app/controllers/namespaces_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -class NamespacesController < ApplicationController - skip_before_action :authenticate_user! - - def show - namespace = Namespace.find_by(path: params[:id]) - - if namespace - if namespace.is_a?(Group) - group = namespace - else - user = namespace.owner - end - end - - if user - redirect_to user_path(user) - elsif group && can?(current_user, :read_group, group) - redirect_to group_path(group) - elsif current_user.nil? - authenticate_user! - else - render_404 - end - end -end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ffd9833e3b1..869d96b86f4 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, - :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts + :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] @@ -31,6 +31,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController # Allow modify merge_request before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] + before_action :authenticate_user!, only: [:assign_related_issues] + before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] def index @@ -354,6 +356,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController render layout: false end + def assign_related_issues + result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute + + respond_to do |format| + format.html do + case result[:count] + when 0 + flash[:error] = "Failed to assign you issues related to the merge request" + when 1 + flash[:notice] = "1 issue has been assigned to you" + else + flash[:notice] = "#{result[:count]} issues have been assigned to you" + end + + redirect_to(merge_request_path(@merge_request)) + end + end + end + def ci_status pipeline = @merge_request.pipeline if pipeline diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 6ea8ee62bc5..8fea20cefef 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) + return render_404 unless @tag + @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) end diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb deleted file mode 100644 index c1e434d9926..00000000000 --- a/app/finders/trending_projects_finder.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Finder for retrieving public trending projects in a given time range. -class TrendingProjectsFinder - # current_user - The currently logged in User, if any. - # last_months - The number of months to limit the trending data to. - def execute(months_limit = 1) - Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do - Project.public_only.trending(months_limit.months.ago) - end - end - - private - - def cache_key_for(months) - "trending_projects/#{months}" - end -end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index b478580978b..a695aceea76 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -15,10 +15,11 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) + css_class = data[:class] || 'btn-clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), - class: "btn btn-clipboard", + class: "btn #{css_class}", data: data, type: :button, title: "Copy to Clipboard" diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 8abe7865fed..b0a76765d97 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -72,6 +72,19 @@ module MergeRequestsHelper ) end + def mr_assign_issues_link + issues = MergeRequests::AssignIssuesService.new(@project, + current_user, + merge_request: @merge_request, + closes_issues: mr_closes_issues + ).assignable_issues + path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + if issues.present? + pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue" + link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post + end + end + def source_branch_with_namespace(merge_request) branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch)) diff --git a/app/models/event.rb b/app/models/event.rb index 314d5ba438f..0764cb8cabd 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -68,8 +68,10 @@ class Event < ActiveRecord::Base true elsif issue? || issue_note? Ability.allowed?(user, :read_issue, note? ? note_target : target) + elsif merge_request? || merge_request_note? + Ability.allowed?(user, :read_merge_request, note? ? note_target : target) else - ((merge_request? || note?) && target.present?) || milestone? + milestone? end end @@ -280,6 +282,10 @@ class Event < ActiveRecord::Base note? && target && target.for_issue? end + def merge_request_note? + note? && target && target.for_merge_request? + end + def project_snippet_note? target.for_snippet? end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 36b8b70870b..3f7e96186a1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 100 + # Valid types of serialized diffs allowed by Gitlab::Git::Diff + VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta] + belongs_to :merge_request state_machine :state, initial: :empty do @@ -170,6 +173,15 @@ class MergeRequestDiff < ActiveRecord::Base private + # Old GitLab implementations may have generated diffs as ["--broken-diff"]. + # Avoid an error 500 by ignoring bad elements. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776 + def valid_raw_diff?(raw) + return false unless raw.respond_to?(:each) + + raw.any? { |element| VALID_CLASSES.include?(element.class) } + end + def dump_commits(commits) commits.map(&:to_hash) end @@ -200,7 +212,7 @@ class MergeRequestDiff < ActiveRecord::Base end def load_diffs(raw, options) - if raw.respond_to?(:each) + if valid_raw_diff?(raw) if paths = options[:paths] raw = raw.select do |diff| paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) diff --git a/app/models/project.rb b/app/models/project.rb index 88e4bd14860..74d54e69648 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -375,19 +375,9 @@ class Project < ActiveRecord::Base %r{(?<project>#{name_pattern}/#{name_pattern})} end - def trending(since = 1.month.ago) - # By counting in the JOIN we don't expose the GROUP BY to the outer query. - # This means that calls such as "any?" and "count" just return a number of - # the total count, instead of the counts grouped per project as a Hash. - join_body = "INNER JOIN ( - SELECT project_id, COUNT(*) AS amount - FROM notes - WHERE created_at >= #{sanitize(since)} - AND system IS FALSE - GROUP BY project_id - ) join_note_counts ON projects.id = join_note_counts.project_id" - - joins(join_body).reorder('join_note_counts.amount DESC') + def trending + joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id'). + reorder('trending_projects.id ASC') end def cached_count diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index 7613cbdea93..db46def11eb 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base belongs_to :group validates :project_id, presence: true - validates :group_id, presence: true + validates :group, presence: true validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" } validates :group_access, presence: true validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb new file mode 100644 index 00000000000..27e3732da17 --- /dev/null +++ b/app/models/trending_project.rb @@ -0,0 +1,35 @@ +class TrendingProject < ActiveRecord::Base + belongs_to :project + + # The number of months to include in the trending calculation. + MONTHS_TO_INCLUDE = 1 + + # The maximum number of projects to include in the trending set. + PROJECTS_LIMIT = 100 + + # Populates the trending projects table with the current list of trending + # projects. + def self.refresh! + # The calculation **must** run in a transaction. If the removal of data and + # insertion of new data were to run separately a user might end up with an + # empty list of trending projects for a short period of time. + transaction do + delete_all + + timestamp = connection.quote(MONTHS_TO_INCLUDE.months.ago) + + connection.execute <<-EOF.strip_heredoc + INSERT INTO #{table_name} (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{Gitlab::VisibilityLevel::PUBLIC} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT #{PROJECTS_LIMIT}; + EOF + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 892ac28d5b3..f367f4616fb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -589,6 +589,11 @@ class User < ActiveRecord::Base end def set_projects_limit + # `User.select(:id)` raises + # `ActiveModel::MissingAttributeError: missing attribute: projects_limit` + # without this safeguard! + return unless self.has_attribute?(:projects_limit) + connection_default_value_defined = new_record? && !projects_limit_changed? return unless self.projects_limit.nil? || connection_default_value_defined diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a806cf83782..be4721d7a51 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy can! :read_milestone can! :read_project_snippet can! :read_project_member - can! :read_merge_request can! :read_note can! :create_project can! :create_issue @@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy can! :read_pipeline can! :read_environment can! :read_deployment + can! :read_merge_request end # Permissions given when an user is team member of a project @@ -117,6 +117,7 @@ class ProjectPolicy < BasePolicy can! :read_container_image can! :build_download_code can! :build_read_container_image + can! :read_merge_request end def owner_access! diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb new file mode 100644 index 00000000000..f636e5fec4f --- /dev/null +++ b/app/services/merge_requests/assign_issues_service.rb @@ -0,0 +1,35 @@ +module MergeRequests + class AssignIssuesService < BaseService + def assignable_issues + @assignable_issues ||= begin + if current_user == merge_request.author + closes_issues.select do |issue| + !issue.assignee_id? && can?(current_user, :admin_issue, issue) + end + else + [] + end + end + end + + def execute + assignable_issues.each do |issue| + Issues::UpdateService.new(issue.project, current_user, assignee_id: current_user.id).execute(issue) + end + + { + count: assignable_issues.count + } + end + + private + + def merge_request + params[:merge_request] + end + + def closes_issues + @closes_issues ||= params[:closes_issues] || merge_request.closes_issues(current_user) + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index e57791f6818..404f75616b5 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -4,7 +4,7 @@ module MergeRequests merge_request = MergeRequest.new(params) # Set MR attributes - merge_request.can_be_created = false + merge_request.can_be_created = true merge_request.compare_commits = [] merge_request.source_project = project unless merge_request.source_project @@ -22,6 +22,12 @@ module MergeRequests return build_failed(merge_request, message) end + if merge_request.source_project == merge_request.target_project && + merge_request.target_branch == merge_request.source_branch + + return build_failed(merge_request, 'You must select different branches') + end + compare = CompareService.new.execute( merge_request.source_project, merge_request.source_branch, @@ -29,17 +35,8 @@ module MergeRequests merge_request.target_branch, ) - commits = compare.commits - - # At this point we decide if merge request can be created - # If we have at least one commit to merge -> creation allowed - if commits.present? - merge_request.compare_commits = commits - merge_request.can_be_created = true - merge_request.compare = compare - else - merge_request.can_be_created = false - end + merge_request.compare_commits = compare.commits + merge_request.compare = compare set_title_and_description(merge_request) end @@ -89,6 +86,8 @@ module MergeRequests end end + merge_request.title = merge_request.wip_title if commits.empty? + merge_request end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index de8049b8e2e..72712afc07e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -475,10 +475,12 @@ class NotificationService end def reject_users_without_access(recipients, target) - return recipients unless target.is_a?(Issue) + return recipients unless target.is_a?(Issuable) + + ability = :"read_#{target.to_ability_name}" recipients.select do |user| - user.can?(:read_issue, target) + user.can?(ability, target) end end diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 1725a30fae5..e4ae3dec8aa 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -122,7 +122,12 @@ module SlashCommands command :label do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:add_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:add_label_ids] ||= [] + @updates[:add_label_ids] += label_ids + + @updates[:add_label_ids].uniq! + end end desc 'Remove all or specific label(s)' @@ -136,7 +141,12 @@ module SlashCommands if labels_param.present? label_ids = find_label_ids(labels_param) - @updates[:remove_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:remove_label_ids] ||= [] + @updates[:remove_label_ids] += label_ids + + @updates[:remove_label_ids].uniq! + end else @updates[:label_ids] = [] end @@ -152,7 +162,12 @@ module SlashCommands command :relabel do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:label_ids] ||= [] + @updates[:label_ids] += label_ids + + @updates[:label_ids].uniq! + end end desc 'Add a todo' diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 776530ac0a5..f8e6b2ef094 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -273,12 +273,12 @@ class TodoService end def reject_users_without_access(users, project, target) - if target.is_a?(Note) && target.for_issue? + if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?) target = target.noteable end - if target.is_a?(Issue) - select_users(users, :read_issue, target) + if target.is_a?(Issuable) + select_users(users, :"read_#{target.to_ability_name}", target) else select_users(users, :read_project, project) end diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 4dc3b2ab9a0..2821ecf0a88 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -24,6 +24,7 @@ class NamespaceValidator < ActiveModel::EachValidator projects public repository + robots.txt s search services diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 0d79ca7dc52..c4c68cd7891 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -221,7 +221,11 @@ %fieldset %legend Metrics %p - These settings require a restart to take effect. + Setup InfluxDB to measure a wide variety of statistics like the time spent + in running SQL queries. These settings require a + = link_to 'restart', help_page_path('administration/restart_gitlab') + to take effect. + = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') .form-group .col-sm-offset-2.col-sm-10 .checkbox diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 547bc0c9c19..017d3ff6af2 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -5,8 +5,10 @@ .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) .ci-status-text= subject.name - else - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index da5b9832ba5..288c06d9b67 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,45 +1,46 @@ -.row-content-block.build-content.middle-block.pipeline-actions - .pull-right - .btn.btn-grouped.btn-white.toggle-pipeline-btn - %span.toggle-btn-text Hide - %span pipeline graph - = icon('caret-up') - - if can?(current_user, :update_pipeline, pipeline.project) - - if pipeline.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post +.pipeline-graph-container + .row-content-block.build-content.middle-block.pipeline-actions + .pull-right + .btn.btn-grouped.btn-white.toggle-pipeline-btn + %span.toggle-btn-text Hide + %span pipeline graph + %span.caret + - if can?(current_user, :update_pipeline, pipeline.project) + - if pipeline.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post - - if pipeline.builds.running_or_pending.any? - = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + - if pipeline.builds.running_or_pending.any? + = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - .oneline.clearfix - - if defined?(pipeline_details) && pipeline_details - Pipeline - = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" - with - = pluralize pipeline.statuses.count(:id), "build" - - if pipeline.ref - for - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" - - if pipeline.duration - in - = time_interval_in_words pipeline.duration + .oneline.clearfix + - if defined?(pipeline_details) && pipeline_details + Pipeline + = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" + with + = pluralize pipeline.statuses.count(:id), "build" + - if pipeline.ref + for + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" + - if pipeline.duration + in + = time_interval_in_words pipeline.duration -.row-content-block.build-content.middle-block.pipeline-graph - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses + .row-content-block.build-content.middle-block.pipeline-graph.hidden + .pipeline-visualization + %ul.stage-column-list + - stages = pipeline.stages_with_latest_statuses + - stages.each do |stage, statuses| + %li.stage-column + .stage-name + %a{name: stage} + - if stage + = stage.titleize + .builds-container + %ul + = render "projects/commit/pipeline_stage", statuses: statuses - if pipeline.yaml_errors.present? diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 23c5c51fbc2..289aa5178b1 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -10,5 +10,5 @@ - else %li.build .curve - .build-content + .dropdown.inline.build-content = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 4e7a6f1af08..6ada719e006 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,11 +1,12 @@ - group_status = CommitStatus.where(id: subject).status -= render_status_with_link('build', group_status) -.dropdown.inline - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } - %span.ci-status-text - = name - %span.badge= subject.size - %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow - - subject.each do |status| +%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } + %span.ci-status-icon + = render_status_with_link('build', group_status) + %span.ci-status-text + = name + %span.badge= subject.size +%ul.dropdown-menu.grouped-pipeline-dropdown + %li.arrow + - subject.each do |status| + %li = render "projects/#{status.to_partial_path}_pipeline", subject: status diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index e9ff8e90dd5..45be6581cfc 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -4,7 +4,7 @@ %div{ class: container_class } .sub-header-block - Compare branches, tags or commit ranges. + Compare Git revisions. %br Fill input field with commit id like %code.label-branch 4eedf23 diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index d07de45fdde..257e0a855bd 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -8,7 +8,7 @@ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ - + = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-file-option') - if editable_diff?(diff_file) - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {} = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 331dc1fcc29..80fe6be49b0 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -62,5 +62,3 @@ %td.coverage - if generic_commit_status.try(:coverage) #{generic_commit_status.coverage}% - - %td diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 409f4701e4b..0a66d60accc 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,7 +1,9 @@ - if subject.target_url = link_to subject.target_url do - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name - else - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index e8c673d48bd..09347ad5fff 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -23,8 +23,8 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - = icon('caret-down') Options + = icon('caret-down') .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul - if can?(current_user, :create_issue, @project) diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index de39964fca8..466ec1475d8 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -65,19 +65,6 @@ - if @merge_request.errors.any? = form_errors(@merge_request) - - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? - .light-well.append-bottom-default - .center - %h4 - There isn't anything to merge. - %p.slead - - if @merge_request.source_branch == @merge_request.target_branch - You'll need to use different branch names to get a valid comparison. - - else - %span.label-branch #{@merge_request.source_branch} - and - %span.label-branch #{@merge_request.target_branch} - are the same. = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" :javascript diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 88d8013a0d1..da6927879a4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,29 +18,35 @@ = f.hidden_field :target_branch .mr-compare.merge-request - %ul.merge-request-tabs.nav-links.no-top.no-bottom - %li.commits-tab.active - = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do - Commits - %span.badge= @commits.size - - if @pipeline - %li.builds-tab - = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do - Builds - %span.badge= @statuses.size - %li.diffs-tab - = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do - Changes - %span.badge= @merge_request.diff_size + - if @commits.empty? + .commits-empty + %h4 + There are no commits yet. + = custom_icon ('illustration_no_commits') + - else + %ul.merge-request-tabs.nav-links.no-top.no-bottom + %li.commits-tab.active + = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do + Commits + %span.badge= @commits.size + - if @pipeline + %li.builds-tab + = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do + Builds + %span.badge= @statuses.size + %li.diffs-tab + = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do + Changes + %span.badge= @merge_request.diff_size - .tab-content - #commits.commits.tab-pane.active - = render "projects/merge_requests/show/commits" - #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX - - if @pipeline - #builds.builds.tab-pane - = render "projects/merge_requests/show/builds" + .tab-content + #commits.commits.tab-pane.active + = render "projects/merge_requests/show/commits" + #diffs.diffs.tab-pane + - # This tab is always loaded via AJAX + - if @pipeline + #builds.builds.tab-pane + = render "projects/merge_requests/show/builds" .mr-loading-status = spinner diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 9f2a0f5d99a..e7c5bca6a37 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -19,8 +19,8 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - = icon('caret-down') Options + = icon('caret-down') .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul %li{ class: merge_request_button_visibility(@merge_request, true) } diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 6f5ee5f16c5..842b6df310d 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -35,3 +35,4 @@ Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)} = succeed '.' do != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author + = mr_assign_issues_link diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index b2ece44d966..29df1bab04e 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -8,7 +8,7 @@ .project-network .controls = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| - = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha' + = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha' = button_tag class: 'btn btn-success' do = icon('search') .inline.prepend-left-20 diff --git a/app/views/shared/icons/_illustration_no_commits.svg b/app/views/shared/icons/_illustration_no_commits.svg new file mode 100644 index 00000000000..4f9d9add60d --- /dev/null +++ b/app/views/shared/icons/_illustration_no_commits.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="m4.01 2h1.102c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-1.102c-2.218 0-4.01 1.788-4.01 4 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-1.108.892-2 2.01-2m12.702 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m8.088 0c.822 0 1.554.503 1.86 1.254.208.512.791.758 1.303.55.512-.208.758-.791.55-1.303-.609-1.497-2.069-2.5-3.712-2.5h-2.188c-.552 0-1 .448-1 1 0 .552.448 1 1 1h2.188m2.01 12.518c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72c-.552 0-1 .448-1 1 0 .552.448 1 1 1h.72c2.218 0 4.01-1.788 4.01-4v-.382c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.382m-14.325 2c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-8.47 0c-.755 0-1.438-.424-1.782-1.085-.255-.49-.859-.681-1.349-.426-.49.255-.681.859-.426 1.349.684 1.316 2.046 2.162 3.556 2.162h2.57c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-2.57m-2.01-12.136c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-6.664c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.764c0 .552.448 1 1 1 .552 0 1-.448 1-1v-.764" id="0"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="1"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="2"/><path d="m131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9c0-.552-.447-.999-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01c1.655 0 2.996 1.344 2.996 2.999v9c0 1.657-1.35 2.999-2.996 2.999h-22.01c-1.655 0-2.996-1.344-2.996-2.999v-9c0-1.657 1.35-2.999 2.996-2.999" id="3"/><g transform="translate(0 59)"><use xlink:href="#0"/><circle cx="21" cy="24" r="10"/><use xlink:href="#1"/><use xlink:href="#2"/><use xlink:href="#3"/></g></g></svg>
\ No newline at end of file diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb new file mode 100644 index 00000000000..df4c4a6628b --- /dev/null +++ b/app/workers/trending_projects_worker.rb @@ -0,0 +1,11 @@ +class TrendingProjectsWorker + include Sidekiq::Worker + + sidekiq_options queue: :trending_projects + + def perform + Rails.logger.info('Refreshing trending projects') + + TrendingProject.refresh! + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index a79356923b2..114ceac8e1f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -112,7 +112,7 @@ production: &base ## Reply by email # Allow users to comment on issues and merge requests by replying to notification emails. - # For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html + # For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html incoming_email: enabled: false diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c5ed2162c92..efe0ac9c965 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -304,6 +304,10 @@ Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *' Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker' +Settings.cron_jobs['trending_projects_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['trending_projects_worker']['cron'] = '0 1 * * *' +Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsWorker' + # # GitLab Shell # diff --git a/config/routes.rb b/config/routes.rb index bf7c5b76128..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,7 +87,5 @@ Rails.application.routes.draw do # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } - root to: "root#index" end diff --git a/config/routes/project.rb b/config/routes/project.rb index e8807ef06a7..dd0e84b1475 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -159,7 +159,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: get( '/commits/*id', to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + constraints: { id: /.+/, format: false }, as: :commits ) end @@ -277,6 +277,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :remove_wip get :diff_for_path post :resolve_conflicts + post :assign_related_issues end collection do diff --git a/db/migrate/20161007133303_precalculate_trending_projects.rb b/db/migrate/20161007133303_precalculate_trending_projects.rb new file mode 100644 index 00000000000..b324cd94268 --- /dev/null +++ b/db/migrate/20161007133303_precalculate_trending_projects.rb @@ -0,0 +1,38 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class PrecalculateTrendingProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + create_table :trending_projects do |t| + t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false + end + + timestamp = connection.quote(1.month.ago) + + # We're hardcoding the visibility level (public) here so that if it ever + # changes this query doesn't suddenly use the new value (which may break + # later migrations). + visibility = 20 + + execute <<-EOF.strip_heredoc + INSERT INTO trending_projects (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{visibility} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT 100; + EOF + end + + def down + drop_table :trending_projects + end +end diff --git a/db/schema.rb b/db/schema.rb index 56da70b3c02..c5ddf9eee32 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: 20160926145521) do +ActiveRecord::Schema.define(version: 20161007133303) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1070,6 +1070,12 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree + create_table "trending_projects", force: :cascade do |t| + t.integer "project_id", null: false + end + + add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree + create_table "u2f_registrations", force: :cascade do |t| t.text "certificate" t.string "key_handle" @@ -1212,5 +1218,6 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_foreign_key "personal_access_tokens", "users" add_foreign_key "protected_branch_merge_access_levels", "protected_branches" add_foreign_key "protected_branch_push_access_levels", "protected_branches" + add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" end diff --git a/doc/README.md b/doc/README.md index 9017b143260..42ee44f83dc 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,7 +7,7 @@ - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. -- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. +- [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](user/markdown.md) GitLab's advanced formatting system. @@ -35,7 +35,7 @@ - [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars. - [Log system](administration/logs.md) Log system. - [Environment Variables](administration/environment_variables.md) to configure GitLab. -- [Operations](operations/README.md) Keeping GitLab up and running. +- [Operations](administration/operations.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Repository checks](administration/repository_checks.md) Periodic Git repository checks. - [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories. @@ -43,12 +43,12 @@ - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. -- [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. +- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. -- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. -- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint. +- [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. +- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md new file mode 100644 index 00000000000..771584268d9 --- /dev/null +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -0,0 +1,40 @@ +# GitLab Configuration + +GitLab Performance Monitoring is disabled by default. To enable it and change any of its +settings, navigate to the Admin area in **Settings > Metrics** +(`/admin/application_settings`). + +The minimum required settings you need to set are the InfluxDB host and port. +Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the +changes. + +--- + +![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) + +--- + +Finally, a restart of all GitLab processes is required for the changes to take +effect: + +```bash +# For Omnibus installations +sudo gitlab-ctl restart + +# For installations from source +sudo service gitlab restart +``` + +## Pending Migrations + +When any migrations are pending, the metrics are disabled until the migrations +have been performed. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md new file mode 100644 index 00000000000..7947b0fedc4 --- /dev/null +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -0,0 +1,111 @@ +# Grafana Configuration + +[Grafana](http://grafana.org/) is a tool that allows you to visualize time +series metrics through graphs and dashboards. It supports several backend +data stores, including InfluxDB. GitLab writes performance data to InfluxDB +and Grafana will allow you to query InfluxDB to display useful graphs. + +For the easiest installation and configuration, install Grafana on the same +server as InfluxDB. For larger installations, you may want to split out these +services. + +## Installation + +Grafana supplies package repositories (Yum/Apt) for easy installation. +See [Grafana installation documentation](http://docs.grafana.org/installation/) +for detailed steps. + +> **Note**: Before starting Grafana for the first time, set the admin user +and password in `/etc/grafana/grafana.ini`. Otherwise, the default password +will be `admin`. + +## Configuration + +Login as the admin user. Expand the menu by clicking the Grafana logo in the +top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' +in the top bar. + +![Grafana empty data source page](img/grafana_data_source_empty.png) + +Fill in the configuration details for the InfluxDB data source. Save and +Test Connection to ensure the configuration is correct. + +- **Name**: InfluxDB +- **Default**: Checked +- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) +- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB +on a separate server) +- **Access**: proxy +- **Database**: gitlab +- **User**: admin (Or the username configured when setting up InfluxDB) +- **Password**: The password configured when you set up InfluxDB + +![Grafana data source configurations](img/grafana_data_source_configuration.png) + +## Apply retention policies and create continuous queries + +If you intend to import the GitLab provided Grafana dashboards, you will need to +set up the right retention policies and continuous queries. The easiest way of +doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) +repository. + +To use this repository you must first clone it: + +``` +git clone https://gitlab.com/gitlab-org/influxdb-management.git +cd influxdb-management +``` + +Next you must install the required dependencies: + +``` +gem install bundler +bundle install +``` + +Now you must configure the repository by first copying `.env.example` to `.env` +and then editing the `.env` file to contain the correct InfluxDB settings. Once +configured you can simply run `bundle exec rake` and the InfluxDB database will +be configured for you. + +For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). + +## Import Dashboards + +You can now import a set of default dashboards that will give you a good +start on displaying useful information. GitLab has published a set of default +[Grafana dashboards][grafana-dashboards] to get you started. Clone the +repository or download a zip/tarball, then follow these steps to import each +JSON file. + +Open the dashboard dropdown menu and click 'Import' + +![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) + +Click 'Choose file' and browse to the location where you downloaded or cloned +the dashboard repository. Pick one of the JSON files to import. + +![Grafana dashboard import](img/grafana_dashboard_import.png) + +Once the dashboard is imported, be sure to click save icon in the top bar. If +you do not save the dashboard after importing it will be removed when you +navigate away. + +![Grafana save icon](img/grafana_save_icon.png) + +Repeat this process for each dashboard you wish to import. + +Alternatively you can automatically import all the dashboards into your Grafana +instance. See the README of the [Grafana dashboards][grafana-dashboards] +repository for more information on this process. + +[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Installation/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png Binary files differnew file mode 100644 index 00000000000..7e34fad71ce --- /dev/null +++ b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_import.png b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png Binary files differnew file mode 100644 index 00000000000..f97624365c7 --- /dev/null +++ b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png Binary files differnew file mode 100644 index 00000000000..7d50e4c88c2 --- /dev/null +++ b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_empty.png b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png Binary files differnew file mode 100644 index 00000000000..aa39a53acae --- /dev/null +++ b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png diff --git a/doc/administration/monitoring/performance/img/grafana_save_icon.png b/doc/administration/monitoring/performance/img/grafana_save_icon.png Binary files differnew file mode 100644 index 00000000000..c740e33cd1c --- /dev/null +++ b/doc/administration/monitoring/performance/img/grafana_save_icon.png diff --git a/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png Binary files differnew file mode 100644 index 00000000000..db396423e30 --- /dev/null +++ b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png diff --git a/doc/administration/monitoring/performance/influxdb_configuration.md b/doc/administration/monitoring/performance/influxdb_configuration.md new file mode 100644 index 00000000000..c30cd2950d8 --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_configuration.md @@ -0,0 +1,193 @@ +# InfluxDB Configuration + +The default settings provided by [InfluxDB] are not sufficient for a high traffic +GitLab environment. The settings discussed in this document are based on the +settings GitLab uses for GitLab.com, depending on your own needs you may need to +further adjust them. + +If you are intending to run InfluxDB on the same server as GitLab, make sure +you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. + +Unless you are going with a budget setup, it's advised to run it separately. + +## Requirements + +- InfluxDB 0.9.5 or newer +- A fairly modern version of Linux +- At least 4GB of RAM +- At least 10GB of storage for InfluxDB data + +Note that the RAM and storage requirements can differ greatly depending on the +amount of data received/stored. To limit the amount of stored data users can +look into [InfluxDB Retention Policies][influxdb-retention]. + +## Installation + +Installing InfluxDB is out of the scope of this document. Please refer to the +[InfluxDB documentation]. + +## InfluxDB Server Settings + +Since InfluxDB has many settings that users may wish to customize themselves +(e.g. what port to run InfluxDB on), we'll only cover the essentials. + +The configuration file in question is usually located at +`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, +InfluxDB needs to be restarted. + +### Storage Engine + +InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new +storage engine is available, called [TSM Tree]. All users **must** use the new +`tsm1` storage engine as this [will be the default engine][tsm1-commit] in +upcoming InfluxDB releases. + +Make sure you have the following in your configuration file: + +``` +[data] + dir = "/var/lib/influxdb/data" + engine = "tsm1" +``` + +### Admin Panel + +Production environments should have the InfluxDB admin panel **disabled**. This +feature can be disabled by adding the following to your InfluxDB configuration +file: + +``` +[admin] + enabled = false +``` + +### HTTP + +HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, +thus it should be enabled. When enabling make sure to _also_ enable +authentication: + +``` +[http] + enabled = true + auth-enabled = true +``` + +_**Note:** Before you enable authentication, you might want to [create an +admin user](#create-a-new-admin-user)._ + +### UDP + +GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling +UDP can be done using the following settings: + +``` +[[udp]] + enabled = true + bind-address = ":8089" + database = "gitlab" + batch-size = 1000 + batch-pending = 5 + batch-timeout = "1s" + read-buffer = 209715200 +``` + +This does the following: + +1. Enable UDP and bind it to port 8089 for all addresses. +2. Store any data received in the "gitlab" database. +3. Define a batch of points to be 1000 points in size and allow a maximum of + 5 batches _or_ flush them automatically after 1 second. +4. Define a UDP read buffer size of 200 MB. + +One of the most important settings here is the UDP read buffer size as if this +value is set too low, packets will be dropped. You must also make sure the OS +buffer size is set to the same value, the default value is almost never enough. + +To set the OS buffer size to 200 MB, on Linux you can run the following command: + +```bash +sysctl -w net.core.rmem_max=209715200 +``` + +To make this permanent, add the following to `/etc/sysctl.conf` and restart the +server: + +```bash +net.core.rmem_max=209715200 +``` + +It is **very important** to make sure the buffer sizes are large enough to +handle all data sent to InfluxDB as otherwise you _will_ lose data. The above +buffer sizes are based on the traffic for GitLab.com. Depending on the amount of +traffic, users may be able to use a smaller buffer size, but we highly recommend +using _at least_ 100 MB. + +When enabling UDP, users should take care to not expose the port to the public, +as doing so will allow anybody to write data into your InfluxDB database (as +[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either +whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only +allowing traffic from members of said VLAN. + +## Create a new admin user + +If you want to [enable authentication](#http), you might want to [create an +admin user][influx-admin]: + +``` +influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" +``` + +## Create the `gitlab` database + +Once you get InfluxDB up and running, you need to create a database for GitLab. +Make sure you have changed the [storage engine](#storage-engine) to `tsm1` +before creating a database. + +_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled +[HTTP authentication](#http), remember to append the username (`-username <username>`) +and password (`-password <password>`) you set earlier to the commands below._ + +Run the following command to create a database named `gitlab`: + +```bash +influx -execute 'CREATE DATABASE gitlab' +``` + +The name **must** be `gitlab`, do not use any other name. + +Next, make sure that the database was successfully created: + +```bash +influx -execute 'SHOW DATABASES' +``` + +The output should be similar to: + +``` +name: databases +--------------- +name +_internal +gitlab +``` + +That's it! Now your GitLab instance should send data to InfluxDB. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management +[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ +[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ +[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ +[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d +[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user diff --git a/doc/administration/monitoring/performance/influxdb_schema.md b/doc/administration/monitoring/performance/influxdb_schema.md new file mode 100644 index 00000000000..eff0e29f58d --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_schema.md @@ -0,0 +1,97 @@ +# InfluxDB Schema + +The following measurements are currently stored in InfluxDB: + +- `PROCESS_file_descriptors` +- `PROCESS_gc_statistics` +- `PROCESS_memory_usage` +- `PROCESS_method_calls` +- `PROCESS_object_counts` +- `PROCESS_transactions` +- `PROCESS_views` +- `events` + +Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the +process type. In all series, any form of duration is stored in milliseconds. + +## PROCESS_file_descriptors + +This measurement contains the number of open file descriptors over time. The +value field `value` contains the number of descriptors. + +## PROCESS_gc_statistics + +This measurement contains Ruby garbage collection statistics such as the amount +of minor/major GC runs (relative to the last sampling interval), the time spent +in garbage collection cycles, and all fields/values returned by `GC.stat`. + +## PROCESS_memory_usage + +This measurement contains the process' memory usage (in bytes) over time. The +value field `value` contains the number of bytes. + +## PROCESS_method_calls + +This measurement contains the methods called during a transaction along with +their duration, and a name of the transaction action that invoked the method (if +available). The method call duration is stored in the value field `duration`, +while the method name is stored in the tag `method`. The tag `action` contains +the full name of the transaction action. Both the `method` and `action` fields +are in the following format: + +``` +ClassName#method_name +``` + +For example, a method called by the `show` method in the `UsersController` class +would have `action` set to `UsersController#show`. + +## PROCESS_object_counts + +This measurement is used to store retained Ruby objects (per class) and the +amount of retained objects. The number of objects is stored in the `count` value +field while the class name is stored in the `type` tag. + +## PROCESS_transactions + +This measurement is used to store basic transaction details such as the time it +took to complete a transaction, how much time was spent in SQL queries, etc. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The total duration of the transaction | +| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | +| `method_duration` | The total time spent in method calls | +| `sql_duration` | The total time spent in SQL queries | +| `view_duration` | The total time spent in views | + +## PROCESS_views + +This measurement is used to store view rendering timings for a transaction. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The rendering time of the view | +| `view` | The path of the view, relative to the application's root directory | + +The `action` tag contains the action name of the transaction that rendered the +view. + +## events + +This measurement is used to store generic events such as the number of Git +pushes, Emails sent, etc. Each point in this measurement has a single value +field called `count`. The value of this field is simply set to `1`. Each point +also has at least one tag: `event`. This tag's value is set to the event name. +Depending on the event type additional tags may be available as well. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md new file mode 100644 index 00000000000..79904916b7e --- /dev/null +++ b/doc/administration/monitoring/performance/introduction.md @@ -0,0 +1,65 @@ +# GitLab Performance Monitoring + +GitLab comes with its own application performance measuring system as of GitLab +8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the +Community and Enterprise editions. + +Apart from this introduction, you are advised to read through the following +documents in order to understand and properly configure GitLab Performance Monitoring: + +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Install/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +## Introduction to GitLab Performance Monitoring + +GitLab Performance Monitoring makes it possible to measure a wide variety of statistics +including (but not limited to): + +- The time it took to complete a transaction (a web request or Sidekiq job). +- The time spent in running SQL queries and rendering HAML views. +- The time spent executing (instrumented) Ruby methods. +- Ruby object allocations, and retained objects in particular. +- System statistics such as the process' memory usage and open file descriptors. +- Ruby garbage collection statistics. + +Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored +data can be visualized using [Grafana][grafana] or any other application that +supports reading data from InfluxDB. Alternatively data can be queried using the +InfluxDB CLI. + +## Metric Types + +Two types of metrics are collected: + +1. Transaction specific metrics. +1. Sampled metrics, collected at a certain interval in a separate thread. + +### Transaction Metrics + +Transaction metrics are metrics that can be associated with a single +transaction. This includes statistics such as the transaction duration, timings +of any executed SQL queries, time spent rendering HAML views, etc. These metrics +are collected for every Rack request and Sidekiq job processed. + +### Sampled Metrics + +Sampled metrics are metrics that can't be associated with a single transaction. +Examples include garbage collection statistics and retained Ruby objects. These +metrics are collected at a regular interval. This interval is made up out of two +parts: + +1. A user defined interval. +1. A randomly generated offset added on top of the interval, the same offset + can't be used twice in a row. + +The actual interval can be anywhere between a half of the defined interval and a +half above the interval. For example, for a user defined interval of 15 seconds +the actual interval can be anywhere between 7.5 and 22.5. The interval is +re-generated for every sampling run instead of being generated once and re-used +for the duration of the process' lifetime. + +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[grafana]: http://grafana.org/ diff --git a/doc/administration/operations.md b/doc/administration/operations.md new file mode 100644 index 00000000000..4b582d16b64 --- /dev/null +++ b/doc/administration/operations.md @@ -0,0 +1,6 @@ +# GitLab operations + +- [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md) +- [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md) +- [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) +- [Moving repositories to a new location](operations/moving_repositories.md) diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md new file mode 100644 index 00000000000..93521e976d5 --- /dev/null +++ b/doc/administration/operations/cleaning_up_redis_sessions.md @@ -0,0 +1,52 @@ +# Cleaning up stale Redis sessions + +Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. +Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If +you have been running a large GitLab server (thousands of users) since before +GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis +database after you upgrade to GitLab 7.3. You can also perform a cleanup while +still running GitLab 7.2 or older, but in that case new stale sessions will +start building up again after you clean up. + +In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte +hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with +GitLab 7.3.0, the keys are +prefixed with 'session:gitlab:', so they would look like +'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to +remove the keys in the old format. + +First we define a shell function with the proper Redis connection details. + +``` +rcli() { + # This example works for Omnibus installations of GitLab 7.3 or newer. For an + # installation from source you will have to change the socket path and the + # path to redis-cli. + sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" +} + +# test the new shell function; the response should be PONG +rcli ping +``` + +Now we do a search to see if there are any session keys in the old format for +us to clean up. + +``` +# returns the number of old-format session keys in Redis +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l +``` + +If the number is larger than zero, you can proceed to expire the keys from +Redis. If the number is zero there is nothing to clean up. + +``` +# Tell Redis to expire each matched key after 600 seconds. +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli +# This will print '(integer) 1' for each key that gets expired. +``` + +Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis +background save interval) your Redis database will be compacted. If you are +still using GitLab 7.2, users who are not clicking around in GitLab during the +10 minute expiry window will be signed out of GitLab. diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md new file mode 100644 index 00000000000..54adb99386a --- /dev/null +++ b/doc/administration/operations/moving_repositories.md @@ -0,0 +1,180 @@ +# Moving repositories managed by GitLab + +Sometimes you need to move all repositories managed by GitLab to +another filesystem or another server. In this document we will look +at some of the ways you can copy all your repositories from +`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. + +We will look at three scenarios: the target directory is empty, the +target directory contains an outdated copy of the repositories, and +how to deal with thousands of repositories. + +**Each of the approaches we list can/will overwrite data in the +target directory `/mnt/gitlab/repositories`. Do not mix up the +source and the target.** + +## Target directory is empty: use a tar pipe + +If the target directory `/mnt/gitlab/repositories` is empty the +simplest thing to do is to use a tar pipe. This method has low +overhead and tar is almost always already installed on your system. +However, it is not possible to resume an interrupted tar pipe: if +that happens then all data must be copied again. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to see progress, replace `-xf` with `-xvf`. + +### Tar pipe to another server + +You can also use a tar pipe to copy data to another server. If your +'git' user has SSH access to the newserver as 'git@newserver', you +can pipe the data through SSH. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + ssh git@newserver tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to compress the data before it goes over the network +(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. + +## The target directory contains an outdated copy of the repositories: use rsync + +If the target directory already contains a partial / outdated copy +of the repositories it may be wasteful to copy all the data again +with tar. In this scenario it is better to use rsync. This utility +is either already installed on your system or easily installable +via apt, yum etc. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + /mnt/gitlab/repositories +``` + +The `/.` in the command above is very important, without it you can +easily get the wrong directory structure in the target directory. +If you want to see progress, replace `-a` with `-av`. + +### Single rsync to another server + +If the 'git' user on your source system has SSH access to the target +server you can send the repositories over the network with rsync. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + git@newserver:/mnt/gitlab/repositories +``` + +## Thousands of Git repositories: use one rsync per repository + +Every time you start an rsync job it has to inspect all files in +the source directory, all files in the target directory, and then +decide what files to copy or not. If the source or target directory +has many contents this startup phase of rsync can become a burden +for your GitLab server. In cases like this you can make rsync's +life easier by dividing its work in smaller pieces, and sync one +repository at a time. + +In addition to rsync we will use [GNU +Parallel](http://www.gnu.org/software/parallel/). This utility is +not included in GitLab so you need to install it yourself with apt +or yum. Also note that the GitLab scripts we used below were added +in GitLab 8.1. + +** This process does not clean up repositories at the target location that no +longer exist at the source. ** If you start using your GitLab instance with +`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` +after switching to the new repository storage directory. + +### Parallel rsync for all repositories known to GitLab + +This will sync repositories with 10 rsync processes at a time. We keep +track of progress so that the transfer can be restarted if necessary. + +First we create a new directory, owned by 'git', to hold transfer +logs. We assume the directory is empty before we start the transfer +procedure, and that we are the only ones writing files in it. + +``` +# Omnibus +sudo mkdir /var/opt/gitlab/transfer-logs +sudo chown git:git /var/opt/gitlab/transfer-logs + +# Source +sudo -u git -H mkdir /home/git/transfer-logs +``` + +We seed the process with a list of the directories we want to copy. + +``` +# Omnibus +sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' +``` + +Now we can start the transfer. The command below is idempotent, and +the number of jobs done by GNU Parallel should converge to zero. If it +does not some repositories listed in all-repos-1234.txt may have been +deleted/renamed before they could be copied. + +``` +# Omnibus +sudo -u git sh -c ' +cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories +' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c ' +cat /home/git/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + /home/git/transfer-logs/success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +` +``` + +### Parallel rsync only for repositories with recent activity + +Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. +Then you might only want to sync repositories that were changed via GitLab +_after_ that time. You can use the 'SINCE' variable to tell 'rake +gitlab:list_repos' to only print repositories with recent activity. + +``` +# Omnibus +sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git \ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories + +# Source +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git -H \ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +``` diff --git a/doc/administration/operations/sidekiq_memory_killer.md b/doc/administration/operations/sidekiq_memory_killer.md new file mode 100644 index 00000000000..b5e78348989 --- /dev/null +++ b/doc/administration/operations/sidekiq_memory_killer.md @@ -0,0 +1,40 @@ +# Sidekiq MemoryKiller + +The GitLab Rails application code suffers from memory leaks. For web requests +this problem is made manageable using +[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which +restarts Unicorn worker processes in between requests when needed. The Sidekiq +MemoryKiller applies the same approach to the Sidekiq processes used by GitLab +to process background jobs. + +Unlike unicorn-worker-killer, which is enabled by default for all GitLab +installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default +_only_ for Omnibus packages. The reason for this is that the MemoryKiller +relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab +installations from source do not all use Runit or an equivalent. + +With the default settings, the MemoryKiller will cause a Sidekiq restart no +more often than once every 15 minutes, with the restart causing about one +minute of delay for incoming background jobs. + +## Configuring the MemoryKiller + +The MemoryKiller is controlled using environment variables. + +- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is + greater than 0, then after each Sidekiq job, the MemoryKiller will check the + RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq + process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a + delayed shutdown is triggered. The default value for Omnibus packages is set + [in the omnibus-gitlab + repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). +- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When + a shutdown is triggered, the Sidekiq process will keep working normally for + another 15 minutes. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace + time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. + Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells + Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must + restart Sidekiq. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of + the final signal sent to the Sidekiq process when we want it to shut down. diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md new file mode 100644 index 00000000000..bad61151bda --- /dev/null +++ b/doc/administration/operations/unicorn.md @@ -0,0 +1,86 @@ +# Understanding Unicorn and unicorn-worker-killer + +## Unicorn + +GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web +server, to handle web requests (web browsers and Git HTTP clients). Unicorn is +a daemon written in Ruby and C that can load and run a Ruby on Rails +application; in our case the Rails application is GitLab Community Edition or +GitLab Enterprise Edition. + +Unicorn has a multi-process architecture to make better use of available CPU +cores (processes can run on different cores) and to have stronger fault +tolerance (most failures stay isolated in only one process and cannot take down +GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby +environment with the GitLab application code, and then spawns 'workers' which +inherit this clean initial environment. The 'master' never handles any +requests, that is left to the workers. The operating system network stack +queues incoming requests and distributes them among the workers. + +In a perfect world, the master would spawn its pool of workers once, and then +the workers handle incoming web requests one after another until the end of +time. In reality, worker processes can crash or time out: if the master notices +that a worker takes too long to handle a request it will terminate the worker +process with SIGKILL ('kill -9'). No matter how the worker process ended, the +master process will replace it with a new 'clean' process again. Unicorn is +designed to be able to replace 'crashed' workers without dropping user +requests. + +This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The +master process has PID 56227 below. + +``` +[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing +[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10 +[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538 +[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready +``` + +### Tunables + +The main tunables for Unicorn are the number of worker processes and the +request timeout after which the Unicorn master terminates a worker process. +See the [omnibus-gitlab Unicorn settings +documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md) +if you want to adjust these settings. + +## unicorn-worker-killer + +GitLab has memory leaks. These memory leaks manifest themselves in long-running +processes, such as Unicorn workers. (The Unicorn master process is not known to +leak memory, probably because it does not handle user requests.) + +To make these memory leaks manageable, GitLab comes with the +[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This +gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn +workers to do a memory self-check after every 16 requests. If the memory of the +Unicorn worker exceeds a pre-set limit then the worker process exits. The +Unicorn master then automatically replaces the worker process. + +This is a robust way to handle memory leaks: Unicorn is designed to handle +workers that 'crash' so no user requests will be dropped. The +unicorn-worker-killer gem is designed to only terminate a worker process _in +between requests_, so no user requests are affected. + +This is what a Unicorn worker memory restart looks like in unicorn_stderr.log. +You see that worker 4 (PID 125918) is inspecting itself and decides to exit. +The threshold memory value was 254802235 bytes, about 250MB. With GitLab this +threshold is a random value between 200 and 250 MB. The master process (PID +117565) then reaps the worker process and spawns a new 'worker 4' with PID +127549. + +``` +[2015-06-05T12:07:41.828374 #125918] WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes) +[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1) +[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4 +[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549 +[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready +``` + +One other thing that stands out in the log snippet above, taken from +GitLab.com, is that 'worker 4' was serving requests for only 23 seconds. This +is a normal value for our current GitLab.com setup and traffic. + +The high frequency of Unicorn memory restarts on some GitLab sites can be a +source of confusion for administrators. Usually they are a [red +herring](https://en.wikipedia.org/wiki/Red_herring). diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md new file mode 100644 index 00000000000..5a9a1582877 --- /dev/null +++ b/doc/administration/reply_by_email.md @@ -0,0 +1,302 @@ +# Reply by email + +GitLab can be set up to allow users to comment on issues and merge requests by +replying to notification emails. + +## Requirement + +Reply by email requires an IMAP-enabled email account. GitLab allows you to use +three strategies for this feature: +- using email sub-addressing +- using a dedicated email address +- using a catch-all mailbox + +### Email sub-addressing + +**If your provider or server supports email sub-addressing, we recommend using it.** + +[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is +a feature where any email to `user+some_arbitrary_tag@example.com` will end up +in the mailbox for `user@example.com`, and is supported by providers such as +Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix +mail server which you can run on-premises. + +### Dedicated email address + +This solution is really simple to set up: you just have to create an email +address dedicated to receive your users' replies to GitLab notifications. + +### Catch-all mailbox + +A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will +"catch all" the emails addressed to the domain that do not exist in the mail +server. + +## How it works? + +### 1. GitLab sends a notification email + +When GitLab sends a notification and Reply by email is enabled, the `Reply-To` +header is set to the address defined in your GitLab configuration, with the +`%{key}` placeholder (if present) replaced by a specific "reply key". In +addition, this "reply key" is also added to the `References` header. + +### 2. You reply to the notification email + +When you reply to the notification email, your email client will: + +- send the email to the `Reply-To` address it got from the notification email +- set the `In-Reply-To` header to the value of the `Message-ID` header from the + notification email +- set the `References` header to the value of the `Message-ID` plus the value of + the notification email's `References` header. + +### 3. GitLab receives your reply to the notification email + +When GitLab receives your reply, it will look for the "reply key" in the +following headers, in this order: + +1. the `To` header +1. the `References` header + +If it finds a reply key, it will be able to leave your reply as a comment on +the entity the notification was about (issue, merge request, commit...). + +For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, +please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). + +## Set it up + +If you want to use Gmail / Google Apps with Reply by email, make sure you have +[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) +and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). + +To set up a basic Postfix mail server with IMAP access on Ubuntu, follow +[these instructions](./postfix.md). + +### Omnibus package installations + +1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the + feature and fill in the details for your specific IMAP server and email account: + + ```ruby + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "incoming" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "gitlab.example.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 143 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = false + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + + ```ruby + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "imap.gmail.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 993 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = true + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + +1. Reconfigure GitLab and restart mailroom for the changes to take effect: + + ```sh + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart mailroom + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo gitlab-rake gitlab:incoming_email:check + ``` + +1. Reply by email should now be working. + +### Installations from source + +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab + ``` + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature + and fill in the details for your specific IMAP server and email account: + + ```sh + sudo editor config/gitlab.yml + ``` + + ```yaml + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "incoming" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "gitlab.example.com" + # IMAP server port + port: 143 + # Whether the IMAP server uses SSL + ssl: false + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + +1. Enable `mail_room` in the init script at `/etc/default/gitlab`: + + ```sh + sudo mkdir -p /etc/default + echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab + ``` + +1. Restart GitLab: + + ```sh + sudo service gitlab restart + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production + ``` + +1. Reply by email should now be working. + +### Development + +1. Go to the GitLab installation directory. + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. + +1. Uncomment the `mail_room` line in your `Procfile`: + + ```yaml + mail_room: bundle exec mail_room -q -c config/mail_room.yml + ``` + +1. Restart GitLab: + + ```sh + bundle exec foreman start + ``` + +1. Verify that everything is configured correctly: + + ```sh + bundle exec rake gitlab:incoming_email:check RAILS_ENV=development + ``` + +1. Reply by email should now be working. diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md new file mode 100644 index 00000000000..22f10489a6c --- /dev/null +++ b/doc/administration/reply_by_email_postfix_setup.md @@ -0,0 +1,324 @@ +# Set up Postfix for Reply by email + +This document will take you through the steps of setting up a basic Postfix mail +server with IMAP authentication on Ubuntu, to be used with [Reply by email]. + +The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. + +## Configure your server firewall + +1. Open up port 25 on your server so that people can send email into the server over SMTP. +2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. + +## Install packages + +1. Install the `postfix` package if it is not installed already: + + ```sh + sudo apt-get install postfix + ``` + + When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. + +1. Install the `mailutils` package. + + ```sh + sudo apt-get install mailutils + ``` + +## Create user + +1. Create a user for incoming email. + + ```sh + sudo useradd -m -s /bin/bash incoming + ``` + +1. Set a password for this user. + + ```sh + sudo passwd incoming + ``` + + Be sure not to forget this, you'll need it later. + +## Test the out-of-the-box setup + +1. Connect to the local SMTP server: + + ```sh + telnet localhost 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, verify that `postfix` is running: + + ```sh + sudo postfix status + ``` + + If it is not, start it: + + ```sh + sudo postfix start + ``` + +1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo localhost + mail from: root@localhost + rcpt to: incoming@localhost + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + _**Note:** The `.` is a literal period on its own line._ + + _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` + then your Postfix `my_network` configuration is not correct. The error will + say 'Temporary lookup failure'. See + [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ + +1. Check if the `incoming` user received the email: + + ```sh + su - incoming + mail + ``` + + You should see output like this: + + ``` + "/var/mail/incoming": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Configure Postfix to use Maildir-style mailboxes + +Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. + +1. Configure Postfix to use Maildir-style mailboxes: + + ```sh + sudo postconf -e "home_mailbox = Maildir/" + ``` + +1. Restart Postfix: + + ```sh + sudo /etc/init.d/postfix restart + ``` + +1. Test the new setup: + + 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + _**Note:** If `mail` returns an error `Maildir: Is a directory` then your + version of `mail` doesn't support Maildir style mailboxes. Install + `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, + try the above steps again, substituting `heirloom-mailx` for the `mail` + command._ + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Install the Courier IMAP server + +1. Install the `courier-imap` package: + + ```sh + sudo apt-get install courier-imap + ``` + +## Configure Postfix to receive email from the internet + +1. Let Postfix know about the domains that it should consider local: + + ```sh + sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" + ``` + +1. Let Postfix know about the IPs that it should consider part of the LAN: + + We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. + + ```sh + sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" + ``` + +1. Configure Postfix to receive mail on all interfaces, which includes the internet: + + ```sh + sudo postconf -e "inet_interfaces = all" + ``` + +1. Configure Postfix to use the `+` delimiter for sub-addressing: + + ```sh + sudo postconf -e "recipient_delimiter = +" + ``` + +1. Restart Postfix: + + ```sh + sudo service postfix restart + ``` + +## Test the final setup + +1. Test SMTP under the new setup: + + 1. Connect to the SMTP server: + + ```sh + telnet gitlab.example.com 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to gitlab.example.com. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. + + 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo gitlab.example.com + mail from: root@gitlab.example.com + rcpt to: incoming@gitlab.example.com + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + (Note: The `.` is a literal period on its own line) + + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@gitlab.example.com 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + 1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +1. Test IMAP under the new setup: + + 1. Connect to the IMAP server: + + ```sh + telnet gitlab.example.com 143 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to mail.example.gitlab.com. + Escape character is '^]'. + - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. + ``` + + 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: + + ``` + a login incoming PASSWORD + ``` + + Replace PASSWORD with the password you set on the `incoming` user earlier. + + You should see output like this: + + ``` + a OK LOGIN Ok. + ``` + + 1. Disconnect from the IMAP server: + + ```sh + a logout + ``` + +## Done! + +If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. + +--- + +_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ + +[reply by email]: reply_by_email.md diff --git a/doc/administration/restart_gitlab.md b/doc/administration/restart_gitlab.md index 483060395dd..b561c2f82aa 100644 --- a/doc/administration/restart_gitlab.md +++ b/doc/administration/restart_gitlab.md @@ -139,7 +139,7 @@ If you are using other init systems, like systemd, you can check the [omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages" [install]: ../install/installation.md "Documentation to install GitLab from source" -[mailroom]: ../incoming_email/README.md "Used for replying by email in GitLab issues and merge requests" +[mailroom]: reply_by_email.md "Used for replying by email in GitLab issues and merge requests" [chef]: https://www.chef.io/chef/ "Chef official website" [src-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab "GitLab init service file" [gl-recipes]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/init "GitLab Recipes repository" diff --git a/doc/api/README.md b/doc/api/README.md index bbd5bcfb386..9e907689c80 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -355,6 +355,19 @@ follows: } ``` +## Unknown route + +When you try to access an API URL that does not exist you will receive 404 Not Found. + +``` +HTTP/1.1 404 Not Found +Content-Type: application/json +{ + "error": "404 Not Found" +} +``` + + ## Clients There are many unofficial GitLab API Clients for most of the popular diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 40f0165deef..08fbd9afa2f 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -11,6 +11,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) - [Test a Scala application](test-scala-application.md) +- [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md new file mode 100644 index 00000000000..150698ca04b --- /dev/null +++ b/doc/ci/examples/test-phoenix-application.md @@ -0,0 +1,56 @@ +## Test a Phoenix application + +This example demonstrates the integration of Gitlab CI with Phoenix, Elixir and +Postgres. + +### Add `.gitlab-ci.yml` file to project + +The following `.gitlab-ci.yml` should be added in the root of your +repository to trigger CI: + +```yaml +image: elixir:1.3 + +services: + - postgres:9.6 + +variables: + MIX_ENV: "test" + +before_script: + # Setup phoenix dependencies + - apt-get update + - apt-get install -y postgresql-client + - mix local.hex --force + - mix deps.get --only test + - mix ecto.reset + +test: + script: + - mix test +``` + +The variables will set the Mix environment to "test". The +`before_script` will install `psql`, some Phoenix dependencies, and will also +run your migrations. + +Finally, the test `script` will run your tests. + +### Update the Config Settings + +In `config/test.exs`, update the database hostname: + +```elixir +config :my_app, MyApp.Repo, + hostname: if(System.get_env("CI"), do: "postgres", else: "localhost"), +``` + +### Add the Migrations Folder + +If you do not have any migrations yet, you will need to create an empty +`.gitkeep` file in `priv/repo/migrations`. + +### Sources + +- https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 +- https://davejlong.com/ci-with-phoenix-and-gitlab/ diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ca9b986a060..729c1dc8c0d 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -31,6 +31,8 @@ project. ## Seeing build status Clicking on a pipeline will show the builds that were run for that pipeline. +Clicking on an individual build will show you its build trace, and allow you to +cancel the build, retry it, or erase the build trace. ## Badges diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 22d67bd9964..a4c3a731a20 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -48,6 +48,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | @@ -105,6 +106,39 @@ Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +#### Debug tracing + +> **WARNING:** Enabling debug tracing can have severe security implications. The + output **will** contain the content of all your secure variables and any other + secrets! The output **will** be uploaded to the GitLab server and made visible + in build traces! + +By default, GitLab Runner hides most of the details of what it is doing when +processing a job. This behaviour keeps build traces short, and prevents secrets +from being leaked into the trace unless your script writes them to the screen. + +If a job isn't working as expected, this can make the problem difficult to +investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. +Available on GitLab Runner v1.7+, this feature enables the shell's execution +trace, resulting in a verbose build trace listing all commands that were run, +variables that were set, etc. + +Before enabling this, you should ensure builds are visible to +[team members only](../../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-traces) all generated build traces +before making them visible again. + +To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: + +```yaml +job1: + variables: + CI_DEBUG_TRACE: "true" +``` + +The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) +demonstrates a working configuration, including build trace examples. + ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 39b801f761d..0b725cf200c 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -314,6 +314,29 @@ In this case: - different highlighting languages are used for each config in the code block - the [references](#references) guide is used for reconfigure/restart +## Fake tokens + +There may be times where a token is needed to demonstrate an API call using +cURL or a secret variable used in CI. It is strongly advised not to use real +tokens in documentation even if the probability of a token being exploited is +low. + +You can use the following fake tokens as examples. + +| **Token type** | **Token value** | +| --------------------- | --------------------------------- | +| Private user token | `9koXpg98eAheJpvBs5tK` | +| Personal access token | `n671WNGecHugsdEDPsyo` | +| Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` | +| Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` | +| Secret CI variable | `Li8j-mLUVA3eZYjPfd_H` | +| Specific Runner token | `yrnZW46BrtBFqM7xDzE7dddd` | +| Shared Runner token | `6Vk7ZsosqQyfreAxXTZr` | +| Trigger token | `be20d8dcc028677c931e04f3871a9b` | +| Webhook secret token | `6XhDroRcYPM5by_h-HLY` | +| Health check token | `Tu7BgjR9qeZTEyRzGG2P` | +| Request profile token | `7VgpS4Ax5utVD2esNstz` | + ## API Here is a list of must-have items. Use them in the exact order that appears diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 3aa83975ace..d7e3aa35bdd 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,14 +2,14 @@ Step-by-step guides on the basics of working with Git and GitLab. +- [Command line basics](command-line-commands.md) - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) -- [Command Line basics](command-line-commands.md) - [Create a project](create-project.md) - [Create a group](create-group.md) - [Create a branch](create-branch.md) - [Fork a project](fork-project.md) - [Add a file](add-file.md) - [Add an image](add-image.md) -- [Create a Merge Request](add-merge-request.md) -- [Create an Issue](create-issue.md) +- [Create an issue](create-issue.md) +- [Create a merge request](add-merge-request.md) diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index ff10a98e8f5..e9fbcbc23a9 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -1,27 +1,5 @@ # How to add a file -You can create a file in your [shell](command-line-commands.md) or in GitLab. - -To create a file in GitLab, sign in to GitLab. - -Select a project on the right side of your screen: - -![Select a project](basicsimages/select_project.png) - -It's a good idea to [create a branch](create-branch.md), but it's not necessary. - -Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory: - -![Create a file](basicsimages/create_file.png) - -Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use : - -![File name](basicsimages/file_name.png) - -Add all the information that you'd like to include in your file: - -![Add information](basicsimages/white_space.png) - -Add a commit message based on what you just added and then click on "commit changes": - -![Commit changes](basicsimages/commit_changes.png) +You can create a file in your [terminal](command-line-commands.md) and push +to GitLab or you can use the +[web interface](../user/project/repository/web_editor.md#create-a-file). diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 236b4248ea2..bf01fe51dc3 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -1,42 +1,33 @@ # How to create a merge request -Merge Requests are useful to integrate separate changes that you've made to a project, on different branches. +Merge requests are useful to integrate separate changes that you've made to a +project, on different branches. This is a brief guide on how to create a merge +request. For more information, check the +[merge requests documentation](../user/project/merge_requests.md). -To create a new Merge Request, sign in to GitLab. +--- -Go to the project where you'd like to merge your changes: +1. Before you start, you should have already [created a branch](create-branch.md) + and [pushed your changes](basic-git-commands.md) to GitLab. -![Select a project](basicsimages/select_project.png) +1. You can then go to the project where you'd like to merge your changes and + click on the **Merge requests** tab. -Click on "Merge Requests" on the left side of your screen: + ![Merge requests](img/project_navbar.png) -![Merge requests](basicsimages/merge_requests.png) +1. Click on **New merge request** on the right side of the screen. -Click on "+ new Merge Request" on the right side of the screen: + ![New Merge Request](img/merge_request_new.png) -![New Merge Request](basicsimages/new_merge_request.png) +1. Select a source branch and click on the **Compare branches and continue** button. -Select a source branch or branch: + ![Select a branch](img/merge_request_select_branch.png) -![Select a branch](basicsimages/select_branch.png) +1. At a minimum, add a title and a description to your merge request. Optionally, + select a user to review your merge request and to accept or close it. You may + also select a milestone and labels. -Click on the "compare branches" button: + ![New merge request page](img/merge_request_page.png) -![Compare branches](basicsimages/compare_branches.png) - -Add a title and a description to your Merge Request: - -![Add a title and description](basicsimages/title_description_mr.png) - -Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button: - -![Add a new merge request](basicsimages/add_new_merge_request.png) - -Your Merge Request will be ready to be approved and published. - -### Note - -After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen. -You may automatically create a Merge Request from your recently created branch when clicking on this button: - -![Automatic MR button](basicsimages/button-create-mr.png) +1. When ready, click on the **Submit merge request** button. Your merge request + will be ready to be approved and published. diff --git a/doc/gitlab-basics/basicsimages/add_new_merge_request.png b/doc/gitlab-basics/basicsimages/add_new_merge_request.png Binary files differdeleted file mode 100644 index e60992c4c6a..00000000000 --- a/doc/gitlab-basics/basicsimages/add_new_merge_request.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png Binary files differdeleted file mode 100644 index 89c86018629..00000000000 --- a/doc/gitlab-basics/basicsimages/add_sshkey.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png Binary files differdeleted file mode 100644 index 2264f3c5bf2..00000000000 --- a/doc/gitlab-basics/basicsimages/branch_info.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png Binary files differdeleted file mode 100644 index 75fe8313611..00000000000 --- a/doc/gitlab-basics/basicsimages/branch_name.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png Binary files differdeleted file mode 100644 index 8621bc05776..00000000000 --- a/doc/gitlab-basics/basicsimages/branches.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/button-create-mr.png b/doc/gitlab-basics/basicsimages/button-create-mr.png Binary files differdeleted file mode 100644 index b52ab148839..00000000000 --- a/doc/gitlab-basics/basicsimages/button-create-mr.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png Binary files differdeleted file mode 100644 index 6450deec6fc..00000000000 --- a/doc/gitlab-basics/basicsimages/click-on-new-group.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png Binary files differdeleted file mode 100644 index a88809c5a3f..00000000000 --- a/doc/gitlab-basics/basicsimages/commit_changes.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png Binary files differdeleted file mode 100644 index 4abe4517f98..00000000000 --- a/doc/gitlab-basics/basicsimages/commit_message.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png Binary files differdeleted file mode 100644 index 2bfcaf75f01..00000000000 --- a/doc/gitlab-basics/basicsimages/commits.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/compare_branches.png b/doc/gitlab-basics/basicsimages/compare_branches.png Binary files differdeleted file mode 100644 index 8a18453dd05..00000000000 --- a/doc/gitlab-basics/basicsimages/compare_branches.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png Binary files differdeleted file mode 100644 index 5ebe1b227dd..00000000000 --- a/doc/gitlab-basics/basicsimages/create_file.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png Binary files differdeleted file mode 100644 index 7ecc3baa990..00000000000 --- a/doc/gitlab-basics/basicsimages/create_group.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png Binary files differdeleted file mode 100644 index 9d3e817d036..00000000000 --- a/doc/gitlab-basics/basicsimages/edit_file.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png Binary files differdeleted file mode 100644 index e357cb5c6ab..00000000000 --- a/doc/gitlab-basics/basicsimages/file_located.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png Binary files differdeleted file mode 100644 index 01639c77d0d..00000000000 --- a/doc/gitlab-basics/basicsimages/file_name.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png Binary files differdeleted file mode 100644 index 6f26d26ae18..00000000000 --- a/doc/gitlab-basics/basicsimages/find_file.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png Binary files differdeleted file mode 100644 index 1211510aae9..00000000000 --- a/doc/gitlab-basics/basicsimages/find_group.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png Binary files differdeleted file mode 100644 index 13ff8345616..00000000000 --- a/doc/gitlab-basics/basicsimages/fork.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png Binary files differdeleted file mode 100644 index 2507d6c295b..00000000000 --- a/doc/gitlab-basics/basicsimages/group_info.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png Binary files differdeleted file mode 100644 index ef3dca60cc8..00000000000 --- a/doc/gitlab-basics/basicsimages/groups.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png Binary files differdeleted file mode 100644 index e74dbc13f9a..00000000000 --- a/doc/gitlab-basics/basicsimages/https.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png Binary files differdeleted file mode 100644 index 7f304b8e1f2..00000000000 --- a/doc/gitlab-basics/basicsimages/image_file.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png Binary files differdeleted file mode 100644 index 60a6f7973be..00000000000 --- a/doc/gitlab-basics/basicsimages/issue_title.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png Binary files differdeleted file mode 100644 index 14e9cdb64e1..00000000000 --- a/doc/gitlab-basics/basicsimages/issues.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png Binary files differdeleted file mode 100644 index 04400173ce8..00000000000 --- a/doc/gitlab-basics/basicsimages/key.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png Binary files differdeleted file mode 100644 index 570164df18b..00000000000 --- a/doc/gitlab-basics/basicsimages/merge_requests.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/new_issue.png b/doc/gitlab-basics/basicsimages/new_issue.png Binary files differdeleted file mode 100644 index 94e7503dd8b..00000000000 --- a/doc/gitlab-basics/basicsimages/new_issue.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png Binary files differdeleted file mode 100644 index 842f5ebed74..00000000000 --- a/doc/gitlab-basics/basicsimages/new_merge_request.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png Binary files differdeleted file mode 100644 index 421e8bc247b..00000000000 --- a/doc/gitlab-basics/basicsimages/new_project.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png Binary files differdeleted file mode 100644 index d5fcf33c4ea..00000000000 --- a/doc/gitlab-basics/basicsimages/newbranch.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png Binary files differdeleted file mode 100644 index 578ebee4440..00000000000 --- a/doc/gitlab-basics/basicsimages/paste_sshkey.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png Binary files differdeleted file mode 100644 index cb3f79f1879..00000000000 --- a/doc/gitlab-basics/basicsimages/profile_settings.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png Binary files differdeleted file mode 100644 index e1adb8d48c2..00000000000 --- a/doc/gitlab-basics/basicsimages/project_info.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png Binary files differdeleted file mode 100644 index 33b978dd899..00000000000 --- a/doc/gitlab-basics/basicsimages/select-group.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png Binary files differdeleted file mode 100644 index aee22c638db..00000000000 --- a/doc/gitlab-basics/basicsimages/select-group2.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png Binary files differdeleted file mode 100644 index f72a3ffb57f..00000000000 --- a/doc/gitlab-basics/basicsimages/select_branch.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png Binary files differdeleted file mode 100644 index 3bb832ea8d0..00000000000 --- a/doc/gitlab-basics/basicsimages/select_project.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png Binary files differdeleted file mode 100644 index 78637013d9b..00000000000 --- a/doc/gitlab-basics/basicsimages/settings.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png Binary files differdeleted file mode 100644 index c87f11a9d3d..00000000000 --- a/doc/gitlab-basics/basicsimages/shh_keys.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png Binary files differdeleted file mode 100644 index 78b854c8903..00000000000 --- a/doc/gitlab-basics/basicsimages/submit_new_issue.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png Binary files differdeleted file mode 100644 index c31d61ec336..00000000000 --- a/doc/gitlab-basics/basicsimages/title_description_mr.png +++ /dev/null diff --git a/doc/gitlab-basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png Binary files differdeleted file mode 100644 index eaa969bdcf4..00000000000 --- a/doc/gitlab-basics/basicsimages/white_space.png +++ /dev/null diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index addd3b6b6eb..3b075ff5fc0 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -4,18 +4,21 @@ In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab. -When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. +When you are on your Dashboard, click on the project that you'd like to clone. +To work in the project, you can copy a link to the Git repository through a SSH +or a HTTPS protocol. SSH is easier to use after it's been +[setup](create-your-ssh-keys.md). While you are at the **Project** tab, select +HTTPS or SSH from the dropdown menu and copy the link using the 'Copy to clipboard' +button (you'll have to paste it on your shell in the next step). -![Select a project](basicsimages/select_project.png) - -To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). - -![Copy the HTTPS or SSH](basicsimages/https.png) +![Copy the HTTPS or SSH](img/project_clone_url.png) ## On the command line ### Clone your project + Go to your computer's shell and type the following command: + ``` git clone PASTE HTTPS OR SSH HERE ``` @@ -23,26 +26,31 @@ git clone PASTE HTTPS OR SSH HERE A clone of the project will be created in your computer. ### Go into a project, directory or file to work in it + ``` cd NAME-OF-PROJECT-OR-FILE ``` ### Go back one directory or file + ``` cd ../ ``` ### View what’s in the directory that you are in + ``` ls ``` ### Create a directory + ``` mkdir NAME-OF-YOUR-DIRECTORY ``` ### Create a README.md or file in directory + ``` touch README.md nano README.md @@ -53,27 +61,33 @@ nano README.md ``` ### Remove a file + ``` rm NAME-OF-FILE ``` ### Remove a directory and all of its contents + ``` rm -rf NAME-OF-DIRECTORY ``` ### View history in the command line + ``` history ``` ### Carry out commands for which the account you are using lacks authority + You will be asked for an administrator’s password. + ``` sudo ``` ### Tell where you are + ``` pwd ``` diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 7556b0f663e..ad94f0dad29 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -2,38 +2,11 @@ A branch is an independent line of development. -New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project. - -To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab. - -To create a new branch in GitLab, sign in and then select a project on the right side of your screen: - -![Select a project](basicsimages/select_project.png) - -Click on "commits" on the menu on the left side of your screen: - -![Commits](basicsimages/commits.png) - -Click on the "branches" tab: - -![Branches](basicsimages/branches.png) - -Click on the "new branch" button on the right side of the screen: - -![New branch](basicsimages/newbranch.png) - -Fill out the information required: - -1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores) - -1. On the "create from" space, add the the name of the branch you want to branch off from - -1. Click on the button "create branch" - -![Branch info](basicsimages/branch_info.png) - -### Note: - -You will be able to find and select the name of your branch in the white box next to a project's name: - -![Branch name](basicsimages/branch_name.png) +New commits are recorded in the history for the current branch, which results +in taking the source from someone’s repository (the place where the history of +your work is stored) at certain point in time, and apply your own changes to it +in the history of the project. + +To add changes to your GitLab project, you should create a branch. You can do +it in your [terminal](basic-git-commands.md) or by +[using the web interface](../user/project/repository/web_editor.md#create-a-new-branch). diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index f80ae62e442..64274ccd5eb 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -1,43 +1,48 @@ # How to create a group in GitLab -## Create a group - Your projects in GitLab can be organized in 2 different ways: -under your own namespace for single projects, such as ´your-name/project-1'; or under groups. -If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects. - -To create a group, follow the instructions below: +under your own namespace for single projects, such as `your-name/project-1` or +under groups. -Sign in to [GitLab.com](https://gitlab.com). +If you organize your projects under a group, it works like a folder. You can +manage your group members' permissions and access to the projects. -When you are on your Dashboard, click on "Groups" on the left menu of your screen: +--- -![Go to groups](basicsimages/select-group2.png) +To create a group: -Click on "New group" on the top right side of your screen: +1. Expand the left sidebar by clicking the three bars at the upper left corner + and then navigate to **Groups**. -![New group](basicsimages/click-on-new-group.png) + ![Go to groups](img/create_new_group_sidebar.png) -Fill out the information required: +1. Once in your groups dashboard, click on **New group**. -1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores) + ![Create new group information](img/create_new_group_info.png) -1. Add details or a group description +1. Fill out the needed information: -1. You can choose a group avatar if you'd like + 1. Set the "Group path" which will be the namespace under which your projects + will be hosted (path can contain only letters, digits, underscores, dashes + and dots; it cannot start with dashes or end in dot). + 1. Optionally, you can add a description so that others can briefly understand + what this group is about. + 1. Optionally, choose and avatar for your project. + 1. Choose the [visibility level](../public_access/public_access.md). -1. Click on "create group" +1. Finally, click the **Create group** button. -![Group information](basicsimages/group_info.png) - -## Add a project to a group +## Add a new project to a group There are 2 different ways to add a new project to a group: -* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) +- Select a group and then click on the **New project** button. + + ![New project](img/create_new_project_from_group.png) -![New project](basicsimages/new_project.png) + You can then continue on [creating a project](create-project.md). -* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen +- While you are [creating a project](create-project.md), select a group namespace + you've already created from the dropdown menu. -![Create a group](basicsimages/create_group.png) + ![Select group](img/select_group_dropdown.png) diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index da9a165b8f5..13e5a738c89 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -1,27 +1,30 @@ # How to create an Issue in GitLab -The Issue Tracker is a good place to add things that need to be improved or solved in a project. +The issue tracker is a good place to add things that need to be improved or +solved in a project. -To create an Issue, sign in to GitLab. +--- -Go to the project where you'd like to create the Issue: +1. Go to the project where you'd like to create the issue and navigate to the + **Issues** tab on top. -![Select a project](basicsimages/select_project.png) + ![Issues](img/project_navbar.png) -Click on "Issues" on the left side of your screen: +1. Click on the **New issue** button on the right side of your screen. -![Issues](basicsimages/issues.png) + ![New issue](img/new_issue_button.png) -Click on the "+ new issue" button on the right side of your screen: +1. At the very minimum, add a title and a description to your issue. + You may assign it to a user, add a milestone or add labels (all optional). -![New issue](basicsimages/new_issue.png) + ![Issue title and description](img/new_issue_page.png) -Add a title and a description to your issue: +1. When ready, click on **Submit issue**. -![Issue title and description](basicsimages/issue_title.png) +--- -You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue": - -![Submit new issue](basicsimages/submit_new_issue.png) - -Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md). +Your Issue will now be added to the issue tracker of the project you opened it +at and will be ready to be reviewed. You can comment on it and mention the +people involved. You can also link issues to the merge requests where the issues +are solved. To do this, you can use an +[issue closing pattern](../user/project/issues/automatic_issue_closing.md). diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index f737dffc024..3f45a631b3a 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -1,21 +1,24 @@ # How to create a project in GitLab -To create a new project, sign in to GitLab. +There are two ways to create a new project in GitLab. -Go to your Dashboard and click on "new project" on the right side of your screen. +1. While in your dashboard, you can create a new project using the **New project** + green button or you can use the cross icon in the upper right corner next to + your avatar which is always visible. -![Create a project](basicsimages/new_project.png) + ![Create a project](img/create_new_project_button.png) -Fill out the required information: +1. From there you can see several options. -1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores) + ![Project information](img/create_new_project_info.png) -1. Your project's description +1. Fill out the information: -1. Select a [visibility level](https://gitlab.com/help/public_access/public_access) + 1. "Project name" is the name of your project (you can't use spaces, but you + can use hyphens or underscores). + 1. The "Project description" is optional and will be shown in your project's + dashboard so others can briefly understand what your project is about. + 1. Select a [visibility level](../public_access/public_access.md). + 1. You can also [import your existing projects](../workflow/importing/README.md). -1. You can also [import your existing projects](http://docs.gitlab.com/ce/workflow/importing/README.html) - -1. Click on "create project" - -!![Project information](basicsimages/project_info.png) +1. Finally, click **Create project**. diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index f31c353f2cf..b6ebe374de3 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -1,33 +1,37 @@ # How to create your SSH Keys -You need to connect your computer to your GitLab account through SSH Keys. They are unique for every computer that you link your GitLab account with. +1. The first thing you need to do is go to your [command line](start-using-git.md) + and follow the [instructions](../ssh/README.md) to generate your SSH key pair. -## Generate your SSH Key +1. Once you do that, login to GitLab with your credentials. +1. On the upper right corner, click on your avatar and go to your **Profile settings**. -Create an account on GitLab. Sign up and check your email for your confirmation link. + ![Profile settings dropdown](img/profile_settings.png) -After you confirm, go to GitLab and sign in to your account. +1. Navigate to the **SSH keys** tab. -## Add your SSH Key + ![SSH Keys](img/profile_settings_ssh_keys.png) -On the left side menu, click on "profile settings" and then click on "SSH Keys": +3. Paste your **public** key that you generated in the first step in the 'Key' + box. -![SSH Keys](basicsimages/shh_keys.png) + ![Paste SSH public key](img/profile_settings_ssh_keys_paste_pub.png) -Then click on the green button "Add SSH Key": +1. Optionally, give it a descriptive title so that you can recognize it in the + event you add multiple keys. -![Add SSH Key](basicsimages/add_sshkey.png) + ![SSH key title](img/profile_settings_ssh_keys_title.png) -There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: +1. Finally, click on **Add key** to add it to GitLab. You will be able to see + its fingerprint, its title and creation date. -![Paste SSH Key](basicsimages/paste_sshkey.png) + ![SSH key single page](img/profile_settings_ssh_keys_single_key.png) -## To generate an SSH Key on your command line -Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it. +>**Note:** +Once you add a key, you cannot edit it, only remove it. In case the paste +didn't work, you will have to remove the offending key and re-add it. -Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. +--- -![Paste SSH Key](basicsimages/key.png) - -Now, you'll be able to use Git over SSH, instead of Git over HTTP. +Congratulations! You are now ready to use Git over SSH, instead of Git over HTTP! diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md index 5f8b81ea919..6c232fe6086 100644 --- a/doc/gitlab-basics/fork-project.md +++ b/doc/gitlab-basics/fork-project.md @@ -1,19 +1,20 @@ # How to fork a project -A fork is a copy of an original repository that you can put somewhere else -or where you can experiment and apply changes that you can later decide if +A fork is a copy of an original repository that you can put in another namespace +where you can experiment and apply changes that you can later decide if publishing or not, without affecting your original project. It takes just a few steps to fork a project in GitLab. -Sign in to GitLab. +1. Go to a project's dashboard under the **Project** tab and click on the + **Fork** button. -Select a project on the right side of your screen: + ![Click on Fork button](img/fork_new.png) -![Select a project](basicsimages/select_project.png) +1. You will be asked where to fork the repository. Click on the user or group + to where you'd like to add the forked project. -Click on the "fork" button on the right side of your screen: + ![Choose namespace](img/fork_choose_namespace.png) -![Fork](basicsimages/fork.png) - -Click on the user or group to where you'd like to add the forked project. +1. After a few moments, depending on the repository's size, the forking will + complete. diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/gitlab-basics/img/create_new_group_info.png Binary files differnew file mode 100644 index 00000000000..c8eddfd1bbb --- /dev/null +++ b/doc/gitlab-basics/img/create_new_group_info.png diff --git a/doc/gitlab-basics/img/create_new_group_sidebar.png b/doc/gitlab-basics/img/create_new_group_sidebar.png Binary files differnew file mode 100644 index 00000000000..28017ee02e0 --- /dev/null +++ b/doc/gitlab-basics/img/create_new_group_sidebar.png diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png Binary files differnew file mode 100644 index 00000000000..e7c794d943f --- /dev/null +++ b/doc/gitlab-basics/img/create_new_project_button.png diff --git a/doc/gitlab-basics/img/create_new_project_from_group.png b/doc/gitlab-basics/img/create_new_project_from_group.png Binary files differnew file mode 100644 index 00000000000..6d41d17f9ca --- /dev/null +++ b/doc/gitlab-basics/img/create_new_project_from_group.png diff --git a/doc/gitlab-basics/img/create_new_project_info.png b/doc/gitlab-basics/img/create_new_project_info.png Binary files differnew file mode 100644 index 00000000000..16d56f0707f --- /dev/null +++ b/doc/gitlab-basics/img/create_new_project_info.png diff --git a/doc/gitlab-basics/img/fork_choose_namespace.png b/doc/gitlab-basics/img/fork_choose_namespace.png Binary files differnew file mode 100644 index 00000000000..82c9c3bd39e --- /dev/null +++ b/doc/gitlab-basics/img/fork_choose_namespace.png diff --git a/doc/gitlab-basics/img/fork_new.png b/doc/gitlab-basics/img/fork_new.png Binary files differnew file mode 100644 index 00000000000..41885223286 --- /dev/null +++ b/doc/gitlab-basics/img/fork_new.png diff --git a/doc/gitlab-basics/img/merge_request_new.png b/doc/gitlab-basics/img/merge_request_new.png Binary files differnew file mode 100644 index 00000000000..0aba5743f01 --- /dev/null +++ b/doc/gitlab-basics/img/merge_request_new.png diff --git a/doc/gitlab-basics/img/merge_request_page.png b/doc/gitlab-basics/img/merge_request_page.png Binary files differnew file mode 100644 index 00000000000..68c3bbf9444 --- /dev/null +++ b/doc/gitlab-basics/img/merge_request_page.png diff --git a/doc/gitlab-basics/img/merge_request_select_branch.png b/doc/gitlab-basics/img/merge_request_select_branch.png Binary files differnew file mode 100644 index 00000000000..516436ff6cc --- /dev/null +++ b/doc/gitlab-basics/img/merge_request_select_branch.png diff --git a/doc/gitlab-basics/img/new_issue_button.png b/doc/gitlab-basics/img/new_issue_button.png Binary files differnew file mode 100644 index 00000000000..46b626bed65 --- /dev/null +++ b/doc/gitlab-basics/img/new_issue_button.png diff --git a/doc/gitlab-basics/img/new_issue_page.png b/doc/gitlab-basics/img/new_issue_page.png Binary files differnew file mode 100644 index 00000000000..843504130b7 --- /dev/null +++ b/doc/gitlab-basics/img/new_issue_page.png diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png Binary files differnew file mode 100644 index 00000000000..f0abd478849 --- /dev/null +++ b/doc/gitlab-basics/img/profile_settings.png diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys.png b/doc/gitlab-basics/img/profile_settings_ssh_keys.png Binary files differnew file mode 100644 index 00000000000..2c9a42fe10c --- /dev/null +++ b/doc/gitlab-basics/img/profile_settings_ssh_keys.png diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png Binary files differnew file mode 100644 index 00000000000..cd7add6937f --- /dev/null +++ b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png Binary files differnew file mode 100644 index 00000000000..095beb02be8 --- /dev/null +++ b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png Binary files differnew file mode 100644 index 00000000000..4b998a7f948 --- /dev/null +++ b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png diff --git a/doc/gitlab-basics/img/project_clone_url.png b/doc/gitlab-basics/img/project_clone_url.png Binary files differnew file mode 100644 index 00000000000..eed430e1036 --- /dev/null +++ b/doc/gitlab-basics/img/project_clone_url.png diff --git a/doc/gitlab-basics/img/project_navbar.png b/doc/gitlab-basics/img/project_navbar.png Binary files differnew file mode 100644 index 00000000000..97cf3cd9702 --- /dev/null +++ b/doc/gitlab-basics/img/project_navbar.png diff --git a/doc/gitlab-basics/basicsimages/public_file_link.png b/doc/gitlab-basics/img/public_file_link.png Binary files differindex f60df6807f4..f60df6807f4 100644 --- a/doc/gitlab-basics/basicsimages/public_file_link.png +++ b/doc/gitlab-basics/img/public_file_link.png diff --git a/doc/gitlab-basics/img/select_group_dropdown.png b/doc/gitlab-basics/img/select_group_dropdown.png Binary files differnew file mode 100644 index 00000000000..7d8b89c2df9 --- /dev/null +++ b/doc/gitlab-basics/img/select_group_dropdown.png diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md index 5a9a1582877..db0f03f2c98 100644 --- a/doc/incoming_email/README.md +++ b/doc/incoming_email/README.md @@ -1,302 +1 @@ -# Reply by email - -GitLab can be set up to allow users to comment on issues and merge requests by -replying to notification emails. - -## Requirement - -Reply by email requires an IMAP-enabled email account. GitLab allows you to use -three strategies for this feature: -- using email sub-addressing -- using a dedicated email address -- using a catch-all mailbox - -### Email sub-addressing - -**If your provider or server supports email sub-addressing, we recommend using it.** - -[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is -a feature where any email to `user+some_arbitrary_tag@example.com` will end up -in the mailbox for `user@example.com`, and is supported by providers such as -Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix -mail server which you can run on-premises. - -### Dedicated email address - -This solution is really simple to set up: you just have to create an email -address dedicated to receive your users' replies to GitLab notifications. - -### Catch-all mailbox - -A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will -"catch all" the emails addressed to the domain that do not exist in the mail -server. - -## How it works? - -### 1. GitLab sends a notification email - -When GitLab sends a notification and Reply by email is enabled, the `Reply-To` -header is set to the address defined in your GitLab configuration, with the -`%{key}` placeholder (if present) replaced by a specific "reply key". In -addition, this "reply key" is also added to the `References` header. - -### 2. You reply to the notification email - -When you reply to the notification email, your email client will: - -- send the email to the `Reply-To` address it got from the notification email -- set the `In-Reply-To` header to the value of the `Message-ID` header from the - notification email -- set the `References` header to the value of the `Message-ID` plus the value of - the notification email's `References` header. - -### 3. GitLab receives your reply to the notification email - -When GitLab receives your reply, it will look for the "reply key" in the -following headers, in this order: - -1. the `To` header -1. the `References` header - -If it finds a reply key, it will be able to leave your reply as a comment on -the entity the notification was about (issue, merge request, commit...). - -For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, -please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). - -## Set it up - -If you want to use Gmail / Google Apps with Reply by email, make sure you have -[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) -and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). - -To set up a basic Postfix mail server with IMAP access on Ubuntu, follow -[these instructions](./postfix.md). - -### Omnibus package installations - -1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the - feature and fill in the details for your specific IMAP server and email account: - - ```ruby - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "incoming" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "gitlab.example.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 143 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = false - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - - ```ruby - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "imap.gmail.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 993 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - -1. Reconfigure GitLab and restart mailroom for the changes to take effect: - - ```sh - sudo gitlab-ctl reconfigure - sudo gitlab-ctl restart mailroom - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo gitlab-rake gitlab:incoming_email:check - ``` - -1. Reply by email should now be working. - -### Installations from source - -1. Go to the GitLab installation directory: - - ```sh - cd /home/git/gitlab - ``` - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature - and fill in the details for your specific IMAP server and email account: - - ```sh - sudo editor config/gitlab.yml - ``` - - ```yaml - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "incoming" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "gitlab.example.com" - # IMAP server port - port: 143 - # Whether the IMAP server uses SSL - ssl: false - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - -1. Enable `mail_room` in the init script at `/etc/default/gitlab`: - - ```sh - sudo mkdir -p /etc/default - echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab - ``` - -1. Restart GitLab: - - ```sh - sudo service gitlab restart - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production - ``` - -1. Reply by email should now be working. - -### Development - -1. Go to the GitLab installation directory. - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. - -1. Uncomment the `mail_room` line in your `Procfile`: - - ```yaml - mail_room: bundle exec mail_room -q -c config/mail_room.yml - ``` - -1. Restart GitLab: - - ```sh - bundle exec foreman start - ``` - -1. Verify that everything is configured correctly: - - ```sh - bundle exec rake gitlab:incoming_email:check RAILS_ENV=development - ``` - -1. Reply by email should now be working. +This document was moved to [administration/reply_by_email](../administration/reply_by_email.md). diff --git a/doc/incoming_email/postfix.md b/doc/incoming_email/postfix.md index 787d21f7f8f..90833238ac5 100644 --- a/doc/incoming_email/postfix.md +++ b/doc/incoming_email/postfix.md @@ -1,321 +1 @@ -# Set up Postfix for Reply by email - -This document will take you through the steps of setting up a basic Postfix mail server with IMAP authentication on Ubuntu, to be used with Reply by email. - -The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. - -## Configure your server firewall - -1. Open up port 25 on your server so that people can send email into the server over SMTP. -2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. - -## Install packages - -1. Install the `postfix` package if it is not installed already: - - ```sh - sudo apt-get install postfix - ``` - - When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. - -1. Install the `mailutils` package. - - ```sh - sudo apt-get install mailutils - ``` - -## Create user - -1. Create a user for incoming email. - - ```sh - sudo useradd -m -s /bin/bash incoming - ``` - -1. Set a password for this user. - - ```sh - sudo passwd incoming - ``` - - Be sure not to forget this, you'll need it later. - -## Test the out-of-the-box setup - -1. Connect to the local SMTP server: - - ```sh - telnet localhost 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 127.0.0.1... - Connected to localhost. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, verify that `postfix` is running: - - ```sh - sudo postfix status - ``` - - If it is not, start it: - - ```sh - sudo postfix start - ``` - -1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo localhost - mail from: root@localhost - rcpt to: incoming@localhost - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - _**Note:** The `.` is a literal period on its own line._ - - _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` - then your Postfix `my_network` configuration is not correct. The error will - say 'Temporary lookup failure'. See - [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ - -1. Check if the `incoming` user received the email: - - ```sh - su - incoming - mail - ``` - - You should see output like this: - - ``` - "/var/mail/incoming": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Configure Postfix to use Maildir-style mailboxes - -Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. - -1. Configure Postfix to use Maildir-style mailboxes: - - ```sh - sudo postconf -e "home_mailbox = Maildir/" - ``` - -1. Restart Postfix: - - ```sh - sudo /etc/init.d/postfix restart - ``` - -1. Test the new setup: - - 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - _**Note:** If `mail` returns an error `Maildir: Is a directory` then your - version of `mail` doesn't support Maildir style mailboxes. Install - `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, - try the above steps again, substituting `heirloom-mailx` for the `mail` - command._ - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Install the Courier IMAP server - -1. Install the `courier-imap` package: - - ```sh - sudo apt-get install courier-imap - ``` - -## Configure Postfix to receive email from the internet - -1. Let Postfix know about the domains that it should consider local: - - ```sh - sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" - ``` - -1. Let Postfix know about the IPs that it should consider part of the LAN: - - We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. - - ```sh - sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" - ``` - -1. Configure Postfix to receive mail on all interfaces, which includes the internet: - - ```sh - sudo postconf -e "inet_interfaces = all" - ``` - -1. Configure Postfix to use the `+` delimiter for sub-addressing: - - ```sh - sudo postconf -e "recipient_delimiter = +" - ``` - -1. Restart Postfix: - - ```sh - sudo service postfix restart - ``` - -## Test the final setup - -1. Test SMTP under the new setup: - - 1. Connect to the SMTP server: - - ```sh - telnet gitlab.example.com 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to gitlab.example.com. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. - - 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo gitlab.example.com - mail from: root@gitlab.example.com - rcpt to: incoming@gitlab.example.com - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - (Note: The `.` is a literal period on its own line) - - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@gitlab.example.com 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - 1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -1. Test IMAP under the new setup: - - 1. Connect to the IMAP server: - - ```sh - telnet gitlab.example.com 143 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to mail.example.gitlab.com. - Escape character is '^]'. - - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. - ``` - - 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: - - ``` - a login incoming PASSWORD - ``` - - Replace PASSWORD with the password you set on the `incoming` user earlier. - - You should see output like this: - - ``` - a OK LOGIN Ok. - ``` - - 1. Disconnect from the IMAP server: - - ```sh - a logout - ``` - -## Done! - -If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. - ---------- - -_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ +This document was moved to [administration/reply_by_email_postfix_setup](../administration/reply_by_email_postfix_setup.md). diff --git a/doc/install/installation.md b/doc/install/installation.md index 378ab6857b8..1fa8678223a 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -563,7 +563,7 @@ Using a self-signed certificate is discouraged but if you must use it follow the ### Enable Reply by email -See the ["Reply by email" documentation](../incoming_email/README.md) for more information on how to set this up. +See the ["Reply by email" documentation](../administration/reply_by_email.md) for more information on how to set this up. ### LDAP Authentication diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md index eac57bc3de4..6cf93c33ec2 100644 --- a/doc/monitoring/health_check.md +++ b/doc/monitoring/health_check.md @@ -1,66 +1 @@ -# Health Check - -> [Introduced][ce-3888] in GitLab 8.8. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -## Access Token - -An access token needs to be provided while accessing the health check endpoint. The current -accepted token can be found on the `admin/health_check` page of your GitLab instance. - -![access token](img/health_check_token.png) - -The access token can be passed as a URL parameter: - -``` -https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN -``` - -or as an HTTP header: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -## Using the Endpoint - -Once you have the access token, health information can be retrieved as plain text, JSON, -or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -would be like: - -``` -{"healthy":true,"message":"success"} -``` - -## Status - -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - -[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 -[pingdom]: https://www.pingdom.com -[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html -[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring +This document was moved to [user/admin_area/monitoring/health_check](../user/admin_area/monitoring/health_check.md). diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md index 771584268d9..a669bb28904 100644 --- a/doc/monitoring/performance/gitlab_configuration.md +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -1,40 +1 @@ -# GitLab Configuration - -GitLab Performance Monitoring is disabled by default. To enable it and change any of its -settings, navigate to the Admin area in **Settings > Metrics** -(`/admin/application_settings`). - -The minimum required settings you need to set are the InfluxDB host and port. -Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the -changes. - ---- - -![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) - ---- - -Finally, a restart of all GitLab processes is required for the changes to take -effect: - -```bash -# For Omnibus installations -sudo gitlab-ctl restart - -# For installations from source -sudo service gitlab restart -``` - -## Pending Migrations - -When any migrations are pending, the metrics are disabled until the migrations -have been performed. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/gitlab_configuration](../administration/monitoring/performance/gitlab_configuration.md). diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 7947b0fedc4..93320b40174 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -1,111 +1 @@ -# Grafana Configuration - -[Grafana](http://grafana.org/) is a tool that allows you to visualize time -series metrics through graphs and dashboards. It supports several backend -data stores, including InfluxDB. GitLab writes performance data to InfluxDB -and Grafana will allow you to query InfluxDB to display useful graphs. - -For the easiest installation and configuration, install Grafana on the same -server as InfluxDB. For larger installations, you may want to split out these -services. - -## Installation - -Grafana supplies package repositories (Yum/Apt) for easy installation. -See [Grafana installation documentation](http://docs.grafana.org/installation/) -for detailed steps. - -> **Note**: Before starting Grafana for the first time, set the admin user -and password in `/etc/grafana/grafana.ini`. Otherwise, the default password -will be `admin`. - -## Configuration - -Login as the admin user. Expand the menu by clicking the Grafana logo in the -top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' -in the top bar. - -![Grafana empty data source page](img/grafana_data_source_empty.png) - -Fill in the configuration details for the InfluxDB data source. Save and -Test Connection to ensure the configuration is correct. - -- **Name**: InfluxDB -- **Default**: Checked -- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) -- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB -on a separate server) -- **Access**: proxy -- **Database**: gitlab -- **User**: admin (Or the username configured when setting up InfluxDB) -- **Password**: The password configured when you set up InfluxDB - -![Grafana data source configurations](img/grafana_data_source_configuration.png) - -## Apply retention policies and create continuous queries - -If you intend to import the GitLab provided Grafana dashboards, you will need to -set up the right retention policies and continuous queries. The easiest way of -doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) -repository. - -To use this repository you must first clone it: - -``` -git clone https://gitlab.com/gitlab-org/influxdb-management.git -cd influxdb-management -``` - -Next you must install the required dependencies: - -``` -gem install bundler -bundle install -``` - -Now you must configure the repository by first copying `.env.example` to `.env` -and then editing the `.env` file to contain the correct InfluxDB settings. Once -configured you can simply run `bundle exec rake` and the InfluxDB database will -be configured for you. - -For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). - -## Import Dashboards - -You can now import a set of default dashboards that will give you a good -start on displaying useful information. GitLab has published a set of default -[Grafana dashboards][grafana-dashboards] to get you started. Clone the -repository or download a zip/tarball, then follow these steps to import each -JSON file. - -Open the dashboard dropdown menu and click 'Import' - -![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) - -Click 'Choose file' and browse to the location where you downloaded or cloned -the dashboard repository. Pick one of the JSON files to import. - -![Grafana dashboard import](img/grafana_dashboard_import.png) - -Once the dashboard is imported, be sure to click save icon in the top bar. If -you do not save the dashboard after importing it will be removed when you -navigate away. - -![Grafana save icon](img/grafana_save_icon.png) - -Repeat this process for each dashboard you wish to import. - -Alternatively you can automatically import all the dashboards into your Grafana -instance. See the README of the [Grafana dashboards][grafana-dashboards] -repository for more information on this process. - -[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Installation/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) +This document was moved to [administration/monitoring/performance/grafana_configuration](../administration/monitoring/performance/grafana_configuration.md). diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md index c30cd2950d8..02647de1eb0 100644 --- a/doc/monitoring/performance/influxdb_configuration.md +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -1,193 +1 @@ -# InfluxDB Configuration - -The default settings provided by [InfluxDB] are not sufficient for a high traffic -GitLab environment. The settings discussed in this document are based on the -settings GitLab uses for GitLab.com, depending on your own needs you may need to -further adjust them. - -If you are intending to run InfluxDB on the same server as GitLab, make sure -you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. - -Unless you are going with a budget setup, it's advised to run it separately. - -## Requirements - -- InfluxDB 0.9.5 or newer -- A fairly modern version of Linux -- At least 4GB of RAM -- At least 10GB of storage for InfluxDB data - -Note that the RAM and storage requirements can differ greatly depending on the -amount of data received/stored. To limit the amount of stored data users can -look into [InfluxDB Retention Policies][influxdb-retention]. - -## Installation - -Installing InfluxDB is out of the scope of this document. Please refer to the -[InfluxDB documentation]. - -## InfluxDB Server Settings - -Since InfluxDB has many settings that users may wish to customize themselves -(e.g. what port to run InfluxDB on), we'll only cover the essentials. - -The configuration file in question is usually located at -`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, -InfluxDB needs to be restarted. - -### Storage Engine - -InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new -storage engine is available, called [TSM Tree]. All users **must** use the new -`tsm1` storage engine as this [will be the default engine][tsm1-commit] in -upcoming InfluxDB releases. - -Make sure you have the following in your configuration file: - -``` -[data] - dir = "/var/lib/influxdb/data" - engine = "tsm1" -``` - -### Admin Panel - -Production environments should have the InfluxDB admin panel **disabled**. This -feature can be disabled by adding the following to your InfluxDB configuration -file: - -``` -[admin] - enabled = false -``` - -### HTTP - -HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, -thus it should be enabled. When enabling make sure to _also_ enable -authentication: - -``` -[http] - enabled = true - auth-enabled = true -``` - -_**Note:** Before you enable authentication, you might want to [create an -admin user](#create-a-new-admin-user)._ - -### UDP - -GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling -UDP can be done using the following settings: - -``` -[[udp]] - enabled = true - bind-address = ":8089" - database = "gitlab" - batch-size = 1000 - batch-pending = 5 - batch-timeout = "1s" - read-buffer = 209715200 -``` - -This does the following: - -1. Enable UDP and bind it to port 8089 for all addresses. -2. Store any data received in the "gitlab" database. -3. Define a batch of points to be 1000 points in size and allow a maximum of - 5 batches _or_ flush them automatically after 1 second. -4. Define a UDP read buffer size of 200 MB. - -One of the most important settings here is the UDP read buffer size as if this -value is set too low, packets will be dropped. You must also make sure the OS -buffer size is set to the same value, the default value is almost never enough. - -To set the OS buffer size to 200 MB, on Linux you can run the following command: - -```bash -sysctl -w net.core.rmem_max=209715200 -``` - -To make this permanent, add the following to `/etc/sysctl.conf` and restart the -server: - -```bash -net.core.rmem_max=209715200 -``` - -It is **very important** to make sure the buffer sizes are large enough to -handle all data sent to InfluxDB as otherwise you _will_ lose data. The above -buffer sizes are based on the traffic for GitLab.com. Depending on the amount of -traffic, users may be able to use a smaller buffer size, but we highly recommend -using _at least_ 100 MB. - -When enabling UDP, users should take care to not expose the port to the public, -as doing so will allow anybody to write data into your InfluxDB database (as -[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either -whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only -allowing traffic from members of said VLAN. - -## Create a new admin user - -If you want to [enable authentication](#http), you might want to [create an -admin user][influx-admin]: - -``` -influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" -``` - -## Create the `gitlab` database - -Once you get InfluxDB up and running, you need to create a database for GitLab. -Make sure you have changed the [storage engine](#storage-engine) to `tsm1` -before creating a database. - -_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled -[HTTP authentication](#http), remember to append the username (`-username <username>`) -and password (`-password <password>`) you set earlier to the commands below._ - -Run the following command to create a database named `gitlab`: - -```bash -influx -execute 'CREATE DATABASE gitlab' -``` - -The name **must** be `gitlab`, do not use any other name. - -Next, make sure that the database was successfully created: - -```bash -influx -execute 'SHOW DATABASES' -``` - -The output should be similar to: - -``` -name: databases ---------------- -name -_internal -gitlab -``` - -That's it! Now your GitLab instance should send data to InfluxDB. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management -[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ -[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ -[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ -[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d -[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user +This document was moved to [administration/monitoring/performance/influxdb_configuration](../administration/monitoring/performance/influxdb_configuration.md). diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md index eff0e29f58d..a989e323e04 100644 --- a/doc/monitoring/performance/influxdb_schema.md +++ b/doc/monitoring/performance/influxdb_schema.md @@ -1,97 +1 @@ -# InfluxDB Schema - -The following measurements are currently stored in InfluxDB: - -- `PROCESS_file_descriptors` -- `PROCESS_gc_statistics` -- `PROCESS_memory_usage` -- `PROCESS_method_calls` -- `PROCESS_object_counts` -- `PROCESS_transactions` -- `PROCESS_views` -- `events` - -Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the -process type. In all series, any form of duration is stored in milliseconds. - -## PROCESS_file_descriptors - -This measurement contains the number of open file descriptors over time. The -value field `value` contains the number of descriptors. - -## PROCESS_gc_statistics - -This measurement contains Ruby garbage collection statistics such as the amount -of minor/major GC runs (relative to the last sampling interval), the time spent -in garbage collection cycles, and all fields/values returned by `GC.stat`. - -## PROCESS_memory_usage - -This measurement contains the process' memory usage (in bytes) over time. The -value field `value` contains the number of bytes. - -## PROCESS_method_calls - -This measurement contains the methods called during a transaction along with -their duration, and a name of the transaction action that invoked the method (if -available). The method call duration is stored in the value field `duration`, -while the method name is stored in the tag `method`. The tag `action` contains -the full name of the transaction action. Both the `method` and `action` fields -are in the following format: - -``` -ClassName#method_name -``` - -For example, a method called by the `show` method in the `UsersController` class -would have `action` set to `UsersController#show`. - -## PROCESS_object_counts - -This measurement is used to store retained Ruby objects (per class) and the -amount of retained objects. The number of objects is stored in the `count` value -field while the class name is stored in the `type` tag. - -## PROCESS_transactions - -This measurement is used to store basic transaction details such as the time it -took to complete a transaction, how much time was spent in SQL queries, etc. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The total duration of the transaction | -| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | -| `method_duration` | The total time spent in method calls | -| `sql_duration` | The total time spent in SQL queries | -| `view_duration` | The total time spent in views | - -## PROCESS_views - -This measurement is used to store view rendering timings for a transaction. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The rendering time of the view | -| `view` | The path of the view, relative to the application's root directory | - -The `action` tag contains the action name of the transaction that rendered the -view. - -## events - -This measurement is used to store generic events such as the number of Git -pushes, Emails sent, etc. Each point in this measurement has a single value -field called `count`. The value of this field is simply set to `1`. Each point -also has at least one tag: `event`. This tag's value is set to the event name. -Depending on the event type additional tags may be available as well. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/influxdb_schema](../administration/monitoring/performance/influxdb_schema.md). diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md index 79904916b7e..ab3f3ac1664 100644 --- a/doc/monitoring/performance/introduction.md +++ b/doc/monitoring/performance/introduction.md @@ -1,65 +1 @@ -# GitLab Performance Monitoring - -GitLab comes with its own application performance measuring system as of GitLab -8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the -Community and Enterprise editions. - -Apart from this introduction, you are advised to read through the following -documents in order to understand and properly configure GitLab Performance Monitoring: - -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Install/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -## Introduction to GitLab Performance Monitoring - -GitLab Performance Monitoring makes it possible to measure a wide variety of statistics -including (but not limited to): - -- The time it took to complete a transaction (a web request or Sidekiq job). -- The time spent in running SQL queries and rendering HAML views. -- The time spent executing (instrumented) Ruby methods. -- Ruby object allocations, and retained objects in particular. -- System statistics such as the process' memory usage and open file descriptors. -- Ruby garbage collection statistics. - -Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored -data can be visualized using [Grafana][grafana] or any other application that -supports reading data from InfluxDB. Alternatively data can be queried using the -InfluxDB CLI. - -## Metric Types - -Two types of metrics are collected: - -1. Transaction specific metrics. -1. Sampled metrics, collected at a certain interval in a separate thread. - -### Transaction Metrics - -Transaction metrics are metrics that can be associated with a single -transaction. This includes statistics such as the transaction duration, timings -of any executed SQL queries, time spent rendering HAML views, etc. These metrics -are collected for every Rack request and Sidekiq job processed. - -### Sampled Metrics - -Sampled metrics are metrics that can't be associated with a single transaction. -Examples include garbage collection statistics and retained Ruby objects. These -metrics are collected at a regular interval. This interval is made up out of two -parts: - -1. A user defined interval. -1. A randomly generated offset added on top of the interval, the same offset - can't be used twice in a row. - -The actual interval can be anywhere between a half of the defined interval and a -half above the interval. For example, for a user defined interval of 15 seconds -the actual interval can be anywhere between 7.5 and 22.5. The interval is -re-generated for every sampling run instead of being generated once and re-used -for the duration of the process' lifetime. - -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[grafana]: http://grafana.org/ +This document was moved to [administration/monitoring/performance/introduction](../administration/monitoring/performance/introduction.md). diff --git a/doc/operations/README.md b/doc/operations/README.md index 6a35dab7b6c..58f16aff7bd 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -1,5 +1 @@ -# GitLab operations - -- [Sidekiq MemoryKiller](sidekiq_memory_killer.md) -- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md) -- [Understanding Unicorn and unicorn-worker-killer](unicorn.md) +This document was moved to [administration/operations](../administration/operations.md). diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md index 93521e976d5..2a1d0a8c8eb 100644 --- a/doc/operations/cleaning_up_redis_sessions.md +++ b/doc/operations/cleaning_up_redis_sessions.md @@ -1,52 +1 @@ -# Cleaning up stale Redis sessions - -Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. -Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If -you have been running a large GitLab server (thousands of users) since before -GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis -database after you upgrade to GitLab 7.3. You can also perform a cleanup while -still running GitLab 7.2 or older, but in that case new stale sessions will -start building up again after you clean up. - -In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte -hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with -GitLab 7.3.0, the keys are -prefixed with 'session:gitlab:', so they would look like -'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to -remove the keys in the old format. - -First we define a shell function with the proper Redis connection details. - -``` -rcli() { - # This example works for Omnibus installations of GitLab 7.3 or newer. For an - # installation from source you will have to change the socket path and the - # path to redis-cli. - sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" -} - -# test the new shell function; the response should be PONG -rcli ping -``` - -Now we do a search to see if there are any session keys in the old format for -us to clean up. - -``` -# returns the number of old-format session keys in Redis -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l -``` - -If the number is larger than zero, you can proceed to expire the keys from -Redis. If the number is zero there is nothing to clean up. - -``` -# Tell Redis to expire each matched key after 600 seconds. -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli -# This will print '(integer) 1' for each key that gets expired. -``` - -Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis -background save interval) your Redis database will be compacted. If you are -still using GitLab 7.2, users who are not clicking around in GitLab during the -10 minute expiry window will be signed out of GitLab. +This document was moved to [administration/operations/cleaning_up_redis_sessions](../administration/operations/cleaning_up_redis_sessions.md). diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md index 54adb99386a..c54bca324a5 100644 --- a/doc/operations/moving_repositories.md +++ b/doc/operations/moving_repositories.md @@ -1,180 +1 @@ -# Moving repositories managed by GitLab - -Sometimes you need to move all repositories managed by GitLab to -another filesystem or another server. In this document we will look -at some of the ways you can copy all your repositories from -`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. - -We will look at three scenarios: the target directory is empty, the -target directory contains an outdated copy of the repositories, and -how to deal with thousands of repositories. - -**Each of the approaches we list can/will overwrite data in the -target directory `/mnt/gitlab/repositories`. Do not mix up the -source and the target.** - -## Target directory is empty: use a tar pipe - -If the target directory `/mnt/gitlab/repositories` is empty the -simplest thing to do is to use a tar pipe. This method has low -overhead and tar is almost always already installed on your system. -However, it is not possible to resume an interrupted tar pipe: if -that happens then all data must be copied again. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to see progress, replace `-xf` with `-xvf`. - -### Tar pipe to another server - -You can also use a tar pipe to copy data to another server. If your -'git' user has SSH access to the newserver as 'git@newserver', you -can pipe the data through SSH. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - ssh git@newserver tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to compress the data before it goes over the network -(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. - -## The target directory contains an outdated copy of the repositories: use rsync - -If the target directory already contains a partial / outdated copy -of the repositories it may be wasteful to copy all the data again -with tar. In this scenario it is better to use rsync. This utility -is either already installed on your system or easily installable -via apt, yum etc. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - /mnt/gitlab/repositories -``` - -The `/.` in the command above is very important, without it you can -easily get the wrong directory structure in the target directory. -If you want to see progress, replace `-a` with `-av`. - -### Single rsync to another server - -If the 'git' user on your source system has SSH access to the target -server you can send the repositories over the network with rsync. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - git@newserver:/mnt/gitlab/repositories -``` - -## Thousands of Git repositories: use one rsync per repository - -Every time you start an rsync job it has to inspect all files in -the source directory, all files in the target directory, and then -decide what files to copy or not. If the source or target directory -has many contents this startup phase of rsync can become a burden -for your GitLab server. In cases like this you can make rsync's -life easier by dividing its work in smaller pieces, and sync one -repository at a time. - -In addition to rsync we will use [GNU -Parallel](http://www.gnu.org/software/parallel/). This utility is -not included in GitLab so you need to install it yourself with apt -or yum. Also note that the GitLab scripts we used below were added -in GitLab 8.1. - -** This process does not clean up repositories at the target location that no -longer exist at the source. ** If you start using your GitLab instance with -`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` -after switching to the new repository storage directory. - -### Parallel rsync for all repositories known to GitLab - -This will sync repositories with 10 rsync processes at a time. We keep -track of progress so that the transfer can be restarted if necessary. - -First we create a new directory, owned by 'git', to hold transfer -logs. We assume the directory is empty before we start the transfer -procedure, and that we are the only ones writing files in it. - -``` -# Omnibus -sudo mkdir /var/opt/gitlab/transfer-logs -sudo chown git:git /var/opt/gitlab/transfer-logs - -# Source -sudo -u git -H mkdir /home/git/transfer-logs -``` - -We seed the process with a list of the directories we want to copy. - -``` -# Omnibus -sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' -``` - -Now we can start the transfer. The command below is idempotent, and -the number of jobs done by GNU Parallel should converge to zero. If it -does not some repositories listed in all-repos-1234.txt may have been -deleted/renamed before they could be copied. - -``` -# Omnibus -sudo -u git sh -c ' -cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories -' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c ' -cat /home/git/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - /home/git/transfer-logs/success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -` -``` - -### Parallel rsync only for repositories with recent activity - -Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. -Then you might only want to sync repositories that were changed via GitLab -_after_ that time. You can use the 'SINCE' variable to tell 'rake -gitlab:list_repos' to only print repositories with recent activity. - -``` -# Omnibus -sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git \ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories - -# Source -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git -H \ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -``` +This document was moved to [administration/operations/moving_repositories](../administration/operations/moving_repositories.md). diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md index b5e78348989..cf7c3b2e2ed 100644 --- a/doc/operations/sidekiq_memory_killer.md +++ b/doc/operations/sidekiq_memory_killer.md @@ -1,40 +1 @@ -# Sidekiq MemoryKiller - -The GitLab Rails application code suffers from memory leaks. For web requests -this problem is made manageable using -[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which -restarts Unicorn worker processes in between requests when needed. The Sidekiq -MemoryKiller applies the same approach to the Sidekiq processes used by GitLab -to process background jobs. - -Unlike unicorn-worker-killer, which is enabled by default for all GitLab -installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default -_only_ for Omnibus packages. The reason for this is that the MemoryKiller -relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab -installations from source do not all use Runit or an equivalent. - -With the default settings, the MemoryKiller will cause a Sidekiq restart no -more often than once every 15 minutes, with the restart causing about one -minute of delay for incoming background jobs. - -## Configuring the MemoryKiller - -The MemoryKiller is controlled using environment variables. - -- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is - greater than 0, then after each Sidekiq job, the MemoryKiller will check the - RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq - process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a - delayed shutdown is triggered. The default value for Omnibus packages is set - [in the omnibus-gitlab - repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). -- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When - a shutdown is triggered, the Sidekiq process will keep working normally for - another 15 minutes. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace - time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. - Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells - Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must - restart Sidekiq. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of - the final signal sent to the Sidekiq process when we want it to shut down. +This document was moved to [administration/operations/sidekiq_memory_killer](../administration/operations/sidekiq_memory_killer.md). diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md index bad61151bda..fbc9697b755 100644 --- a/doc/operations/unicorn.md +++ b/doc/operations/unicorn.md @@ -1,86 +1 @@ -# Understanding Unicorn and unicorn-worker-killer - -## Unicorn - -GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web -server, to handle web requests (web browsers and Git HTTP clients). Unicorn is -a daemon written in Ruby and C that can load and run a Ruby on Rails -application; in our case the Rails application is GitLab Community Edition or -GitLab Enterprise Edition. - -Unicorn has a multi-process architecture to make better use of available CPU -cores (processes can run on different cores) and to have stronger fault -tolerance (most failures stay isolated in only one process and cannot take down -GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby -environment with the GitLab application code, and then spawns 'workers' which -inherit this clean initial environment. The 'master' never handles any -requests, that is left to the workers. The operating system network stack -queues incoming requests and distributes them among the workers. - -In a perfect world, the master would spawn its pool of workers once, and then -the workers handle incoming web requests one after another until the end of -time. In reality, worker processes can crash or time out: if the master notices -that a worker takes too long to handle a request it will terminate the worker -process with SIGKILL ('kill -9'). No matter how the worker process ended, the -master process will replace it with a new 'clean' process again. Unicorn is -designed to be able to replace 'crashed' workers without dropping user -requests. - -This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The -master process has PID 56227 below. - -``` -[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing -[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10 -[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538 -[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready -``` - -### Tunables - -The main tunables for Unicorn are the number of worker processes and the -request timeout after which the Unicorn master terminates a worker process. -See the [omnibus-gitlab Unicorn settings -documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md) -if you want to adjust these settings. - -## unicorn-worker-killer - -GitLab has memory leaks. These memory leaks manifest themselves in long-running -processes, such as Unicorn workers. (The Unicorn master process is not known to -leak memory, probably because it does not handle user requests.) - -To make these memory leaks manageable, GitLab comes with the -[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This -gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn -workers to do a memory self-check after every 16 requests. If the memory of the -Unicorn worker exceeds a pre-set limit then the worker process exits. The -Unicorn master then automatically replaces the worker process. - -This is a robust way to handle memory leaks: Unicorn is designed to handle -workers that 'crash' so no user requests will be dropped. The -unicorn-worker-killer gem is designed to only terminate a worker process _in -between requests_, so no user requests are affected. - -This is what a Unicorn worker memory restart looks like in unicorn_stderr.log. -You see that worker 4 (PID 125918) is inspecting itself and decides to exit. -The threshold memory value was 254802235 bytes, about 250MB. With GitLab this -threshold is a random value between 200 and 250 MB. The master process (PID -117565) then reaps the worker process and spawns a new 'worker 4' with PID -127549. - -``` -[2015-06-05T12:07:41.828374 #125918] WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes) -[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1) -[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4 -[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549 -[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready -``` - -One other thing that stands out in the log snippet above, taken from -GitLab.com, is that 'worker 4' was serving requests for only 23 seconds. This -is a normal value for our current GitLab.com setup and traffic. - -The high frequency of Unicorn memory restarts on some GitLab sites can be a -source of confusion for administrators. Usually they are a [red -herring](https://en.wikipedia.org/wiki/Red_herring). +This document was moved to [administration/operations/unicorn](../administration/operations/unicorn.md). diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 8a5e2d6e16b..044b104f5c2 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -70,3 +70,18 @@ sudo gitlab-rake gitlab:two_factor:disable_for_all_users # installation from source bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production ``` + +## Clear authentication tokens for all users. Important! Data loss! + +Clear authentication tokens for all users in the GitLab database. This +task is useful if your users' authentication tokens might have been exposed in +any way. All the existing tokens will become invalid, and new tokens are +automatically generated upon sign-in or user modification. + +``` +# omnibus-gitlab +sudo gitlab-rake gitlab:users:clear_all_authentication_tokens + +# installation from source +bundle exec rake gitlab:users:clear_all_authentication_tokens RAILS_ENV=production +``` diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md new file mode 100644 index 00000000000..eac57bc3de4 --- /dev/null +++ b/doc/user/admin_area/monitoring/health_check.md @@ -0,0 +1,66 @@ +# Health Check + +> [Introduced][ce-3888] in GitLab 8.8. + +GitLab provides a health check endpoint for uptime monitoring on the `health_check` web +endpoint. The health check reports on the overall system status based on the status of +the database connection, the state of the database migrations, and the ability to write +and access the cache. This endpoint can be provided to uptime monitoring services like +[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. + +## Access Token + +An access token needs to be provided while accessing the health check endpoint. The current +accepted token can be found on the `admin/health_check` page of your GitLab instance. + +![access token](img/health_check_token.png) + +The access token can be passed as a URL parameter: + +``` +https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN +``` + +or as an HTTP header: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +## Using the Endpoint + +Once you have the access token, health information can be retrieved as plain text, JSON, +or XML using the `health_check` endpoint: + +- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` + +You can also ask for the status of specific services: + +- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` + +For example, the JSON output of the following health check: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +would be like: + +``` +{"healthy":true,"message":"success"} +``` + +## Status + +On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint +will return a valid successful HTTP status code, and a `success` message. Ideally your +uptime monitoring should look for the success message. + +[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 +[pingdom]: https://www.pingdom.com +[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html +[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/monitoring/img/health_check_token.png b/doc/user/admin_area/monitoring/img/health_check_token.png Binary files differindex 2d7c82a65a8..2d7c82a65a8 100644 --- a/doc/monitoring/img/health_check_token.png +++ b/doc/user/admin_area/monitoring/img/health_check_token.png diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c0dc80325b6..d6216a8dd50 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -32,6 +32,7 @@ The following table depicts the various user permission levels in a project. | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | | See environments | | ✓ | ✓ | ✓ | ✓ | +| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Manage/Accept merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | diff --git a/doc/user/project/repository/img/web_editor_new_branch_from_issue.png b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png Binary files differnew file mode 100644 index 00000000000..b0a63ddf0ab --- /dev/null +++ b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md index 993c6bfb7e9..675e89e4247 100644 --- a/doc/user/project/repository/web_editor.md +++ b/doc/user/project/repository/web_editor.md @@ -97,11 +97,11 @@ There are multiple ways to create a branch from GitLab's web interface. In case your development workflow dictates to have an issue for every merge request, you can quickly create a branch right on the issue page which will be -tied with the issue itself. You can see a **New Branch** button after the issue +tied with the issue itself. You can see a **New branch** button after the issue description, unless there is already a branch with the same name or a referenced merge request. -![New Branch Button](img/new_branch_from_issue.png) +![New Branch Button](img/web_editor_new_branch_from_issue.png) Once you click it, a new branch will be created that diverges from the default branch of your project, by default `master`. The branch name will be based on diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 4828bb5dce6..423b095e69e 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -4,6 +4,112 @@ Subversion (SVN) is a central version control system (VCS) while Git is a distributed version control system. There are some major differences between the two, for more information consult your favorite search engine. +## Overview + +There are two approaches to SVN to Git migration: + +1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: + - Makes the GitLab repository to mirror the SVN project. + - Git and SVN repositories are kept in sync; you can use either one. + - Smoothens the migration process and allows to manage migration risks. + +1. [Cut over migration](#cut-over-migration-with-svn2git) which: + - Translates and imports the existing data and history from SVN to Git. + - Is a fire and forget approach, good for smaller teams. + +## Smooth migration with a Git/SVN mirror using SubGit + +[SubGit](https://subgit.com) is a tool for a smooth, stress-free SVN to Git +migration. It creates a writable Git mirror of a local or remote Subversion +repository and that way you can use both Subversion and Git as long as you like. +It requires access to your GitLab server as it talks with the Git repositories +directly in a filesystem level. + +### SubGit prerequisites + +1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can + follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). +1. Download SubGit from https://subgit.com/download/. +1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit` + command will be available at `/opt/subgit-VERSION/bin/subgit`. + +### SubGit configuration + +The first step to mirror you SVN repository in GitLab is to create a new empty +project which will be used as a mirror. For Omnibus installations the path to +the repository will be located at +`/var/opt/gitlab/git-data/repositories/USER/REPO.git` by default. For +installations from source, the default repository directory will be +`/home/git/repositories/USER/REPO.git`. For convenience, assign this path to a +variable: + +``` +GIT_REPO_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git +``` + +SubGit will keep this repository in sync with a remote SVN project. For +convenience, assign your remote SVN project URL to a variable: + +``` +SVN_PROJECT_URL=http://svn.company.com/repos/project +``` + +Next you need to run SubGit to set up a Git/SVN mirror. Make sure the following +`subgit` command is ran on behalf of the same user that keeps ownership of +GitLab Git repositories (by default `git`): + +``` +subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPO_PATH +``` + +Adjust authors and branches mappings, if necessary. Open with your favorite +text editor: + +``` +edit $GIT_REPO_PATH/subgit/authors.txt +edit $GIT_REPO_PATH/subgit/config +``` + +For more information regarding the SubGit configuration options, refer to +[SubGit's documentation](https://subgit.com/documentation.html) website. + +### Initial translation + +Now that SubGit has configured the Git/SVN repos, run `subgit` to perform the +initial translation of existing SVN revisions into the Git repository: + +``` +subgit install $GIT_REPOS_PATH +``` + +After the initial translation is completed, the Git repository and the SVN +project will be kept in sync by `subgit` - new Git commits will be translated to +SVN revisions and new SVN revisions will be translated to Git commits. Mirror +works transparently and does not require any special commands. + +If you would prefer to perform one-time cut over migration with `subgit`, use +the `import` command instead of `install`: + +``` +subgit import $GIT_REPO_PATH +``` + +### SubGit licensing + +Running SubGit in a mirror mode requires a +[registration](https://subgit.com/pricing.html). Registration is free for open +source, academic and startup projects. + +We're currently working on deeper GitLab/SubGit integration. You may track our +progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). + +### SubGit support + +For any questions related to SVN to GitLab migration with SubGit, you can +contact the SubGit team directly at [support@subgit.com](mailto:support@subgit.com). + +## Cut over migration with svn2git + If you are currently using an SVN repository, you can migrate the repository to Git and GitLab. We recommend a hard cut over - run the migration command once and then have all developers start using the new GitLab repository immediately. @@ -75,5 +181,3 @@ git push --tags origin ## Contribute to this guide We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems. - - diff --git a/lib/api/api.rb b/lib/api/api.rb index 0bbf73a1b63..99722a0a65c 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -73,5 +73,9 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + + route :any, '*path' do + error!('404 Not Found', 404) + end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c24e8e8bd9b..da16e24d7ea 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -416,6 +416,12 @@ module API required_attributes! [:group_id, :group_access] attrs = attributes_for_keys [:group_id, :group_access, :expires_at] + group = Group.find_by_id(attrs[:group_id]) + + unless group && can?(current_user, :read_group, group) + not_found!('Group') + end + unless user_project.allowed_to_share_with_group? return render_api_error!("The project sharing with group is disabled", 400) end diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb index 4ef8b3b6dcf..e008fd428b0 100644 --- a/lib/banzai/filter/html_entity_filter.rb +++ b/lib/banzai/filter/html_entity_filter.rb @@ -3,7 +3,7 @@ require 'erb' module Banzai module Filter # Text filter that escapes these HTML entities: & " < > - class HTMLEntityFilter < HTML::Pipeline::TextFilter + class HtmlEntityFilter < HTML::Pipeline::TextFilter def call ERB::Util.html_escape(text) end diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb index 30bc035d085..1929099931b 100644 --- a/lib/banzai/pipeline/single_line_pipeline.rb +++ b/lib/banzai/pipeline/single_line_pipeline.rb @@ -3,7 +3,7 @@ module Banzai class SingleLinePipeline < GfmPipeline def self.filters @filters ||= FilterArray[ - Filter::HTMLEntityFilter, + Filter::HtmlEntityFilter, Filter::SanitizationFilter, Filter::EmojiFilter, diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index a4558d157c0..e4d996a3fb6 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -52,8 +52,7 @@ module ExtractsPath # Append a trailing slash if we only get a ref and no file path id += '/' unless id.ends_with?('/') - valid_refs = @project.repository.ref_names - valid_refs.select! { |v| id.start_with?("#{v}/") } + valid_refs = ref_names.select { |v| id.start_with?("#{v}/") } if valid_refs.length == 0 # No exact ref match, so just try our best @@ -74,6 +73,19 @@ module ExtractsPath pair end + # If we have an ID of 'foo.atom', and the controller provides Atom and HTML + # formats, then we have to check if the request was for the Atom version of + # the ID without the '.atom' suffix, or the HTML version of the ID including + # the suffix. We only check this if the version including the suffix doesn't + # match, so it is possible to create a branch which has an unroutable Atom + # feed. + def extract_ref_without_atom(id) + id_without_atom = id.sub(/\.atom$/, '') + valid_refs = ref_names.select { |v| "#{id_without_atom}/".start_with?("#{v}/") } + + valid_refs.max_by(&:length) + end + # Assigns common instance variables for views working with Git tree-ish objects # # Assignments are: @@ -86,6 +98,10 @@ module ExtractsPath # If the :id parameter appears to be requesting a specific response format, # that will be handled as well. # + # If there is no path and the ref doesn't exist in the repo, try to resolve + # the ref without an '.atom' suffix. If _that_ ref is found, set the request's + # format to Atom manually. + # # Automatically renders `not_found!` if a valid tree path could not be # resolved (e.g., when a user inserts an invalid path or ref). def assign_ref_vars @@ -103,6 +119,13 @@ module ExtractsPath @commit = @repo.commit(@options[:extended_sha1]) end + if @path.empty? && !@commit + @id = @ref = extract_ref_without_atom(@id) + @commit = @repo.commit(@ref) + + request.format = :atom if @commit + end + raise InvalidPathError unless @commit @hex_path = Digest::SHA1.hexdigest(@path) @@ -125,4 +148,10 @@ module ExtractsPath id += "/" + params[:path] unless params[:path].blank? id end + + def ref_names + return [] unless @project + + @ref_names ||= @project.repository.ref_names + end end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 5f4a6bbfa35..2ae48a970ce 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -671,7 +671,7 @@ namespace :gitlab do "Enable mail_room in the init.d configuration." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -690,7 +690,7 @@ namespace :gitlab do "Enable mail_room in your Procfile." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -747,7 +747,7 @@ namespace :gitlab do "Check that the information in config/gitlab.yml is correct" ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end diff --git a/lib/tasks/gitlab/users.rake b/lib/tasks/gitlab/users.rake new file mode 100644 index 00000000000..3a16ace60bd --- /dev/null +++ b/lib/tasks/gitlab/users.rake @@ -0,0 +1,11 @@ +namespace :gitlab do + namespace :users do + desc "GitLab | Clear the authentication token for all users" + task clear_all_authentication_tokens: :environment do |t, args| + # Do small batched updates because these updates will be slow and locking + User.select(:id).find_in_batches(batch_size: 100) do |batch| + User.where(id: batch.map(&:id)).update_all(authentication_token: nil) + end + end + end +end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb deleted file mode 100644 index 2b334ed1172..00000000000 --- a/spec/controllers/namespaces_controller_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'spec_helper' - -describe NamespacesController do - let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } - - describe "GET show" do - context "when the namespace belongs to a user" do - let!(:other_user) { create(:user) } - - it "redirects to the user's page" do - get :show, id: other_user.username - - expect(response).to redirect_to(user_path(other_user)) - end - end - - context "when the namespace belongs to a group" do - let!(:group) { create(:group) } - - context "when the group is public" do - context "when not signed in" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the group is private" do - before do - group.update_attribute(:visibility_level, Group::PRIVATE) - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: group.path - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - context "when the user has access to the group" do - before do - group.add_developer(user) - end - - context "when the user is blocked" do - before do - user.block - end - - it "redirects to the sign in page" do - get :show, id: group.path - - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when the user isn't blocked" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the user doesn't have access to the group" do - it "responds with status 404" do - get :show, id: group.path - - expect(response).to have_http_status(404) - end - end - end - end - end - - context "when the namespace doesn't exist" do - context "when signed in" do - before do - sign_in(user) - end - - it "responds with status 404" do - get :show, id: "doesntexist" - - expect(response).to have_http_status(404) - end - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: "doesntexist" - - expect(response).to redirect_to(new_user_session_path) - end - end - end - end -end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 2518a48e336..1ac7e03a2db 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -10,15 +10,38 @@ describe Projects::CommitsController do end describe "GET show" do - context "as atom feed" do - it "renders as atom" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: "master", - format: "atom") - expect(response).to be_success - expect(response.content_type).to eq('application/atom+xml') + context "when the ref name ends in .atom" do + render_views + + context "when the ref does not exist with the suffix" do + it "renders as atom" do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + + expect(response).to be_success + expect(response.content_type).to eq('application/atom+xml') + end + end + + context "when the ref exists with the suffix" do + before do + commit = project.repository.commit('master') + + allow_any_instance_of(Repository).to receive(:commit).and_call_original + allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit) + + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + end + + it "renders as HTML" do + expect(response).to be_success + expect(response.content_type).to eq('text/html') + end end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 742edd8ba3d..84298f8bef4 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -708,4 +708,52 @@ describe Projects::MergeRequestsController do end end end + + describe 'POST assign_related_issues' do + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + + def post_assign_issues + merge_request.update!(description: "Closes #{issue1.to_reference} and #{issue2.to_reference}", + author: user, + source_branch: 'feature', + target_branch: 'master') + + post :assign_related_issues, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid + end + + it 'shows a flash message on success' do + post_assign_issues + + expect(flash[:notice]).to eq '2 issues have been assigned to you' + end + + it 'correctly pluralizes flash message on success' do + issue2.update!(assignee: user) + + post_assign_issues + + expect(flash[:notice]).to eq '1 issue has been assigned to you' + end + + it 'calls MergeRequests::AssignIssuesService' do + expect(MergeRequests::AssignIssuesService).to receive(:new). + with(project, user, merge_request: merge_request). + and_return(double(execute: { count: 1 })) + + post_assign_issues + end + + it 'is skipped when not signed in' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + sign_out(:user) + + expect(MergeRequests::AssignIssuesService).not_to receive(:new) + + post_assign_issues + end + end end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index a6995145cc1..5e661c2c41d 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,4 +17,18 @@ describe Projects::TagsController do expect(assigns(:releases)).not_to include(invalid_release) end end + + describe 'GET show' do + before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id } + + context "valid tag" do + let(:id) { 'v1.0.0' } + it { is_expected.to respond_with(:success) } + end + + context "invalid tag" do + let(:id) { 'latest' } + it { is_expected.to respond_with(:not_found) } + end + end end diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb new file mode 100644 index 00000000000..43cc6f2a2a7 --- /dev/null +++ b/spec/features/merge_requests/assign_issues_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +feature 'Merge request issue assignment', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") } + let(:service) { MergeRequests::AssignIssuesService.new(merge_request, user, user, project) } + + before do + project.team << [user, :developer] + end + + def visit_merge_request(current_user = nil) + login_as(current_user || user) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'logged in as author' do + scenario 'updates related issues' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content "2 issues have been assigned to you" + end + + it 'returns user to the merge request' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content merge_request.description + end + + it "doesn't display if related issues are already assigned" do + [issue1, issue2].each { |issue| issue.update!(assignee: user) } + + visit_merge_request + + expect(page).not_to have_content "Assign yourself" + end + end + + context 'not MR author' do + it "doesn't not show assignment link" do + visit_merge_request(create(:user)) + + expect(page).not_to have_content "Assign yourself" + end + end +end diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb new file mode 100644 index 00000000000..c22441f8929 --- /dev/null +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe "Guest navigation menu" do + let(:project) { create :empty_project, :private } + let(:guest) { create :user } + + before do + project.team << [guest, :guest] + + login_as(guest) + end + + it "shows allowed tabs only" do + visit namespace_project_path(project.namespace, project) + + within(".nav-links") do + expect(page).to have_content 'Project' + expect(page).to have_content 'Activity' + expect(page).to have_content 'Issues' + expect(page).to have_content 'Wiki' + + expect(page).not_to have_content 'Repository' + expect(page).not_to have_content 'Pipelines' + expect(page).not_to have_content 'Graphs' + expect(page).not_to have_content 'Merge Requests' + end + end +end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index ccb5c06dab0..79417c769a8 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -203,7 +203,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for developer } it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb deleted file mode 100644 index cfe15b9defa..00000000000 --- a/spec/finders/trending_projects_finder_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe TrendingProjectsFinder do - let(:user) { create(:user) } - let(:public_project1) { create(:empty_project, :public) } - let(:public_project2) { create(:empty_project, :public) } - let(:private_project) { create(:empty_project, :private) } - let(:internal_project) { create(:empty_project, :internal) } - - before do - 3.times do - create(:note_on_commit, project: public_project1) - end - - 2.times do - create(:note_on_commit, project: public_project2, created_at: 5.weeks.ago) - end - - create(:note_on_commit, project: private_project) - create(:note_on_commit, project: internal_project) - end - - describe '#execute', caching: true do - context 'without an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute - - expect(projects).to eq([public_project1]) - end - end - - context 'with an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute(2) - - expect(projects).to eq([public_project1, public_project2]) - end - end - - it 'caches the list of projects' do - projects = described_class.new - - expect(Project).to receive(:trending).once - - 2.times { projects.execute } - end - end -end diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb index 6dc4a970071..4c68ce6d6e4 100644 --- a/spec/lib/banzai/filter/html_entity_filter_spec.rb +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Banzai::Filter::HTMLEntityFilter, lib: true do +describe Banzai::Filter::HtmlEntityFilter, lib: true do include FilterSpecHelper let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' } diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index e10c1f5c547..0e85e302f29 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -6,6 +6,7 @@ describe ExtractsPath, lib: true do include Gitlab::Routing.url_helpers let(:project) { double('project') } + let(:request) { double('request') } before do @project = project @@ -15,9 +16,10 @@ describe ExtractsPath, lib: true do allow(project).to receive(:repository).and_return(repo) allow(project).to receive(:path_with_namespace). and_return('gitlab/gitlab-ci') + allow(request).to receive(:format=) end - describe '#assign_ref' do + describe '#assign_ref_vars' do let(:ref) { sample_commit[:id] } let(:params) { { path: sample_commit[:line_code_path], ref: ref } } @@ -61,6 +63,75 @@ describe ExtractsPath, lib: true do expect(@id).to eq(get_id) end end + + context 'ref only exists without .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'v1.0.0.atom', path: 'README.md' } } + + it 'renders a 404' do + expect(self).to receive(:render_404) + + assign_ref_vars + end + end + + context 'without a path' do + let(:params) { { ref: 'v1.0.0.atom' } } + before { assign_ref_vars } + + it 'sets the un-suffixed version as @ref' do + expect(@ref).to eq('v1.0.0') + end + + it 'sets the request format to Atom' do + expect(request).to have_received(:format=).with(:atom) + end + end + end + + context 'ref exists with .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'master.atom', path: 'README.md' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + + assign_ref_vars + end + + it 'sets the suffixed version as @ref' do + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to have_received(:format=) + end + end + + context 'without a path' do + let(:params) { { ref: 'master.atom' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + end + + it 'sets the suffixed version as @ref' do + assign_ref_vars + + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to receive(:format=) + + assign_ref_vars + end + end + end end describe '#extract_ref' do @@ -115,4 +186,18 @@ describe ExtractsPath, lib: true do end end end + + describe '#extract_ref_without_atom' do + it 'ignores any matching refs suffixed with atom' do + expect(extract_ref_without_atom('master.atom')).to eq('master') + end + + it 'returns the longest matching ref' do + expect(extract_ref_without_atom('release/app/v1.0.0.atom')).to eq('release/app/v1.0.0') + end + + it 'returns nil if there are no matching refs' do + expect(extract_ref_without_atom('foo.atom')).to eq(nil) + end + end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index af5002487cc..06cac929bbf 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -135,6 +135,17 @@ describe Event, models: true do it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(guest)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true } + + context 'private project' do + let(:project) { create(:project, :private) } + + it { expect(event.visible_to_user?(non_member)).to eq false } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(guest)).to eq false } + it { expect(event.visible_to_user?(admin)).to eq true } + end end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index fd4a2beff58..7fc6ed1dd54 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -5,9 +5,6 @@ describe Key, models: true do it { is_expected.to belong_to(:user) } end - describe "Mass assignment" do - end - describe "Validation" do it { is_expected.to validate_presence_of(:title) } it { is_expected.to validate_presence_of(:key) } diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index 5e6f8ca1528..c18ed8574b1 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe LabelLink, models: true do - let(:label) { create(:label_link) } - it { expect(label).to be_valid } + it { expect(build(:label_link)).to be_valid } it { is_expected.to belong_to(:label) } it { is_expected.to belong_to(:target) } diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..96f1f60dbc0 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -44,6 +44,16 @@ describe MergeRequestDiff, models: true do end end + context 'when the raw diffs have invalid content' do + before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + + it 'returns an empty DiffCollection' do + expect(mr_diff.raw_diffs.to_a).to be_empty + expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.raw_diffs).to be_empty + end + end + context 'when the raw diffs exist' do it 'returns the diffs' do expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 2fa6715fcaf..c5ff1941378 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -11,7 +11,7 @@ describe ProjectGroupLink do it { should validate_presence_of(:project_id) } it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } - it { should validate_presence_of(:group_id) } + it { should validate_presence_of(:group) } it { should validate_presence_of(:group_access) } end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8aadfcb439b..dae546a0cdc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -800,32 +800,14 @@ describe Project, models: true do end create(:note_on_commit, project: project2) - end - - describe 'without an explicit start date' do - subject { described_class.trending.to_a } - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project1, project2]) - end + TrendingProject.refresh! end - describe 'with an explicit start date' do - let(:date) { 2.months.ago } + subject { described_class.trending.to_a } - subject { described_class.trending(date).to_a } - - before do - 2.times do - # Little fix for special issue related to Fractional Seconds support for MySQL. - # See: https://github.com/rails/rails/pull/14359/files - create(:note_on_commit, project: project2, created_at: date + 1) - end - end - - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project2, project1]) - end + it 'sorts projects by the amount of notes in descending order' do + expect(subject).to eq([project1, project2]) end it 'does not take system notes into account' do diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb new file mode 100644 index 00000000000..cc28c6d4004 --- /dev/null +++ b/spec/models/trending_project_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe TrendingProject do + let(:user) { create(:user) } + let(:public_project1) { create(:empty_project, :public) } + let(:public_project2) { create(:empty_project, :public) } + let(:public_project3) { create(:empty_project, :public) } + let(:private_project) { create(:empty_project, :private) } + let(:internal_project) { create(:empty_project, :internal) } + + before do + 3.times do + create(:note_on_commit, project: public_project1) + end + + 2.times do + create(:note_on_commit, project: public_project2) + end + + create(:note_on_commit, project: public_project3, created_at: 5.weeks.ago) + create(:note_on_commit, project: private_project) + create(:note_on_commit, project: internal_project) + end + + describe '.refresh!' do + before do + described_class.refresh! + end + + it 'populates the trending projects table' do + expect(described_class.count).to eq(2) + end + + it 'removes existing rows before populating the table' do + described_class.refresh! + + expect(described_class.count).to eq(2) + end + + it 'stores the project IDs for every trending project' do + rows = described_class.order(id: :asc).all + + expect(rows[0].project_id).to eq(public_project1.id) + expect(rows[1].project_id).to eq(public_project2.id) + end + + it 'does not store projects that fall out of the trending time range' do + expect(described_class.where(project_id: public_project3).any?).to eq(false) + end + + it 'stores only public projects' do + expect(described_class.where(project_id: [public_project1.id, public_project2.id]).count).to eq(2) + expect(described_class.where(project_id: [private_project.id, internal_project.id]).count).to eq(0) + end + end +end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 43c8d884a47..658e3c13a73 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -12,7 +12,7 @@ describe ProjectPolicy, models: true do [ :read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label, :read_milestone, :read_project_snippet, :read_project_member, - :read_merge_request, :read_note, :create_project, :create_issue, :create_note, + :read_note, :create_project, :create_issue, :create_note, :upload_file ] end @@ -21,7 +21,8 @@ describe ProjectPolicy, models: true do [ :download_code, :fork_project, :create_project_snippet, :update_issue, :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, - :read_container_image, :read_pipeline, :read_environment, :read_deployment + :read_container_image, :read_pipeline, :read_environment, :read_deployment, + :read_merge_request ] end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 765dc8a8f66..cfcdcad74cd 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -163,9 +163,10 @@ describe API::API, 'ProjectHooks', api: true do expect(response).to have_http_status(404) end - it "returns a 405 error if hook id not given" do + it "returns a 404 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(404) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..19a2c7a2700 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -819,6 +819,20 @@ describe API::API, api: true do expect(response.status).to eq 400 end + it 'returns a 404 error when user cannot read group' do + private_group = create(:group, :private) + + post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + + it 'returns a 404 error when group does not exist' do + post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + it "returns a 409 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 expect(response.status).to eq 409 diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f4ea3bebb4c..b002949b41b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -90,8 +90,9 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 404 if invalid ID" do + it "returns a 404 for invalid ID" do get api("/users/1ASDF", user) + expect(response).to have_http_status(404) end end @@ -340,8 +341,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 404 if invalid ID" do + put api("/users/ASDF", admin) + + expect(response).to have_http_status(404) end it 'returns 400 error if user does not validate' do @@ -493,8 +496,9 @@ describe API::API, api: true do end.to change{ user.emails.count }.by(1) end - it "raises error for invalid ID" do + it "returns a 400 for invalid ID" do post api("/users/999999/emails", admin) + expect(response).to have_http_status(400) end end @@ -525,9 +529,10 @@ describe API::API, api: true do expect(json_response.first['email']).to eq(email.email) end - it "raises error for invalid ID" do + it "returns a 404 for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(404) end end end @@ -566,8 +571,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Email Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF/emails/bar", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 404 for invalid ID" do + delete api("/users/ASDF/emails/bar", admin) + + expect(response).to have_http_status(404) end end end @@ -600,8 +607,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 404 for invalid ID" do + delete api("/users/ASDF", admin) + + expect(response).to have_http_status(404) end end @@ -654,6 +663,7 @@ describe API::API, api: true do it "returns 404 Not Found within invalid ID" do get api("/user/keys/42", user) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -669,6 +679,7 @@ describe API::API, api: true do it "returns 404 for invalid ID" do get api("/users/keys/ASDF", admin) + expect(response).to have_http_status(404) end end @@ -727,8 +738,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/keys/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 404 for invalid ID" do + delete api("/users/keys/ASDF", admin) + + expect(response).to have_http_status(404) end end @@ -778,6 +791,7 @@ describe API::API, api: true do it "returns 404 for invalid ID" do get api("/users/emails/ASDF", admin) + expect(response).to have_http_status(404) end end @@ -825,8 +839,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/emails/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 404 for invalid ID" do + delete api("/users/emails/ASDF", admin) + + expect(response).to have_http_status(404) end end @@ -891,8 +907,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 404 for invalid ID" do + put api("/users/ASDF/block", admin) + + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c0c1e62e910..413d06715b3 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -412,10 +412,9 @@ describe 'Git HTTP requests', lib: true do context "when the params are anything else" do let(:params) { { service: 'git-implode-pack' } } - before { get path, params } - it "redirects to the sign-in page" do - expect(response).to redirect_to(new_user_session_path) + it "fails to find a route" do + expect { get(path, params) }.to raise_error(ActionController::RoutingError) end end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 77842057a10..2322430d212 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -337,7 +337,7 @@ describe Projects::CommitsController, 'routing' do end it 'to #show' do - expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'atom') + expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 0dd00af878d..0ee1c811dfb 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -266,7 +266,9 @@ describe "Groups", "routing" do end it "also display group#show on the short path" do - expect(get('/1')).to route_to('namespaces#show', id: '1') + allow(Group).to receive(:find_by_path).and_return(true) + + expect(get('/1')).to route_to('groups#show', id: '1') end end diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb new file mode 100644 index 00000000000..7aeb95a15ea --- /dev/null +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe MergeRequests::AssignIssuesService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue.to_reference}") } + let(:service) { described_class.new(project, user, merge_request: merge_request) } + + before do + project.team << [user, :developer] + end + + it 'finds unassigned issues fixed in merge request' do + expect(service.assignable_issues.map(&:id)).to include(issue.id) + end + + it 'ignores issues already assigned to any user' do + issue.update!(assignee: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'ignores issues the user cannot update assignee on' do + project.team.truncate + + expect(service.assignable_issues).to be_empty + end + + it 'ignores all issues unless current_user is merge_request.author' do + merge_request.update!(author: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'accepts precomputed data for closes_issues' do + issue2 = create(:issue, project: project) + service2 = described_class.new(project, + user, + merge_request: merge_request, + closes_issues: [issue, issue2]) + + expect(service2.assignable_issues.count).to eq 2 + end + + it 'assigns these to the merge request owner' do + expect { service.execute }.to change { issue.reload.assignee }.to(user) + end +end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 0d586e2216b..3a3f07ddcb9 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -52,12 +52,28 @@ describe MergeRequests::BuildService, services: true do end end - context 'no commits in the diff' do - let(:commits) { [] } + context 'same source and target branch' do + let(:source_branch) { 'master' } it 'forbids the merge request from being created' do expect(merge_request.can_be_created).to eq(false) end + + it 'adds an error message to the merge request' do + expect(merge_request.errors).to contain_exactly('You must select different branches') + end + end + + context 'no commits in the diff' do + let(:commits) { [] } + + it 'allows the merge request to be created' do + expect(merge_request.can_be_created).to eq(true) + end + + it 'adds a WIP prefix to the merge request title' do + expect(merge_request.title).to eq('WIP: Feature branch') + end end context 'one commit in the diff' do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 33db34c0f62..fd5f94047c2 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -17,6 +17,7 @@ describe MergeRequests::UpdateService, services: true do before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do @@ -188,6 +189,11 @@ describe MergeRequests::UpdateService, services: true do let!(:non_subscriber) { create(:user) } let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } } + before do + project.team << [non_subscriber, :developer] + project.team << [subscriber, :developer] + end + it 'sends notifications for subscribers of newly added labels' do opts = { label_ids: [label.id] } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d820646ebdf..699b9925b4e 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -331,7 +331,7 @@ describe NotificationService, services: true do describe '#new_note' do it "records sent notifications" do # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note - expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original + expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(3).times.and_call_original notification.new_note(note) @@ -1169,6 +1169,61 @@ describe NotificationService, services: true do end end + context 'guest user in private project' do + let(:private_project) { create(:empty_project, :private) } + let(:guest) { create(:user) } + let(:developer) { create(:user) } + let(:assignee) { create(:user) } + let(:merge_request) { create(:merge_request, source_project: private_project, assignee: assignee) } + let(:merge_request1) { create(:merge_request, source_project: private_project, assignee: assignee, description: "cc @#{guest.username}") } + let(:note) { create(:note, noteable: merge_request, project: private_project) } + + before do + private_project.team << [assignee, :developer] + private_project.team << [developer, :developer] + private_project.team << [guest, :guest] + + ActionMailer::Base.deliveries.clear + end + + it 'filters out guests when new note is created' do + expect(SentNotification).to receive(:record).with(merge_request, any_args).exactly(1).times + + notification.new_note(note) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when new merge request is created' do + notification.new_merge_request(merge_request1, @u_disabled) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is closed' do + notification.close_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is reopened' do + notification.reopen_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is merged' do + notification.merge_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index ae4d286d250..b57e338b782 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -86,6 +86,25 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple label command' do + it 'fetches label ids and populates add_label_ids if content contains multiple /label' do + bug # populate the label + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id, bug.id]) + end + end + + shared_examples 'multiple label with same argument' do + it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id]) + end + end + shared_examples 'unlabel command' do it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -95,6 +114,15 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple unlabel command' do + it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do + issuable.update(label_ids: [inprogress.id, bug.id]) # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id]) + end + end + shared_examples 'unlabel command with no argument' do it 'populates label_ids: [] if content contains /unlabel with no arguments' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -285,6 +313,16 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple label command' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) } + let(:issuable) { issue } + end + + it_behaves_like 'multiple label with same argument' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{inprogress.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command' do let(:content) { %(/unlabel ~"#{inprogress.title}") } let(:issuable) { issue } @@ -295,6 +333,11 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}" \n/unlabel ~#{bug.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command with no argument' do let(:content) { %(/unlabel) } let(:issuable) { issue } diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index b41f6f14fbd..ed55791d24e 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -345,7 +345,7 @@ describe TodoService, services: true do service.new_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -357,7 +357,7 @@ describe TodoService, services: true do service.update_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -381,6 +381,7 @@ describe TodoService, services: true do should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) end it 'does not raise an error when description not change' do @@ -430,6 +431,11 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED) end + + it 'does not create a todo for guests' do + service.reassigned_merge_request(mr_assigned, author) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#merge_merge_request' do @@ -441,6 +447,11 @@ describe TodoService, services: true do expect(first_todo.reload).to be_done expect(second_todo.reload).to be_done end + + it 'does not create todo for guests' do + service.merge_merge_request(mr_assigned, john_doe) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#new_award_emoji' do @@ -495,6 +506,13 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) end + + it 'does not create todo for guests' do + note_on_merge_request = create :note_on_merge_request, project: project, noteable: mr_assigned, note: mentions + service.new_note(note_on_merge_request, author) + + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end end diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb new file mode 100644 index 00000000000..e6ebef82b78 --- /dev/null +++ b/spec/tasks/gitlab/users_rake_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:users namespace rake task' do + let(:enable_registry) { true } + + before :all do + Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/users' + + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + def run_rake_task(task_name) + Rake::Task[task_name].reenable + Rake.application.invoke_task task_name + end + + describe 'clear_all_authentication_tokens' do + before do + # avoid writing task output to spec progress + allow($stdout).to receive :write + end + + context 'gitlab version' do + it 'clears the authentication token for all users' do + create_list(:user, 2) + + expect(User.pluck(:authentication_token)).to all(be_present) + + run_rake_task('gitlab:users:clear_all_authentication_tokens') + + expect(User.pluck(:authentication_token)).to all(be_nil) + end + end + end +end diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb new file mode 100644 index 00000000000..c3c6fdcf2d5 --- /dev/null +++ b/spec/workers/trending_projects_worker_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe TrendingProjectsWorker do + describe '#perform' do + it 'refreshes the trending projects' do + expect(TrendingProject).to receive(:refresh!) + + described_class.new.perform + end + end +end |