diff options
118 files changed, 1680 insertions, 719 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ddc3e7063..3423e810c2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,77 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) + +- Backups do not fail anymore when using tar on annex and custom_hooks only. !5814 +- Adds user project membership expired event to clarify why user was removed (Callum Dryden) +- Trim leading and trailing whitespace on project_path (Linus Thiel) +- Prevent award emoji via notes for issues/MRs authored by user (barthc) +- Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO) +- Fix Markdown styling inside reference links (Jan Zdráhal) +- Fix extra space on Build sidebar on Firefox !7060 +- Fail gracefully when creating merge request with non-existing branch (alexsanford) +- Fix mobile layout issues in admin user overview page !7087 +- Fix HipChat notifications rendering (airatshigapov, eisnerd) +- Refactor Jira service to use jira-ruby gem +- Improved todos empty state +- Add hover to trash icon in notes !7008 (blackst0ne) +- Hides project activity tabs when features are disabled +- Only show one error message for an invalid email !5905 (lycoperdon) +- Fix sidekiq stats in admin area (blackst0ne) +- Added label description as tooltip to issue board list title +- Created cycle analytics bundle JavaScript file +- API: Fix booleans not recognized as such when using the `to_boolean` helper +- Removed delete branch tooltip !6954 +- Stop unauthorized users dragging on milestone page (blackst0ne) +- Restore issue boards welcome message when a project is created !6899 +- Do not show tooltip for active element !7105 (winniehell) +- Escape ref and path for relative links !6050 (winniehell) +- Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose) +- Fix filtering of milestones with quotes in title (airatshigapov) +- Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison) +- Update mail_room and enable sentinel support to Reply By Email (!7101) +- Add task completion status in Issues and Merge Requests tabs: "X of Y tasks completed" (!6527, @gmesalazar) +- Simpler arguments passed to named_route on toggle_award_url helper method +- Fix typo in framework css class. !7086 (Daniel Voogsgerd) +- New issue board list dropdown stays open after adding a new list +- Fix: Backup restore doesn't clear cache +- Optimize Event queries by removing default order +- API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh) +- Add job for removal of unreferenced LFS objects from both the database and the filesystem (Frank Groeneveld) +- Replace jquery.cookie plugin with js.cookie !7085 +- Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method +- Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens +- Show full status link on MR & commit pipelines +- Fix documents and comments on Build API `scope` +- Initialize Sidekiq with the list of queues used by GitLab +- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov) +- Shortened merge request modal to let clipboard button not overlap +- In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo) +- Improve search query parameter naming in /admin/users !7115 (YarNayar) +- Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar) + +## 8.13.3 + +- Fix relative links in Markdown wiki when displayed in "Project" tab !7218 +- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project +- Fix project features default values + +## 8.13.2 (2016-10-31) + +- Fix encoding issues on pipeline commits. !6832 +- Use Hash rocket syntax to fix cycle analytics under Ruby 2.1. !6977 +- Modify GitHub importer to be retryable. !7003 +- Fix refs dropdown selection with special characters. !7061 +- Fix horizontal padding for highlight blocks. !7062 +- Pass user instance to `Labels::FindOrCreateService` or `skip_authorization: true`. !7093 +- Fix builds dropdown overlapping bug. !7124 +- Fix applying labels for GitHub-imported MRs. !7139 +- Fix importing MR comments from GitHub. !7139 +- Fix project member access for group links. !7144 +- API: Fix booleans not recognized as such when using the `to_boolean` helper. !7149 +- Fix and improve `Sortable.highest_label_priority`. !7165 +- Fixed sticky merge request tabs when sidebar is pinned. !7167 +- Only remove right connector of first build of last stage. !7179 - Backups do not fail anymore when using tar on annex and custom_hooks only. !5814 - Adds user project membership expired event to clarify why user was removed (Callum Dryden) - Trim leading and trailing whitespace on project_path (Linus Thiel) @@ -48,172 +119,175 @@ Please view this file on the master branch, on stable branches it's out of date. - Fixed sticky merge request tabs when sidebar is pinned ## 8.13.1 (2016-10-25) - - Fix branch protection API. !6215 - - Fix hidden pipeline graph on commit and MR page. !6895 - - Fix Cycle analytics not showing correct data when filtering by date. !6906 - - Ensure custom provider tab labels don't break layout. !6993 - - Fix issue boards user link when in subdirectory. !7018 - - Refactor and add new environment functionality to CI yaml reference. !7026 - - Fix typo in project settings that prevents users from enabling container registry. !7037 - - Fix events order in `users/:id/events` endpoint. !7039 - - Remove extra line for empty issue description. !7045 - - Don't append issue/MR templates to any existing text. !7050 - - Fix error in generating labels. !7055 - - Stop clearing the database cache on `rake cache:clear`. !7056 - - Only show register tab if signup enabled. !7058 - - Expire and build repository cache after project import. !7064 - - Fix bug where labels would be assigned to issues that were moved. !7065 - - Fix reply-by-email not working due to queue name mismatch. !7068 - - Fix 404 for group pages when GitLab setup uses relative url. !7071 - - Fix `User#to_reference`. !7088 - - Reduce overhead of `LabelFinder` by avoiding `#presence` call. !7094 - - Fix unauthorized users dragging on issue boards. !7096 - - Only schedule `ProjectCacheWorker` jobs when needed. !7099 + +- Fix branch protection API. !6215 +- Fix hidden pipeline graph on commit and MR page. !6895 +- Fix Cycle analytics not showing correct data when filtering by date. !6906 +- Ensure custom provider tab labels don't break layout. !6993 +- Fix issue boards user link when in subdirectory. !7018 +- Refactor and add new environment functionality to CI yaml reference. !7026 +- Fix typo in project settings that prevents users from enabling container registry. !7037 +- Fix events order in `users/:id/events` endpoint. !7039 +- Remove extra line for empty issue description. !7045 +- Don't append issue/MR templates to any existing text. !7050 +- Fix error in generating labels. !7055 +- Stop clearing the database cache on `rake cache:clear`. !7056 +- Only show register tab if signup enabled. !7058 +- Fix lightweight tags not processed correctly by GitTagPushService +- Expire and build repository cache after project import. !7064 +- Fix bug where labels would be assigned to issues that were moved. !7065 +- Fix reply-by-email not working due to queue name mismatch. !7068 +- Fix 404 for group pages when GitLab setup uses relative url. !7071 +- Fix `User#to_reference`. !7088 +- Reduce overhead of `LabelFinder` by avoiding `#presence` call. !7094 +- Fix unauthorized users dragging on issue boards. !7096 +- Only schedule `ProjectCacheWorker` jobs when needed. !7099 ## 8.13.0 (2016-10-22) - - Fix save button on project pipeline settings page. (!6955) - - All Sidekiq workers now use their own queue - - Avoid race condition when asynchronously removing expired artifacts. (!6881) - - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - - Respond with 404 Not Found for non-existent tags (Linus Thiel) - - Truncate long labels with ellipsis in labels page - - Improve tabbing usability for sign in page (ClemMakesApps) - - Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint - - Adding members no longer silently fails when there is extra whitespace - - Update runner version only when updating contacted_at - - Add link from system note to compare with previous version - - Use gitlab-shell v3.6.6 - - Ignore references to internal issues when using external issues tracker - - Ability to resolve merge request conflicts with editor !6374 - - Add `/projects/visible` API endpoint (Ben Boeckel) - - Fix centering of custom header logos (Ashley Dumaine) - - Keep around commits only pipeline creation as pipeline data doesn't change over time - - Update duration at the end of pipeline - - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - - Add group level labels. (!6425) - - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - - Cancelled pipelines could be retried. !6927 - - Updating verbiage on git basics to be more intuitive - - Fix project_feature record not generated on project creation - - Clarify documentation for Runners API (Gennady Trafimenkov) - - Use optimistic locking for pipelines and builds - - The instrumentation for Banzai::Renderer has been restored - - Change user & group landing page routing from /u/:username to /:username - - Added documentation for .gitattributes files - - Move Pipeline Metrics to separate worker - - 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) - - ProjectCacheWorker updates caches at most once per 15 minutes per project - - Fix Error 500 when viewing old merge requests with bad diff data - - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - - Fix viewing merged MRs when the source project has been removed !6991 - - Speed-up group milestones show page - - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService - - Fix discussion thread from emails for merge requests. !7010 - - 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 - - Close open tooltips on page navigation (Linus Thiel) - - Allow browsing branches that end with '.atom' - - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) - - Add more tests for calendar contribution (ClemMakesApps) - - Update Gitlab Shell to fix some problems with moving projects between storages - - Cache rendered markdown in the database, rather than Redis - - Add todo toggle event (ClemMakesApps) - - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - - Simplify Mentionable concern instance methods - - API: Ability to retrieve version information (Robert Schilling) - - Fix permission for setting an issue's due date - - API: Multi-file commit !6096 (mahcsig) - - Unicode emoji are now converted to images - - 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 - - Add new issue button to each list on Issues Board - - Execute specific named route method from toggle_award_url helper method - - Added soft wrap button to repository file/blob editor - - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - - Show the time ago a merge request was deployed to an environment - - Add RTL support to markdown renderer (Ebrahim Byagowi) - - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - - Fix todos page mobile viewport layout (ClemMakesApps) - - Make issues search less finicky - - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - - Remove redundant mixins (ClemMakesApps) - - Added 'Download' button to the Snippets page (Justin DiPierro) - - Add visibility level to project repository - - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - - Fix showing commits from source project for merge request !6658 - - Fix that manual jobs would no longer block jobs in the next stage. !6604 - - Add configurable email subject suffix (Fu Xu) - - Use defined colour for a language when available !6748 (nilsding) - - Added tooltip to fork count on project show page. (Justin DiPierro) - - Use a ConnectionPool for Rails.cache on Sidekiq servers - - Replace `alias_method_chain` with `Module#prepend` - - Enable GitLab Import/Export for non-admin users. - - Preserve label filters when sorting !6136 (Joseph Frazier) - - MergeRequest#new form load diff asynchronously - - Only update issuable labels if they have been changed - - Take filters in account in issuable counters. !6496 - - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - - Replace static issue fixtures by script !6059 (winniehell) - - Append issue template to existing description !6149 (Joseph Frazier) - - Trending projects now only show public projects and the list of projects is cached for a day - - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) - - Revoke button in Applications Settings underlines on hover. - - Use higher size on Gitlab::Redis connection pool on Sidekiq servers - - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - - Revert avoid touching file system on Build#artifacts? - - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - - Add disabled delete button to protected branches (ClemMakesApps) - - Add broadcast messages and alerts below sub-nav - - Better empty state for Groups view - - API: New /users/:id/events endpoint - - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - - Add organization field to user profile - - Change user pages routing from /u/:username/PATH to /users/:username/PATH. Old routes will redirect to the new ones for the time being. - - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - - Fix deploy status responsiveness error !6633 - - Make searching for commits case insensitive - - Fix resolved discussion display in side-by-side diff view !6575 - - Optimize GitHub importing for speed and memory - - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - - Notify the Merger about merge after successful build (Dimitris Karakasilis) - - Reduce queries needed to find users using their SSH keys when pushing commits - - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - - Fix broken repository 500 errors in project list - - Fix the diff in the merge request view when converting a symlink to a regular file - - Fix Pipeline list commit column width should be adjusted - - 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) - - Add Nofollow for uppercased scheme in external urls !6820 (the-undefined) - - Allow empty merge requests !6384 (Artem Sidorenko) - - Grouped pipeline dropdown is a scrollable container - - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - - Fixes padding in all clipboard icons that have .btn class - - Fix a typo in doc/api/labels.md - - Fix double-escaping in activities tab (Alexandre Maia) - - API: all unknown routing will be handled with 404 Not Found - - Add docs for request profiling - - Delete dynamic environments - - Fix buggy iOS tooltip layering behavior. - - Make guests unable to view MRs on private projects - - Fix broken Project API docs (Takuya Noguchi) - - Migrate invalid project members (owner -> master) + +- Fix save button on project pipeline settings page. (!6955) +- All Sidekiq workers now use their own queue +- Avoid race condition when asynchronously removing expired artifacts. (!6881) +- Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) +- Respond with 404 Not Found for non-existent tags (Linus Thiel) +- Truncate long labels with ellipsis in labels page +- Improve tabbing usability for sign in page (ClemMakesApps) +- Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint +- Adding members no longer silently fails when there is extra whitespace +- Update runner version only when updating contacted_at +- Add link from system note to compare with previous version +- Use gitlab-shell v3.6.6 +- Ignore references to internal issues when using external issues tracker +- Ability to resolve merge request conflicts with editor !6374 +- Add `/projects/visible` API endpoint (Ben Boeckel) +- Fix centering of custom header logos (Ashley Dumaine) +- Keep around commits only pipeline creation as pipeline data doesn't change over time +- Update duration at the end of pipeline +- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup +- Add group level labels. (!6425) +- Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) +- Cancelled pipelines could be retried. !6927 +- Updating verbiage on git basics to be more intuitive +- Fix project_feature record not generated on project creation +- Clarify documentation for Runners API (Gennady Trafimenkov) +- Use optimistic locking for pipelines and builds +- The instrumentation for Banzai::Renderer has been restored +- Change user & group landing page routing from /u/:username to /:username +- Added documentation for .gitattributes files +- Move Pipeline Metrics to separate worker +- 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) +- ProjectCacheWorker updates caches at most once per 15 minutes per project +- Fix Error 500 when viewing old merge requests with bad diff data +- Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) +- Fix viewing merged MRs when the source project has been removed !6991 +- Speed-up group milestones show page +- Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) +- Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService +- Fix discussion thread from emails for merge requests. !7010 +- 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 +- Close open tooltips on page navigation (Linus Thiel) +- Allow browsing branches that end with '.atom' +- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) +- Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) +- Add more tests for calendar contribution (ClemMakesApps) +- Update Gitlab Shell to fix some problems with moving projects between storages +- Cache rendered markdown in the database, rather than Redis +- Add todo toggle event (ClemMakesApps) +- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references +- Simplify Mentionable concern instance methods +- API: Ability to retrieve version information (Robert Schilling) +- Fix permission for setting an issue's due date +- API: Multi-file commit !6096 (mahcsig) +- Unicode emoji are now converted to images +- 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 +- Add new issue button to each list on Issues Board +- Execute specific named route method from toggle_award_url helper method +- Added soft wrap button to repository file/blob editor +- Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) +- Show the time ago a merge request was deployed to an environment +- Add RTL support to markdown renderer (Ebrahim Byagowi) +- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) +- Fix todos page mobile viewport layout (ClemMakesApps) +- Make issues search less finicky +- Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) +- Remove redundant mixins (ClemMakesApps) +- Added 'Download' button to the Snippets page (Justin DiPierro) +- Add visibility level to project repository +- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) +- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) +- Fix showing commits from source project for merge request !6658 +- Fix that manual jobs would no longer block jobs in the next stage. !6604 +- Add configurable email subject suffix (Fu Xu) +- Use defined colour for a language when available !6748 (nilsding) +- Added tooltip to fork count on project show page. (Justin DiPierro) +- Use a ConnectionPool for Rails.cache on Sidekiq servers +- Replace `alias_method_chain` with `Module#prepend` +- Enable GitLab Import/Export for non-admin users. +- Preserve label filters when sorting !6136 (Joseph Frazier) +- MergeRequest#new form load diff asynchronously +- Only update issuable labels if they have been changed +- Take filters in account in issuable counters. !6496 +- Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) +- Replace static issue fixtures by script !6059 (winniehell) +- Append issue template to existing description !6149 (Joseph Frazier) +- Trending projects now only show public projects and the list of projects is cached for a day +- Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) +- Revoke button in Applications Settings underlines on hover. +- Use higher size on Gitlab::Redis connection pool on Sidekiq servers +- Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) +- Revert avoid touching file system on Build#artifacts? +- Stop using a Redis lease when updating the project activity timestamp whenever a new event is created +- Add disabled delete button to protected branches (ClemMakesApps) +- Add broadcast messages and alerts below sub-nav +- Better empty state for Groups view +- API: New /users/:id/events endpoint +- Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) +- Replace bootstrap caret with fontawesome caret (ClemMakesApps) +- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 +- Add organization field to user profile +- Change user pages routing from /u/:username/PATH to /users/:username/PATH. Old routes will redirect to the new ones for the time being. +- Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) +- Fix deploy status responsiveness error !6633 +- Make searching for commits case insensitive +- Fix resolved discussion display in side-by-side diff view !6575 +- Optimize GitHub importing for speed and memory +- API: expose pipeline data in builds API (!6502, Guilherme Salazar) +- Notify the Merger about merge after successful build (Dimitris Karakasilis) +- Reduce queries needed to find users using their SSH keys when pushing commits +- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) +- Fix broken repository 500 errors in project list +- Fix the diff in the merge request view when converting a symlink to a regular file +- Fix Pipeline list commit column width should be adjusted +- 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) +- Add Nofollow for uppercased scheme in external urls !6820 (the-undefined) +- Allow empty merge requests !6384 (Artem Sidorenko) +- Grouped pipeline dropdown is a scrollable container +- Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) +- Fixes padding in all clipboard icons that have .btn class +- Fix a typo in doc/api/labels.md +- Fix double-escaping in activities tab (Alexandre Maia) +- API: all unknown routing will be handled with 404 Not Found +- Add docs for request profiling +- Delete dynamic environments +- Fix buggy iOS tooltip layering behavior. +- Make guests unable to view MRs on private projects +- Fix broken Project API docs (Takuya Noguchi) +- Migrate invalid project members (owner -> master) ## 8.12.7 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 4f2c1d15f6d..fcdb2e109f6 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.6.6 +4.0.0 @@ -51,7 +51,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.6.8' +gem 'gitlab_git', '~> 10.7.0' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index f8116e1894f..81af1ad2dac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -283,7 +283,7 @@ GEM mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) gitlab-markup (1.5.0) - gitlab_git (10.6.8) + gitlab_git (10.7.0) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -870,7 +870,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.5.0) - gitlab_git (~> 10.6.8) + gitlab_git (~> 10.7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) @@ -998,4 +998,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.13.3 + 1.13.5 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e57cf1b3a58..7dd9adac736 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,6 +1,6 @@ /* eslint-disable */ // This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// Add new JavaScript code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. @@ -188,6 +188,7 @@ // Close select2 on escape }); // Initialize tooltips + $.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover'; $body.tooltip({ selector: '.has-tooltip, [data-toggle="tooltip"]', placement: function(_, el) { diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js index 48490869364..a64cefb62bd 100644 --- a/app/assets/javascripts/behaviors/details_behavior.js +++ b/app/assets/javascripts/behaviors/details_behavior.js @@ -15,6 +15,11 @@ return $("body").on("click", ".js-details-expand", function(e) { $(this).next('.js-details-content').removeClass("hide"); $(this).hide(); + + var truncatedItem = $(this).siblings('.js-details-short'); + if (truncatedItem.length) { + truncatedItem.addClass("hide"); + } return e.preventDefault(); }); }); diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 index e520170ef74..db9a5a8e40a 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -22,7 +22,7 @@ fallbackClass: 'is-dragging', fallbackOnBody: true, ghostClass: 'is-ghost', - filter: '.has-tooltip, .btn', + filter: '.board-delete, .btn', delay: gl.issueBoards.touchEnabled ? 100 : 50, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSpeed: 20, diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js index 056baf66525..e103748d499 100644 --- a/app/assets/javascripts/graphs/graphs_bundle.js +++ b/app/assets/javascripts/graphs/graphs_bundle.js @@ -1,6 +1,6 @@ /* eslint-disable */ // This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// Add new JavaScript code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js index ede72a96d76..42d6799c82f 100644 --- a/app/assets/javascripts/network/network_bundle.js +++ b/app/assets/javascripts/network/network_bundle.js @@ -1,6 +1,6 @@ /* eslint-disable */ // This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// Add new JavaScript code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index a18d16ea46c..cfd1e2204d5 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -11,11 +11,9 @@ $this.parent().find('.star-count').text(data.star_count); if (isStarred) { $starSpan.removeClass('starred').text('Star'); - gl.utils.updateTooltipTitle($this, 'Star project'); $starIcon.removeClass('fa-star').addClass('fa-star-o'); } else { $starSpan.addClass('starred').text('Unstar'); - gl.utils.updateTooltipTitle($this, 'Unstar project'); $starIcon.removeClass('fa-star-o').addClass('fa-star'); } }; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index c0e9c8bf829..ed21ad83a1c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -216,7 +216,7 @@ svg, .fa { &:not(:last-child) { - margin-right: 3px; + margin-right: 5px; } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b271f8cf332..be2a7ceefff 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -90,6 +90,8 @@ $table-border-color: #f0f0f0; $background-color: $gray-light; $dark-background-color: #f5f5f5; $table-text-gray: #8f8f8f; +$widget-expand-item: #e8f2f7; +$widget-inner-border: #eef0f2; /* * Text diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 9282e0ae03b..486ad16ea26 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -1,7 +1,7 @@ .awards { .emoji-icon { - width: 20px; - height: 20px; + width: 19px; + height: 19px; } } @@ -94,7 +94,7 @@ .award-control { margin: 3px 5px 3px 0; - padding: 6px 5px; + padding: 5px 6px; outline: 0; &:hover, @@ -127,7 +127,7 @@ .award-control-icon { float: left; margin-right: 5px; - font-size: 20px; + font-size: 19px; } .award-control-icon-loading { diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 8ecac08137b..8ecf7fcb96d 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -33,10 +33,8 @@ &.commit-info-row-header { line-height: 34px; - - @media (min-width: $screen-sm-min) { - margin-bottom: 0; - } + padding: 10px 0; + margin-bottom: 0; .commit-options-dropdown-caret { @media (max-width: $screen-sm) { @@ -80,6 +78,58 @@ } } +.js-details-expand { + &:hover { + text-decoration: none; + } +} + +.commit-info-widget { + background: $background-color; + color: $gl-gray; + border: 1px solid $border-color; + border-radius: $border-radius-default; + + .widget-row { + padding: $gl-padding; + + &:not(:last-of-type) { + border-bottom: 1px solid $widget-inner-border; + } + + &.branch-info { + .monospace, + .commit-info { + margin-left: 4px; + } + } + } + + .icon-container { + display: inline-block; + margin-right: 8px; + + svg { + position: relative; + top: 2px; + height: 16px; + width: 16px; + } + + &.commit-icon { + svg { + path { + fill: $gl-text-color; + } + } + } + } + + .label.label-gray { + background-color: $widget-expand-item; + } +} + .ci-status-link { svg { overflow: visible; @@ -88,6 +138,7 @@ .commit-box { border-top: 1px solid $border-color; + padding: $gl-padding 0; .commit-title { margin: 0; @@ -138,6 +189,9 @@ } .commit-action-buttons { + position: relative; + top: -1px; + i { color: $gl-icon-color; font-size: 13px; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index ad315cfae62..52d6a39bd59 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -33,21 +33,22 @@ color: $gl-dark-link-color; } - .text-expander { - display: inline-block; - background: $gray-light; - color: $gl-placeholder-color; - padding: 0 5px; - cursor: pointer; - border: 1px solid $border-gray-dark; - border-radius: $border-radius-default; - margin-left: 5px; - line-height: 1; - - &:hover { - background-color: darken($gray-light, 10%); - text-decoration: none; - } +} + +.text-expander { + display: inline-block; + background: $gray-light; + color: $gl-placeholder-color; + padding: 0 5px; + cursor: pointer; + border: 1px solid $border-gray-dark; + border-radius: $border-radius-default; + margin-left: 5px; + line-height: 1; + + &:hover { + background-color: darken($gray-light, 10%); + text-decoration: none; } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 3d2b024fe5c..a2f5c6c6bd3 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -54,7 +54,6 @@ margin: 0 0 10px; } - .login-footer { margin-top: 10px; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f510e3d3cdf..f8e31a624ec 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -60,7 +60,7 @@ } .ci_widget { - border-bottom: 1px solid #eef0f2; + border-bottom: 1px solid $widget-inner-border; svg { margin-right: 4px; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index b90c91831f2..526e9ae5cdd 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -105,11 +105,6 @@ ul.notes { padding: 2px; margin-top: 10px; } - - .award-control { - font-size: 13px; - padding: 2px 5px; - } } .note-header { diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index ea76fe18876..b3aef2fdd32 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -161,3 +161,63 @@ } } } + +.todos-empty { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; + max-width: 900px; + margin-left: auto; + margin-right: auto; + + @media (min-width: $screen-sm-min) { + -webkit-flex-direction: row; + flex-direction: row; + padding-top: 80px; + } +} + +.todos-empty-content { + -webkit-align-self: center; + align-self: center; + max-width: 480px; + margin-right: 20px; +} + +.todos-empty-hero { + width: 200px; + margin-left: auto; + margin-right: auto; + + @media (min-width: $screen-sm-min) { + width: 300px; + margin-right: 0; + -webkit-order: 2; + order: 2; + } +} + +.todos-all-done { + padding-top: 20px; + + @media (min-width: $screen-sm-min) { + padding-top: 50px; + } + + > svg { + display: block; + max-width: 300px; + margin: 0 auto 20px; + } + + p { + max-width: 470px; + margin-left: auto; + margin-right: auto; + } + + a { + font-weight: 600; + } +} diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index f35f4a8c811..bb912ed10cc 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -3,7 +3,7 @@ class Admin::UsersController < Admin::ApplicationController def index @users = User.order_name_asc.filter(params[:filter]) - @users = @users.search(params[:name]) if params[:name].present? + @users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present? @users = @users.sort(@sort = params[:sort]) @users = @users.page(params[:page]) end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 4f855134368..42fd09e9b7e 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -126,7 +126,7 @@ class Projects::LabelsController < Projects::ApplicationController alias_method :subscribable_resource, :label def find_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities) + @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute end def authorize_admin_labels! diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d08f490de18..699a56ae2f8 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -25,18 +25,15 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def create - if params[:user_ids].blank? - return redirect_to(namespace_project_project_members_path(@project.namespace, @project), alert: 'No users or groups specified.') - end + status = Members::CreateService.new(@project, current_user, params).execute - @project.team.add_users( - params[:user_ids].split(','), - params[:access_level], - expires_at: params[:expires_at], - current_user: current_user - ) + redirect_url = namespace_project_project_members_path(@project.namespace, @project) - redirect_to namespace_project_project_members_path(@project.namespace, @project), notice: 'Users were successfully added.' + if status + redirect_to redirect_url, notice: 'Users were successfully added.' + else + redirect_to redirect_url, alert: 'No users or groups specified.' + end end def update diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 8fea20cefef..953091492ae 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -23,7 +23,7 @@ class Projects::TagsController < Projects::ApplicationController return render_404 unless @tag @release = @project.releases.find_or_initialize_by(tag: @tag.name) - @commit = @repository.commit(@tag.target) + @commit = @repository.commit(@tag.dereferenced_target) end def create diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 8c148ecfaeb..bce5e29d8d8 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -289,7 +289,8 @@ class ProjectsController < Projects::ApplicationController render 'projects/empty' if @project.empty_repo? else if @project.wiki_enabled? - @wiki_home = @project.wiki.find_page('home', params[:version_id]) + @project_wiki = @project.wiki + @wiki_home = @project_wiki.find_page('home', params[:version_id]) elsif @project.feature_available?(:issues, current_user) @issues = issues_collection @issues = @issues.page(params[:page]) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index e27986ef95b..cc2073081b5 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -126,7 +126,7 @@ class IssuableFinder @labels = if labels? && !filter_by_no_label? - LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute + LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true) else Label.none end @@ -273,7 +273,7 @@ class IssuableFinder items = items.with_label(label_names, params[:sort]) if projects - label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id) + label_ids = LabelsFinder.new(current_user, project_ids: projects).execute(skip_authorization: true).select(:id) items = items.where(labels: { id: label_ids }) end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 85e1dc33ee8..dee3c78df47 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -16,13 +16,14 @@ module ButtonHelper # See http://clipboardjs.com/#usage def clipboard_button(data = {}) css_class = data[:class] || 'btn-clipboard btn-transparent' + title = data[:title] || 'Copy to Clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), class: "btn #{css_class}", data: data, type: :button, - title: 'Copy to Clipboard' + title: title end def http_clone_button(project, placement = 'right', append_link: true) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index f8ded05c31a..00e64076408 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -39,6 +39,12 @@ module EventsHelper end end + def event_filter_visible(feature_key) + return true unless @project + + @project.feature_available?(feature_key, current_user) + end + def event_preposition(event) if event.push? || event.commented? || event.target "at" diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d26b4018be6..42c00ec3cd5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -174,10 +174,14 @@ module ProjectsHelper nav_tabs << :merge_requests end - if can?(current_user, :read_build, project) + if can?(current_user, :read_pipeline, project) nav_tabs << :pipelines end + if can?(current_user, :read_build, project) + nav_tabs << :builds + end + if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) nav_tabs << :container_registry end diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb index 9216122923e..6d88951c713 100644 --- a/app/models/concerns/project_features_compatibility.rb +++ b/app/models/concerns/project_features_compatibility.rb @@ -31,7 +31,7 @@ module ProjectFeaturesCompatibility def write_feature_attribute(field, value) build_project_feature unless project_feature - access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED + access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED project_feature.update_attribute(field, access_level) end end diff --git a/app/models/event.rb b/app/models/event.rb index 3993b35f96d..43e67069b70 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,6 +1,6 @@ class Event < ActiveRecord::Base include Sortable - default_scope { where.not(author_id: nil) } + default_scope { reorder(nil).where.not(author_id: nil) } CREATED = 1 UPDATED = 2 diff --git a/app/models/group_label.rb b/app/models/group_label.rb index a698b532d19..68841ace2e6 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -5,6 +5,10 @@ class GroupLabel < Label alias_attribute :subject, :group + def subject_foreign_key + 'group_id' + end + def to_reference(source_project = nil, target_project = nil, format: :id) super(source_project, target_project, format: format) end diff --git a/app/models/label.rb b/app/models/label.rb index 149fd98ecb3..d9287f2dc29 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -92,16 +92,23 @@ class Label < ActiveRecord::Base nil end - def open_issues_count(user = nil, project = nil) - issues_count(user, project_id: project.try(:id) || project_id, state: 'opened') + def open_issues_count(user = nil) + issues_count(user, state: 'opened') end - def closed_issues_count(user = nil, project = nil) - issues_count(user, project_id: project.try(:id) || project_id, state: 'closed') + def closed_issues_count(user = nil) + issues_count(user, state: 'closed') end - def open_merge_requests_count(user = nil, project = nil) - merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened') + def open_merge_requests_count(user = nil) + params = { + subject_foreign_key => subject.id, + label_name: title, + scope: 'all', + state: 'opened' + } + + MergeRequestsFinder.new(user, params.with_indifferent_access).execute.count end def prioritize!(project, value) @@ -167,15 +174,8 @@ class Label < ActiveRecord::Base end def issues_count(user, params = {}) - IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) - .execute - .count - end - - def merge_requests_count(user, params = {}) - MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) - .execute - .count + params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all') + IssuesFinder.new(user, params.with_indifferent_access).execute.count end def label_format_reference(format = :id) diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 18657c3e1c8..7712d5783e0 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -17,4 +17,10 @@ class LfsObject < ActiveRecord::Base def project_allowed_access?(project) projects.exists?(storage_project(project).id) end + + def self.destroy_unreferenced + joins("LEFT JOIN lfs_objects_projects ON lfs_objects_projects.lfs_object_id = #{table_name}.id") + .where(lfs_objects_projects: { id: nil }) + .destroy_all + end end diff --git a/app/models/project.rb b/app/models/project.rb index ae57815c620..d5512dfaf9c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -30,6 +30,11 @@ class Project < ActiveRecord::Base default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for(:repository_storage) { current_application_settings.repository_storage } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } + default_value_for :issues_enabled, gitlab_config_features.issues + default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests + default_value_for :builds_enabled, gitlab_config_features.builds + default_value_for :wiki_enabled, gitlab_config_features.wiki + default_value_for :snippets_enabled, gitlab_config_features.snippets after_create :ensure_dir_exist after_create :create_project_feature, unless: :project_feature @@ -390,7 +395,7 @@ class Project < ActiveRecord::Base end def group_ids - joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id) + joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id) end end diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 33c2b617715..82f47f0e8fd 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -12,6 +12,10 @@ class ProjectLabel < Label alias_attribute :subject, :project + def subject_foreign_key + 'project_id' + end + def to_reference(target_project = nil, format: :id) super(project, target_project, format: format) end diff --git a/app/models/repository.rb b/app/models/repository.rb index e2b0093859d..30be7262438 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -195,7 +195,7 @@ class Repository before_remove_branch branch = find_branch(branch_name) - oldrev = branch.try(:target).try(:id) + oldrev = branch.try(:dereferenced_target).try(:id) newrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name @@ -311,10 +311,10 @@ class Repository # Rugged seems to throw a `ReferenceError` when given branch_names rather # than SHA-1 hashes number_commits_behind = raw_repository. - count_commits_between(branch.target.sha, root_ref_hash) + count_commits_between(branch.dereferenced_target.sha, root_ref_hash) number_commits_ahead = raw_repository. - count_commits_between(root_ref_hash, branch.target.sha) + count_commits_between(root_ref_hash, branch.dereferenced_target.sha) { behind: number_commits_behind, ahead: number_commits_ahead } end @@ -696,11 +696,11 @@ class Repository branches.sort_by(&:name) when 'updated_desc' branches.sort do |a, b| - commit(b.target).committed_date <=> commit(a.target).committed_date + commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date end when 'updated_asc' branches.sort do |a, b| - commit(a.target).committed_date <=> commit(b.target).committed_date + commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date end else branches @@ -875,7 +875,7 @@ class Repository branch = find_branch(ref) if branch - last_commit = branch.target + last_commit = branch.dereferenced_target index.read_tree(last_commit.raw_commit.tree) parents = [last_commit.sha] end @@ -962,7 +962,7 @@ class Repository end def revert(user, commit, base_branch, revert_tree_id = nil) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha revert_tree_id ||= check_revert_content(commit, base_branch) return false unless revert_tree_id @@ -979,7 +979,7 @@ class Repository end def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) return false unless cherry_pick_tree_id @@ -1008,7 +1008,7 @@ class Repository end def check_revert_content(commit, base_branch) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha args = [commit.id, source_sha] args << { mainline: 1 } if commit.merge_commit? @@ -1022,7 +1022,7 @@ class Repository end def check_cherry_pick_content(commit, base_branch) - source_sha = find_branch(base_branch).target.sha + source_sha = find_branch(base_branch).dereferenced_target.sha args = [commit.id, source_sha] args << 1 if commit.merge_commit? @@ -1095,7 +1095,7 @@ class Repository if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? oldrev = Gitlab::Git::BLANK_SHA else - oldrev = rugged.merge_base(newrev, target_branch.target.sha) + oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha) end GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do @@ -1155,7 +1155,7 @@ class Repository end def tags_sorted_by_committed_date - tags.sort_by { |tag| tag.target.committed_date } + tags.sort_by { |tag| tag.dereferenced_target.committed_date } end def keep_around_ref_name(sha) diff --git a/app/models/user.rb b/app/models/user.rb index e2a97c3a757..af3c0b7dc02 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -258,6 +258,24 @@ class User < ActiveRecord::Base ) end + # searches user by given pattern + # it compares name, email, username fields and user's secondary emails with given pattern + # This method uses ILIKE on PostgreSQL and LIKE on MySQL. + + def search_with_secondary_emails(query) + table = arel_table + email_table = Email.arel_table + pattern = "%#{query}%" + matched_by_emails_user_ids = email_table.project(email_table[:user_id]).where(email_table[:email].matches(pattern)) + + where( + table[:name].matches(pattern). + or(table[:email].matches(pattern)). + or(table[:username].matches(pattern)). + or(table[:id].in(matched_by_emails_user_ids)) + ) + end + def by_login(login) return nil unless login diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index 918eddaa53a..3e5dd4ebb86 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -42,7 +42,7 @@ class DeleteBranchService < BaseService Gitlab::DataBuilder::Push.build( project, current_user, - branch.target.sha, + branch.dereferenced_target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index d0cb151a010..d824406cb49 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -36,7 +36,7 @@ class DeleteTagService < BaseService Gitlab::DataBuilder::Push.build( project, current_user, - tag.target.sha, + tag.dereferenced_target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index e6002b03b93..20a4445bddf 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -27,8 +27,8 @@ class GitTagPushService < BaseService tag_name = Gitlab::Git.ref_name(params[:ref]) tag = project.repository.find_tag(tag_name) - if tag && tag.object_sha == params[:newrev] - commit = project.commit(tag.target) + if tag && tag.target == params[:newrev] + commit = project.commit(tag.dereferenced_target) commits = [commit].compact message = tag.message end diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb new file mode 100644 index 00000000000..e4b24ccef92 --- /dev/null +++ b/app/services/members/create_service.rb @@ -0,0 +1,16 @@ +module Members + class CreateService < BaseService + def execute + return false if params[:user_ids].blank? + + project.team.add_users( + params[:user_ids].split(','), + params[:access_level], + expires_at: params[:expires_at], + current_user: current_user + ) + + true + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 404f75616b5..f415244068b 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -13,20 +13,8 @@ module MergeRequests merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_branch ||= merge_request.target_project.default_branch - if merge_request.target_branch.blank? || merge_request.source_branch.blank? - message = - if params[:source_branch] || params[:target_branch] - "You must select source and target branch" - end - - 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 + messages = validate_branches(merge_request) + return build_failed(merge_request, messages) unless messages.empty? compare = CompareService.new.execute( merge_request.source_project, @@ -43,6 +31,34 @@ module MergeRequests private + def validate_branches(merge_request) + messages = [] + + if merge_request.target_branch.blank? || merge_request.source_branch.blank? + messages << + if params[:source_branch] || params[:target_branch] + "You must select source and target branch" + end + end + + if merge_request.source_project == merge_request.target_project && + merge_request.target_branch == merge_request.source_branch + + messages << 'You must select different branches' + end + + # See if source and target branches exist + unless merge_request.source_project.commit(merge_request.source_branch) + messages << "Source branch \"#{merge_request.source_branch}\" does not exist" + end + + unless merge_request.target_project.commit(merge_request.target_branch) + messages << "Target branch \"#{merge_request.target_branch}\" does not exist" + end + + messages + end + # When your branch name starts with an iid followed by a dash this pattern will be # interpreted as the user wants to close that issue on this project. # @@ -91,8 +107,10 @@ module MergeRequests merge_request end - def build_failed(merge_request, message) - merge_request.errors.add(:base, message) unless message.nil? + def build_failed(merge_request, messages) + messages.compact.each do |message| + merge_request.errors.add(:base, message) + end merge_request.compare_commits = [] merge_request.can_be_created = false merge_request diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 357123c2c13..d3038ae644f 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -10,7 +10,7 @@ = hidden_field_tag "filter", h(params[:filter]) .search-holder .search-field-holder - = search_field_tag :name, params[:name], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false + = search_field_tag :search_query, params[:search_query], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false = icon("search", class: "search-icon") .dropdown - toggle_text = if @sort.present? then sort_options_hash[@sort] else sort_title_name end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 2a0302638ba..2411cc45724 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -1,69 +1,70 @@ - page_title "Todos" - header_title "Todos", dashboard_todos_path -.top-area - %ul.nav-links - - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending') - %li{class: "todos-pending #{todo_pending_active}"} - = link_to todos_filter_path(state: 'pending') do - %span - To do - %span.badge - = number_with_delimiter(todos_pending_count) - - todo_done_active = ('active' if params[:state] == 'done') - %li{class: "todos-done #{todo_done_active}"} - = link_to todos_filter_path(state: 'done') do - %span - Done - %span.badge - = number_with_delimiter(todos_done_count) +- if current_user.todos.any? + .top-area + %ul.nav-links + - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending') + %li{class: "todos-pending #{todo_pending_active}"} + = link_to todos_filter_path(state: 'pending') do + %span + To do + %span.badge + = number_with_delimiter(todos_pending_count) + - todo_done_active = ('active' if params[:state] == 'done') + %li{class: "todos-done #{todo_done_active}"} + = link_to todos_filter_path(state: 'done') do + %span + Done + %span.badge + = number_with_delimiter(todos_done_count) - .nav-controls - - if @todos.any?(&:pending?) - = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do - Mark all as done - = icon('spinner spin') + .nav-controls + - if @todos.any?(&:pending?) + = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do + Mark all as done + = icon('spinner spin') -.todos-filters - .row-content-block.second-block - = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do - .filter-item.inline - - if params[:project_id].present? - = hidden_field_tag(:project_id, params[:project_id]) - = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', - placeholder: 'Search projects', data: { data: todo_projects_options } }) - .filter-item.inline - - if params[:author_id].present? - = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', - placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) - .filter-item.inline - - if params[:type].present? - = hidden_field_tag(:type, params[:type]) - = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', - data: { data: todo_types_options } }) - .filter-item.inline.actions-filter - - if params[:action_id].present? - = hidden_field_tag(:action_id, params[:action_id]) - = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', - data: { data: todo_actions_options }}) - .pull-right - .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_created - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort - %li - = link_to todos_filter_path(sort: sort_value_priority) do - = sort_title_priority - = link_to todos_filter_path(sort: sort_value_recently_created) do + .todos-filters + .row-content-block.second-block + = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do + .filter-item.inline + - if params[:project_id].present? + = hidden_field_tag(:project_id, params[:project_id]) + = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', + placeholder: 'Search projects', data: { data: todo_projects_options } }) + .filter-item.inline + - if params[:author_id].present? + = hidden_field_tag(:author_id, params[:author_id]) + = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', + placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) + .filter-item.inline + - if params[:type].present? + = hidden_field_tag(:type, params[:type]) + = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', + data: { data: todo_types_options } }) + .filter-item.inline.actions-filter + - if params[:action_id].present? + = hidden_field_tag(:action_id, params[:action_id]) + = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', + data: { data: todo_actions_options }}) + .pull-right + .dropdown.inline.prepend-left-10 + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else = sort_title_recently_created - = link_to todos_filter_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort + %li + = link_to todos_filter_path(sort: sort_value_priority) do + = sort_title_priority + = link_to todos_filter_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to todos_filter_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created .prepend-top-default @@ -78,5 +79,29 @@ %ul.content-list.todos-list = render group[1] = paginate @todos, theme: "gitlab" + - elsif current_user.todos.any? + .todos-all-done + = render "shared/empty_states/todos_all_done.svg" + %h4.text-center + Good job! Looks like you don't have any todos left. + %p.text-center + Are you looking for things to do? Take a look at + = succeed "," do + = link_to "the opened issues", issues_dashboard_path + contribute to + = link_to "merge requests", merge_requests_dashboard_path + or mention someone in a comment to assign a new todo automatically. - else - .nothing-here-block You're all done! + .todos-empty + .todos-empty-hero + = render "shared/empty_states/todos_empty.svg" + .todos-empty-content + %h4 + Todos let you see what you should do next. + %p + When an issue or merge request is assigned to you, or when you + %strong + @mention + in a comment, this will trigger a new item in your todo list, automatically. + %p + You will always know what to work on next. diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 70783a63409..45325d6bc4b 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -13,7 +13,7 @@ .other-labels - if @labels.present? %ul.content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', collection: @labels, as: :label + = render partial: 'shared/label', subject: @group, collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - else .nothing-here-block diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index ba1502c97b6..f7071051efc 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -11,7 +11,9 @@ .board-inner %header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } - {{ list.title }} + %span.has-tooltip{ ":title" => "(list.label ? list.label.description : '')", + data: { container: "body", placement: "bottom" } } + {{ list.title }} .board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" } %span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" } {{ list.issuesSize }} diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 99f3e1167d1..9135cee8364 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,4 +1,4 @@ -- commit = @repository.commit(branch.target) +- commit = @repository.commit(branch.dereferenced_target) - bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0 - diverging_commit_counts = @repository.diverging_commit_counts(branch) - number_commits_behind = diverging_commit_counts[:behind] diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 29d549a60f5..27da86b9efe 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -5,10 +5,10 @@ = custom_icon('icon_fork') %span Fork - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn has-tooltip' do + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn' do = custom_icon('icon_fork') %span Fork %div.count-with-arrow %span.arrow - = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count has-tooltip' do + = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count' do = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 311583037e5..12d35101770 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,5 +1,5 @@ - if current_user - = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do + = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do - if current_user.starred?(@project) = icon('star') %span.starred Unstar diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 6c82a4e5600..d8c95376b94 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,8 +1,23 @@ .commit-info-row.commit-info-row-header - %span.hidden-xs Authored by + %span.hidden-xs.hidden-sm Commit + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace js-details-short" + = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do + %span.text-expander + \... + %span.js-details-content.hide + = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace hidden-xs hidden-sm" + = clipboard_button(clipboard_text: @commit.id) + %span.hidden-xs authored + #{time_ago_with_tooltip(@commit.authored_date)} + %span by + = author_avatar(@commit, size: 24) %strong = commit_author_link(@commit, avatar: true, size: 24) - #{time_ago_with_tooltip(@commit.authored_date)} + - if @commit.different_committer? + %span.light Committed by + %strong + = commit_committer_link(@commit, avatar: true, size: 24) + #{time_ago_with_tooltip(@commit.committed_date)} .pull-right.commit-action-buttons - if defined?(@notes_count) && @notes_count > 0 @@ -33,42 +48,35 @@ %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) %li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) -- if @commit.different_committer? - .commit-info-row - %span.light Committed by - %strong - = commit_committer_link(@commit, avatar: true, size: 24) - #{time_ago_with_tooltip(@commit.committed_date)} - -.commit-info-row - %span.hidden-xs.hidden-sm Commit - = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace hidden-xs hidden-sm" - = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace visible-xs-inline visible-sm-inline" - = clipboard_button(clipboard_text: @commit.id) - %span.cgray= pluralize(@commit.parents.count, "parent") - - @commit.parents.each do |parent| - = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" - - %span.commit-info.branches - %i.fa.fa-spinner.fa-spin - -- if @commit.status - .commit-info-row - Builds for - = pluralize(@commit.pipelines.count, 'pipeline') - = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do - = ci_icon_for_status(@commit.status) - %span.ci-status-label - = ci_label_for_status(@commit.status) - in - = time_interval_in_words @commit.pipelines.total_duration - -.commit-box.content-block +.commit-box %h3.commit-title = markdown(@commit.title, pipeline: :single_line, author: @commit.author) - if @commit.description.present? %pre.commit-description = preserve(markdown(@commit.description, pipeline: :single_line, author: @commit.author)) +.commit-info-widget + .widget-row.branch-info + .icon-container.commit-icon + = custom_icon("icon_commit") + %span.cgray= pluralize(@commit.parents.count, "parent") + - @commit.parents.each do |parent| + = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" + %span.commit-info.branches + %i.fa.fa-spinner.fa-spin + + - if @commit.status + .widget-row.pipeline-info + .icon-container + = ci_icon_for_status(@commit.status) + Pipeline + = link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + for + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" + %span.ci-status-label + = ci_label_for_status(@commit.status) + in + = time_interval_in_words @commit.pipelines.total_duration + :javascript $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}"); diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml index f9d7eac3542..077b2d2725b 100644 --- a/app/views/projects/commit/builds.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -3,8 +3,7 @@ = render "projects/commits/head" %div{ class: container_class } - .prepend-top-default - = render "commit_box" + = render "commit_box" = render "ci_menu" = render "builds" diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index d85d6729a81..8233e26e4e7 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -1,7 +1,6 @@ - page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits" -.prepend-top-default - = render "commit_box" += render "commit_box" = render "ci_menu" = render "pipelines_list", pipelines: @ci_pipelines diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index cebf58d63df..b8c64d1f13e 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -4,8 +4,7 @@ = render "projects/commits/head" %div{ class: container_class } - .prepend-top-default - = render "commit_box" + = render "commit_box" - if @commit.status = render "ci_menu" - else diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 257e0a855bd..8f4f9ad4a80 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -8,7 +8,6 @@ = 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/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index a6a2e5690b5..73993f35b39 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -21,6 +21,8 @@ - if diff_file.deleted_file deleted + = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy filename to clipboard') + - if diff_file.mode_changed? %small = "#{diff_file.a_mode} → #{diff_file.b_mode}" diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index 44683c8bcdb..1892ebb512f 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -4,7 +4,7 @@ %ul.unstyled-list.related-merge-requests - @related_branches.each do |branch| %li - - target = @project.repository.find_branch(branch).target + - target = @project.repository.find_branch(branch).dereferenced_target - pipeline = @project.pipeline_for(branch, target.sha) if target - if pipeline %span.related-branch-ci-status diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index f135bf6f6b4..05a8475dcd6 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -22,14 +22,14 @@ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - if @prioritized_labels.present? - = render partial: 'shared/label', collection: @prioritized_labels, as: :label + = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label .other-labels - if can?(current_user, :admin_label, @project) %h5{ class: ('hide' if hide) } Other Labels %ul.content-list.manage-labels-list.js-other-labels - if @labels.present? - = render partial: 'shared/label', collection: @labels, as: :label + = render partial: 'shared/label', subject: @project, collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - if @labels.blank? .nothing-here-block diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 05fccb4f976..c42641afea0 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -1,4 +1,4 @@ -- commit = @repository.commit(tag.target) +- commit = @repository.commit(tag.dereferenced_target) - release = @releases.find { |release| release.tag == tag.name } %li %div diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 3480800369a..c367ae336db 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,6 +1,9 @@ %ul.nav-links.event-filter.scrolling-tabs = event_filter_link EventFilter.all, 'All' - = event_filter_link EventFilter.push, 'Push events' - = event_filter_link EventFilter.merged, 'Merge events' - = event_filter_link EventFilter.comments, 'Comments' + - if event_filter_visible(:repository) + = event_filter_link EventFilter.push, 'Push events' + - if event_filter_visible(:merge_requests) + = event_filter_link EventFilter.merged, 'Merge events' + - if event_filter_visible(:issues) + = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 40c8d2af226..6ccdef0df46 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -1,6 +1,7 @@ - label_css_id = dom_id(label) -- open_issues_count = label.open_issues_count(current_user, @project) -- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) +- open_issues_count = label.open_issues_count(current_user) +- open_merge_requests_count = label.open_merge_requests_count(current_user) +- subject = local_assigns[:subject] %li{id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label @@ -12,10 +13,10 @@ .dropdown-menu.dropdown-menu-align-right %ul %li - = link_to_label(label, subject: @project, type: :merge_request) do + = link_to_label(label, subject: subject, type: :merge_request) do = pluralize open_merge_requests_count, 'merge request' %li - = link_to_label(label, subject: @project) do + = link_to_label(label, subject: subject) do = pluralize open_issues_count, 'open issue' - if current_user %li.label-subscription{ data: toggle_subscription_data(label) } @@ -28,9 +29,9 @@ = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_label(label, subject: @project, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do = pluralize open_merge_requests_count, 'merge request' - = link_to_label(label, subject: @project, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do = pluralize open_issues_count, 'open issue' - if current_user diff --git a/app/views/shared/empty_states/_todos_all_done.svg b/app/views/shared/empty_states/_todos_all_done.svg new file mode 100644 index 00000000000..94b5c2e0ea0 --- /dev/null +++ b/app/views/shared/empty_states/_todos_all_done.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 293 216"><g fill="none" fill-rule="evenodd"><g transform="rotate(-5 211.388 -693.89)"><rect width="163.6" height="200" x=".2" fill="#FFF" stroke="#EEE" stroke-width="3" stroke-linecap="round" stroke-dasharray="6 9" rx="6"/><g transform="translate(24 38)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/></g><g transform="translate(24 83)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g><g transform="translate(24 130)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g></g><path fill="#FFCE29" d="M30 11l-1.8 4-2-4-4-1.8 4-2 2-4 2 4 4 2M286 60l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8M263 97l-2 4-2-4-4-2 4-2 2-4 2 4 4 2M12 85l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8"/></g></svg> diff --git a/app/views/shared/empty_states/_todos_empty.svg b/app/views/shared/empty_states/_todos_empty.svg new file mode 100644 index 00000000000..b1e661268fb --- /dev/null +++ b/app/views/shared/empty_states/_todos_empty.svg @@ -0,0 +1,110 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 284 337" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <rect id="a" width="180" height="220" x="66.2" y="74.4" rx="6"/> + <mask id="l" width="180" height="220" x="0" y="0" fill="#fff"> + <use xlink:href="#a"/> + </mask> + <rect id="b" width="180" height="220" rx="6"/> + <mask id="m" width="180" height="220" x="0" y="0" fill="#fff"> + <use xlink:href="#b"/> + </mask> + <rect id="c" width="28" height="28" rx="4"/> + <mask id="n" width="28" height="28" x="0" y="0" fill="#fff"> + <use xlink:href="#c"/> + </mask> + <rect id="d" width="28" height="28" rx="4"/> + <mask id="o" width="28" height="28" x="0" y="0" fill="#fff"> + <use xlink:href="#d"/> + </mask> + <circle id="e" cx="21.5" cy="21.5" r="21.5"/> + <mask id="p" width="43" height="43" x="0" y="0" fill="#fff"> + <use xlink:href="#e"/> + </mask> + <circle id="f" cx="26.5" cy="26.5" r="26.5"/> + <mask id="q" width="53" height="53" x="0" y="0" fill="#fff"> + <use xlink:href="#f"/> + </mask> + <circle id="g" cx="9.5" cy="4.5" r="4.5"/> + <mask id="r" width="13" height="13" x="-2" y="-2"> + <path fill="#fff" d="M3-2h13v13H3z"/> + <use xlink:href="#g"/> + </mask> + <circle id="h" cx="26.5" cy="26.5" r="26.5"/> + <mask id="s" width="53" height="53" x="0" y="0" fill="#fff"> + <use xlink:href="#h"/> + </mask> + <circle id="i" cx="21.5" cy="21.5" r="21.5"/> + <mask id="t" width="43" height="43" x="0" y="0" fill="#fff"> + <use xlink:href="#i"/> + </mask> + <path id="j" d="M18 38h15c10.5 0 19-8.5 19-19S43.5 0 33 0H19C8.5 0 0 8.5 0 19c0 6.3 3 12 7.8 15.3l5.2 9c.6 1 1.4 1 2 0l3-5.3z"/> + <mask id="u" width="52" height="44" x="0" y="0" fill="#fff"> + <use xlink:href="#j"/> + </mask> + <circle id="k" cx="18.5" cy="18.5" r="18.5"/> + <mask id="v" width="37" height="37" x="0" y="0" fill="#fff"> + <use xlink:href="#k"/> + </mask> + </defs> + <g fill="none" fill-rule="evenodd" transform="translate(-6 -4)"> + <use stroke="#EEE" stroke-width="6" mask="url(#l)" transform="rotate(-5 156.245 184.425)" xlink:href="#a"/> + <g transform="rotate(5 -707.333 618.042)"> + <use fill="#FFF" stroke="#EEE" stroke-width="6" mask="url(#m)" xlink:href="#b"/> + <g transform="translate(29 24)"> + <path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/> + <path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/> + <rect width="86" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/> + <rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/> + </g> + <g transform="translate(29 69)"> + <path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/> + <path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/> + <rect width="86" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/> + <rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/> + </g> + <g transform="translate(28 160)"> + <use stroke="#E5E5E5" stroke-width="6" mask="url(#n)" opacity=".7" xlink:href="#c"/> + <rect width="26" height="3" x="41" y="7" fill="#ECECEC" rx="1.5"/> + <rect width="43" height="3" x="41" y="17" fill="#ECECEC" rx="1.5"/> + </g> + <g transform="translate(28 116)"> + <use stroke="#E5E5E5" stroke-width="6" mask="url(#o)" xlink:href="#d"/> + <rect width="86" height="3" x="41" y="7" fill="#E5E5E5" rx="1.5"/> + <rect width="43" height="3" x="41" y="17" fill="#E5E5E5" rx="1.5"/> + </g> + </g> + <g transform="rotate(-15 601.917 -782.362)"> + <use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#p)" xlink:href="#e"/> + <text fill="#6B4FBB" font-family="SourceSansPro-Black, Source Sans Pro" font-size="20" font-weight="700" letter-spacing="-.1"> + <tspan x="12" y="27">@</tspan> + </text> + </g> + <g transform="rotate(15 -686.59 1035.907)"> + <use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#q)" xlink:href="#f"/> + <path fill="#FC6D26" d="M26.5 38.2c3.3 0 9.5-2.5 9.5-9.6 0-7-2.4-6.6-9.5-6.6-7 0-9.5-.4-9.5 6.6s6.2 9.6 9.5 9.6z"/> + <g transform="translate(17 14)"> + <use fill="#FC6D26" xlink:href="#g"/> + <use stroke="#FFF" stroke-width="4" mask="url(#r)" xlink:href="#g"/> + </g> + </g> + <g transform="rotate(15 -85.125 65.185)"> + <use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#s)" xlink:href="#h"/> + <path fill="#6B4FBB" d="M24 18.5c0-1.4 1-2.5 2.5-2.5 1.4 0 2.5 1 2.5 2.5v9c0 1.4-1 2.5-2.5 2.5-1.4 0-2.5-1-2.5-2.5v-9zM26.5 37c1.4 0 2.5-1 2.5-2.5 0-1.4-1-2.5-2.5-2.5-1.4 0-2.5 1-2.5 2.5 0 1.4 1 2.5 2.5 2.5z"/> + </g> + <g transform="rotate(-15 716.492 78.873)"> + <use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#t)" xlink:href="#i"/> + <path fill="#FC6D26" d="M20 23v-3h3v3h-3zm0 3v1.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-2.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-3h-1.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-2.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h3v-1.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h2.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v3h1.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v2.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-3z"/> + </g> + <g transform="rotate(-15 129.114 -585.74)"> + <use stroke="#FDE5D8" stroke-width="6" mask="url(#u)" xlink:href="#j"/> + <circle cx="16" cy="20" r="2" fill="#FC6D26"/> + <circle cx="27" cy="20" r="2" fill="#FC6D26"/> + <circle cx="38" cy="20" r="2" fill="#FC6D26"/> + </g> + <g transform="rotate(-15 1254.8 -458.986)"> + <use stroke="#FDE5D8" stroke-width="6" mask="url(#v)" xlink:href="#k"/> + <path fill="#FC6D26" d="M10.6 19l2-2c.5-.5.5-1 0-1.5-.3-.4-1-.4-1.3 0l-2.8 2.8c-.2.2-.3.4-.3.7 0 .3 0 .5.3.7l2.8 2.8c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4l-2-2zm14.8 0l-2-2c-.5-.5-.5-1 0-1.5.3-.4 1-.4 1.3 0l2.8 2.8c.2.2.3.4.3.7 0 .3 0 .5-.3.7l-2.8 2.8c-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4l2-2z"/> + <rect width="2" height="7" x="17" y="15.1" fill="#FC6D26" opacity=".5" transform="rotate(15 18.002 18.64)" rx="1"/> + </g> + </g> +</svg> diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb index efb85eafd15..d973e662ff2 100644 --- a/app/workers/project_web_hook_worker.rb +++ b/app/workers/project_web_hook_worker.rb @@ -2,6 +2,8 @@ class ProjectWebHookWorker include Sidekiq::Worker include DedicatedSidekiqQueue + sidekiq_options retry: 4 + def perform(hook_id, data, hook_name) data = data.with_indifferent_access WebHook.find(hook_id).execute(data, hook_name) diff --git a/app/workers/remove_unreferenced_lfs_objects_worker.rb b/app/workers/remove_unreferenced_lfs_objects_worker.rb new file mode 100644 index 00000000000..b80f131d5f7 --- /dev/null +++ b/app/workers/remove_unreferenced_lfs_objects_worker.rb @@ -0,0 +1,8 @@ +class RemoveUnreferencedLfsObjectsWorker + include Sidekiq::Worker + include CronjobQueue + + def perform + LfsObject.destroy_unreferenced + end +end diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index 74325872b09..c11296975b7 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -101,6 +101,13 @@ :why: GPL-licensed libraries cannot be linked to from non-GPL projects. :versions: [] :when: 2016-05-02 05:29:43.904715000 Z +- - :blacklist + - OSL-3.0 + - :who: Sean McGivern + :why: The OSL license is a copyleft license + :versions: [] + :when: 2016-10-28 11:02:15.540105000 Z + # GEM LICENSES - - :license diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index efe0ac9c965..9fec2ad6bf7 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -307,6 +307,9 @@ Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWork 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' +Settings.cron_jobs['remove_unreferenced_lfs_objects_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['cron'] ||= '20 0 * * *' +Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['job_class'] = 'RemoveUnreferencedLfsObjectsWorker' # # GitLab Shell diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index f7e714cd6bc..0455a98dbfe 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -42,3 +42,19 @@ end Sidekiq.configure_client do |config| config.redis = redis_config_hash end + +# The Sidekiq client API always adds the queue to the Sidekiq queue +# list, but mail_room and gitlab-shell do not. This is only necessary +# for monitoring. +config = YAML.load_file(Rails.root.join('config', 'sidekiq_queues.yml').to_s) + +begin + Sidekiq.redis do |conn| + conn.pipelined do + config[:queues].each do |queue| + conn.sadd('queues', queue[0]) + end + end + end +rescue Redis::BaseError, SocketError +end diff --git a/doc/api/README.md b/doc/api/README.md index 3fbe5197a21..f65b934b9db 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -23,6 +23,7 @@ following locations: - [Group Access Requests](access_requests.md) - [Group Members](members.md) - [Issues](issues.md) +- [Issue Boards](boards.md) - [Keys](keys.md) - [Labels](labels.md) - [Merge Requests](merge_requests.md) diff --git a/doc/api/projects.md b/doc/api/projects.md index b69db90e70d..8ebac57e612 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -598,7 +598,7 @@ Parameters: | `container_registry_enabled` | boolean | no | Enable container registry for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | | `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 | -| `visibility_level` | integer | no | See [project visibility level][#project-visibility-level] | +| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | @@ -630,7 +630,7 @@ Parameters: | `container_registry_enabled` | boolean | no | Enable container registry for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | | `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 | -| `visibility_level` | integer | no | See [project visibility level][#project-visibility-level] | +| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | @@ -661,7 +661,7 @@ Parameters: | `container_registry_enabled` | boolean | no | Enable container registry for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project | | `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 | -| `visibility_level` | integer | no | See [project visibility level][#project-visibility-level] | +| `visibility_level` | integer | no | See [project visibility level](#project-visibility-level) | | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | diff --git a/doc/api/tags.md b/doc/api/tags.md index 54059117456..398b080e3f6 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -124,7 +124,7 @@ Parameters: The message will be `nil` when creating a lightweight tag otherwise it will contain the annotation. -It returns 200 if the operation succeed. In case of an error, +It returns 201 if the operation succeed. In case of an error, 405 with an explaining error message is returned. ## Delete a tag diff --git a/doc/development/frontend.md b/doc/development/frontend.md index 4fb56444917..ece8f880542 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -238,13 +238,18 @@ For our currently-supported browsers, see our [requirements][requirements]. [scss-style-guide]: scss_styleguide.md [requirements]: ../install/requirements.md#supported-web-browsers -## Common Errors +## Gotchas -### Rspec (Capybara/Poltergeist) chokes on general JavaScript errors +### Phantom.JS (used by Teaspoon & Rspec) chokes, returning vague JavaScript errors If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being thrown in tests, but can't reproduce them manually, you may have included `ES6`-style JavaScript in files that don't have the `.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file you're -working in (`git mv <file>.js> <file.js.es6>`). +working in (`git mv <file.js> <file.js.es6>`). + +Similar errors will be thrown if you're using +any of the [array methods introduced in ES6](http://www.2ality.com/2014/05/es6-array-methods.html) +whether or not you've updated the file extension. + diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index 159d5ce286d..b25ce79e89f 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -41,9 +41,9 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9 [Exception]: http://stackoverflow.com/q/10048173/223897 -## Don't use inline CoffeeScript/JavaScript in views +## Don't use inline JavaScript in views -Using the inline `:coffee` or `:coffeescript` Haml filters comes with a +Using the inline `:javascript` Haml filters comes with a performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided. _**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb) @@ -51,9 +51,7 @@ in an initializer._ ### Further reading -- Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu) - Stack Overflow: [Why you should not write inline JavaScript](http://programmers.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting) -- Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897) ## ID-based CSS selectors need to be a bit more specific diff --git a/doc/development/licensing.md b/doc/development/licensing.md index 05972b33fdb..5d177eb26ee 100644 --- a/doc/development/licensing.md +++ b/doc/development/licensing.md @@ -62,6 +62,7 @@ Libraries with the following licenses are unacceptable for use: - [GNU GPL][GPL] (version 1, [version 2][GPLv2], [version 3][GPLv3], or any future versions): GPL-licensed libraries cannot be linked to from non-GPL projects. - [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects. +- [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU]. ## Notes @@ -93,3 +94,5 @@ Gems which are included only in the "development" or "test" groups by Bundler ar [AGPLv3]: http://choosealicense.com/licenses/agpl-3.0/ [GNU-GPL-FAQ]: http://www.gnu.org/licenses/gpl-faq.html#IfLibraryIsGPL [OSI-GPL]: https://opensource.org/faq#linking-proprietary-code +[OSL]: https://opensource.org/licenses/OSL-3.0 +[OSL-GNU]: https://www.gnu.org/licenses/license-list.en.html#OSL diff --git a/doc/development/testing.md b/doc/development/testing.md index 513457d203a..8e91ac5e3ba 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -36,8 +36,8 @@ the command line via `bundle exec teaspoon`, or via a web browser at `http://localhost:3000/teaspoon` when the Rails server is running. - JavaScript tests live in `spec/javascripts/`, matching the folder structure of - `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js.coffee` has a corresponding - `spec/javascripts/behaviors/autosize_spec.js.coffee` file. + `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js.es6` has a corresponding + `spec/javascripts/behaviors/autosize_spec.js.es6` file. - Haml fixtures required for JavaScript tests live in `spec/javascripts/fixtures`. They should contain the bare minimum amount of markup necessary for the test. diff --git a/doc/integration/saml.md b/doc/integration/saml.md index f3b2a288776..4a242c321aa 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -268,13 +268,20 @@ message `Can't verify CSRF token authenticity`. This means that there is an erro the SAML request, but this error never reaches GitLab due to the CSRF check. To bypass this you can add `skip_before_action :verify_authenticity_token` to the -`omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab, -where it can then be seen in the usual logs, or as a flash message in the login -screen. - -That file is located at `/opt/gitlab/embedded/service/gitlab-rails/app/controllers` -for Omnibus installations and by default on `/home/git/gitlab/app/controllers` for -installations from source. +`omniauth_callbacks_controller.rb` file immediately after the `class` line and +comment out the `protect_from_forgery` line using a `#` then restart Unicorn. This +will allow the error to hit GitLab, where it can then be seen in the usual logs, +or as a flash message on the login screen. + +That file is located in `/opt/gitlab/embedded/service/gitlab-rails/app/controllers` +for Omnibus installations and by default in `/home/git/gitlab/app/controllers` for +installations from source. Restart Unicorn using the `sudo gitlab-ctl restart unicorn` +command on Omnibus installations and `sudo service gitlab restart` on installations +from source. + +You may also find the [SSO Tracer](https://addons.mozilla.org/en-US/firefox/addon/sso-tracer) +(Firefox) and [SAML Chrome Panel](https://chrome.google.com/webstore/detail/saml-chrome-panel/paijfdbeoenhembfhkhllainmocckace) +(Chrome) browser extensions useful in your debugging. ### Invalid audience diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index 244306e8464..007dfb67a77 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see commit ci info' do - expect(page).to have_content "Builds for 1 pipeline pending" + expect(page).to have_content "Pipeline #1 for 570e7b2a pending" end step 'I click status link' do @@ -171,7 +171,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see builds list' do - expect(page).to have_content "Builds for 1 pipeline pending" + expect(page).to have_content "Pipeline #1 for 570e7b2a pending" expect(page).to have_content "1 build" end diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 6d827448994..21a106387f0 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -6,58 +6,55 @@ module API before { authenticate! } before { authorize! :download_code, user_project } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do - # Get a project repository branches - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/branches + desc 'Get a project repository branches' do + success Entities::RepoBranch + end get ":id/repository/branches" do branches = user_project.repository.branches.sort_by(&:name) present branches, with: Entities::RepoBranch, project: user_project end - # Get a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # GET /projects/:id/repository/branches/:branch - get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do - @branch = user_project.repository.branches.find { |item| item.name == params[:branch] } - not_found!("Branch") unless @branch + desc 'Get a single branch' do + success Entities::RepoBranch + end + params do + requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + end + get ':id/repository/branches/:branch' do + branch = user_project.repository.find_branch(params[:branch]) + not_found!("Branch") unless branch - present @branch, with: Entities::RepoBranch, project: user_project + present branch, with: Entities::RepoBranch, project: user_project end - # Protect a single branch - # # Note: The internal data model moved from `developers_can_{merge,push}` to `allowed_to_{merge,push}` # in `gitlab-org/gitlab-ce!5081`. The API interface has not been changed (to maintain compatibility), # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`. - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # developers_can_push (optional) - Flag if developers can push to that branch - # developers_can_merge (optional) - Flag if developers can merge to that branch - # Example Request: - # PUT /projects/:id/repository/branches/:branch/protect - put ':id/repository/branches/:branch/protect', - requirements: { branch: /.+/ } do + desc 'Protect a single branch' do + success Entities::RepoBranch + end + params do + requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch' + optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch' + end + put ':id/repository/branches/:branch/protect' do authorize_admin_project - @branch = user_project.repository.find_branch(params[:branch]) - not_found!('Branch') unless @branch - protected_branch = user_project.protected_branches.find_by(name: @branch.name) + branch = user_project.repository.find_branch(params[:branch]) + not_found!('Branch') unless branch + + protected_branch = user_project.protected_branches.find_by(name: branch.name) protected_branch_params = { - name: @branch.name, - developers_can_push: to_boolean(params[:developers_can_push]), - developers_can_merge: to_boolean(params[:developers_can_merge]) + name: branch.name, + developers_can_push: params[:developers_can_push], + developers_can_merge: params[:developers_can_merge] } service_args = [user_project, current_user, protected_branch_params] @@ -69,39 +66,36 @@ module API end if protected_branch.valid? - present @branch, with: Entities::RepoBranch, project: user_project + present branch, with: Entities::RepoBranch, project: user_project else render_api_error!(protected_branch.errors.full_messages, 422) end end - # Unprotect a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # PUT /projects/:id/repository/branches/:branch/unprotect - put ':id/repository/branches/:branch/unprotect', - requirements: { branch: /.+/ } do + desc 'Unprotect a single branch' do + success Entities::RepoBranch + end + params do + requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + end + put ':id/repository/branches/:branch/unprotect' do authorize_admin_project - @branch = user_project.repository.find_branch(params[:branch]) - not_found!("Branch") unless @branch - protected_branch = user_project.protected_branches.find_by(name: @branch.name) + branch = user_project.repository.find_branch(params[:branch]) + not_found!("Branch") unless branch + protected_branch = user_project.protected_branches.find_by(name: branch.name) protected_branch.destroy if protected_branch - present @branch, with: Entities::RepoBranch, project: user_project + present branch, with: Entities::RepoBranch, project: user_project end - # Create branch - # - # Parameters: - # id (required) - The ID of a project - # branch_name (required) - The name of the branch - # ref (required) - Create branch from commit sha or existing branch - # Example Request: - # POST /projects/:id/repository/branches + desc 'Create branch' do + success Entities::RepoBranch + end + params do + requires :branch_name, type: String, desc: 'The name of the branch' + requires :ref, type: String, desc: 'Create branch from commit sha or existing branch' + end post ":id/repository/branches" do authorize_push_project result = CreateBranchService.new(user_project, current_user). @@ -116,16 +110,13 @@ module API end end - # Delete branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # DELETE /projects/:id/repository/branches/:branch - delete ":id/repository/branches/:branch", - requirements: { branch: /.+/ } do + desc 'Delete a branch' + params do + requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + end + delete ":id/repository/branches/:branch" do authorize_push_project + result = DeleteBranchService.new(user_project, current_user). execute(params[:branch]) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index feaa0c213bf..ab9d2d54f4b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -138,7 +138,7 @@ module API expose :name expose :commit do |repo_branch, options| - options[:project].repository.commit(repo_branch.target) + options[:project].repository.commit(repo_branch.dereferenced_target) end expose :protected do |repo_branch, options| @@ -523,7 +523,7 @@ module API expose :name, :message expose :commit do |repo_tag, options| - options[:project].repository.commit(repo_tag.target) + options[:project].repository.commit(repo_tag.dereferenced_target) end expose :release, using: Entities::Release do |repo_tag, options| diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 8025581d3ca..3c9d7b1aaef 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -1,18 +1,12 @@ module API module Helpers + include Gitlab::Utils + PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN" PRIVATE_TOKEN_PARAM = :private_token SUDO_HEADER = "HTTP_SUDO" SUDO_PARAM = :sudo - def to_boolean(value) - return value if [true, false].include?(value) - return true if value =~ /^(true|t|yes|y|1|on)$/i - return false if value =~ /^(false|f|no|n|0|off)$/i - - nil - end - def private_token params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER] end diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index cb213a76a05..3740d4fb4cd 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -102,10 +102,10 @@ module Banzai end elsif element_node?(node) - yield_valid_link(node) do |link, text| + yield_valid_link(node) do |link, inner_html| if ref_pattern && link =~ /\A#{ref_pattern}\z/ replace_link_node_with_href(node, link) do - object_link_filter(link, ref_pattern, link_text: text) + object_link_filter(link, ref_pattern, link_content: inner_html) end next @@ -113,9 +113,9 @@ module Banzai next unless link_pattern - if link == text && text =~ /\A#{link_pattern}/ + if link == inner_html && inner_html =~ /\A#{link_pattern}/ replace_link_node_with_text(node, link) do - object_link_filter(text, link_pattern) + object_link_filter(inner_html, link_pattern) end next @@ -123,7 +123,7 @@ module Banzai if link =~ /\A#{link_pattern}\z/ replace_link_node_with_href(node, link) do - object_link_filter(link, link_pattern, link_text: text) + object_link_filter(link, link_pattern, link_content: inner_html) end next @@ -140,11 +140,11 @@ module Banzai # # text - String text to replace references in. # pattern - Reference pattern to match against. - # link_text - Original content of the link being replaced. + # link_content - Original content of the link being replaced. # # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. - def object_link_filter(text, pattern, link_text: nil) + def object_link_filter(text, pattern, link_content: nil) references_in(text, pattern) do |match, id, project_ref, matches| project = project_from_ref_cached(project_ref) @@ -152,7 +152,7 @@ module Banzai title = object_link_title(object) klass = reference_class(object_sym) - data = data_attributes_for(link_text || match, project, object) + data = data_attributes_for(link_content || match, project, object) if matches.names.include?("url") && matches[:url] url = matches[:url] @@ -160,11 +160,11 @@ module Banzai url = url_for_object_cached(object, project) end - text = link_text || object_link_text(object, matches) + content = link_content || object_link_text(object, matches) %(<a href="#{url}" #{data} title="#{escape_once(title)}" - class="#{klass}">#{escape_once(text)}</a>) + class="#{klass}">#{content}</a>) else match end diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb index 0d20be557a0..dce4de3ceaf 100644 --- a/lib/banzai/filter/external_issue_reference_filter.rb +++ b/lib/banzai/filter/external_issue_reference_filter.rb @@ -37,10 +37,10 @@ module Banzai end elsif element_node?(node) - yield_valid_link(node) do |link, text| + yield_valid_link(node) do |link, inner_html| if link =~ ref_start_pattern replace_link_node_with_href(node, link) do - issue_link_filter(link, link_text: text) + issue_link_filter(link, link_content: inner_html) end end end @@ -54,10 +54,11 @@ module Banzai # issue's details page. # # text - String text to replace references in. + # link_content - Original content of the link being replaced. # # Returns a String with `JIRA-123` references replaced with links. All # links have `gfm` and `gfm-issue` class names attached for styling. - def issue_link_filter(text, link_text: nil) + def issue_link_filter(text, link_content: nil) project = context[:project] self.class.references_in(text, issue_reference_pattern) do |match, id| @@ -69,11 +70,11 @@ module Banzai klass = reference_class(:issue) data = data_attribute(project: project.id, external_issue: id) - text = link_text || match + content = link_content || match %(<a href="#{url}" #{data} title="#{escape_once(title)}" - class="#{klass}">#{escape_once(text)}</a>) + class="#{klass}">#{content}</a>) end end diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index 2d221290f7e..84bfeac8041 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -85,14 +85,14 @@ module Banzai @nodes ||= each_node.to_a end - # Yields the link's URL and text whenever the node is a valid <a> tag. + # Yields the link's URL and inner HTML whenever the node is a valid <a> tag. def yield_valid_link(node) link = CGI.unescape(node.attr('href').to_s) - text = node.text + inner_html = node.inner_html return unless link.force_encoding('UTF-8').valid_encoding? - yield link, text + yield link, inner_html end def replace_text_when_pattern_matches(node, pattern) diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index c6302b586d3..f842b1fb779 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -35,10 +35,10 @@ module Banzai user_link_filter(content) end elsif element_node?(node) - yield_valid_link(node) do |link, text| + yield_valid_link(node) do |link, inner_html| if link =~ ref_pattern_start replace_link_node_with_href(node, link) do - user_link_filter(link, link_text: text) + user_link_filter(link, link_content: inner_html) end end end @@ -52,15 +52,16 @@ module Banzai # user's profile page. # # text - String text to replace references in. + # link_content - Original content of the link being replaced. # # Returns a String with `@user` references replaced with links. All links # have `gfm` and `gfm-project_member` class names attached for styling. - def user_link_filter(text, link_text: nil) + def user_link_filter(text, link_content: nil) self.class.references_in(text) do |match, username| if username == 'all' - link_to_all(link_text: link_text) + link_to_all(link_content: link_content) elsif namespace = namespaces[username] - link_to_namespace(namespace, link_text: link_text) || match + link_to_namespace(namespace, link_content: link_content) || match else match end @@ -102,49 +103,49 @@ module Banzai reference_class(:project_member) end - def link_to_all(link_text: nil) + def link_to_all(link_content: nil) project = context[:project] author = context[:author] if author && !project.team.member?(author) - link_text + link_content else url = urls.namespace_project_url(project.namespace, project, only_path: context[:only_path]) data = data_attribute(project: project.id, author: author.try(:id)) - text = link_text || User.reference_prefix + 'all' + content = link_content || User.reference_prefix + 'all' - link_tag(url, data, text, 'All Project and Group Members') + link_tag(url, data, content, 'All Project and Group Members') end end - def link_to_namespace(namespace, link_text: nil) + def link_to_namespace(namespace, link_content: nil) if namespace.is_a?(Group) - link_to_group(namespace.path, namespace, link_text: link_text) + link_to_group(namespace.path, namespace, link_content: link_content) else - link_to_user(namespace.path, namespace, link_text: link_text) + link_to_user(namespace.path, namespace, link_content: link_content) end end - def link_to_group(group, namespace, link_text: nil) + def link_to_group(group, namespace, link_content: nil) url = urls.group_url(group, only_path: context[:only_path]) data = data_attribute(group: namespace.id) - text = link_text || Group.reference_prefix + group + content = link_content || Group.reference_prefix + group - link_tag(url, data, text, namespace.name) + link_tag(url, data, content, namespace.name) end - def link_to_user(user, namespace, link_text: nil) + def link_to_user(user, namespace, link_content: nil) url = urls.user_url(user, only_path: context[:only_path]) data = data_attribute(user: namespace.owner_id) - text = link_text || User.reference_prefix + user + content = link_content || User.reference_prefix + user - link_tag(url, data, text, namespace.owner_name) + link_tag(url, data, content, namespace.owner_name) end - def link_tag(url, data, text, title) - %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{escape_once(text)}</a>) + def link_tag(url, data, link_content, title) + %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{link_content}</a>) end end end diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb index 0df3a72d1c4..de3ebe72720 100644 --- a/lib/banzai/redactor.rb +++ b/lib/banzai/redactor.rb @@ -41,10 +41,10 @@ module Banzai next if visible.include?(node) doc_data[:visible_reference_count] -= 1 - # The reference should be replaced by the original text, - # which is not always the same as the rendered text. - text = node.attr('data-original') || node.text - node.replace(text) + # The reference should be replaced by the original link's content, + # which is not always the same as the rendered one. + content = node.attr('data-original') || node.inner_html + node.replace(content) end end diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb index 4f81863da35..d76aa38f741 100644 --- a/lib/gitlab/data_builder/push.rb +++ b/lib/gitlab/data_builder/push.rb @@ -83,7 +83,7 @@ module Gitlab tag = repository.find_tag(tag_name) if tag - commit = repository.commit(tag.target) + commit = repository.commit(tag.dereferenced_target) commit.try(:sha) end else diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index e59ead5d76c..4c395b4266e 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -13,5 +13,13 @@ module Gitlab def force_utf8(str) str.force_encoding(Encoding::UTF_8) end + + def to_boolean(value) + return value if [true, false].include?(value) + return true if value =~ /^(true|t|yes|y|1|on)$/i + return false if value =~ /^(false|f|no|n|0|off)$/i + + nil + end end end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 4e3ef5dc6fa..7c5f33c63b8 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::MilestonesController do delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js expect(response).to be_success - expect(Event.first.action).to eq(Event::DESTROYED) + expect(Event.recent.first.action).to eq(Event::DESTROYED) expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound) issue.reload diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index b4f066d8600..2a7523c6512 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -14,49 +14,49 @@ describe Projects::ProjectMembersController do end describe 'POST create' do - context 'when users are added' do - let(:project_user) { create(:user) } + let(:project_user) { create(:user) } - before { sign_in(user) } + before { sign_in(user) } - context 'when user does not have enough rights' do - before { project.team << [user, :developer] } + context 'when user does not have enough rights' do + before { project.team << [user, :developer] } - it 'returns 404' do - post :create, namespace_id: project.namespace, - project_id: project, - user_ids: project_user.id, - access_level: Gitlab::Access::GUEST + it 'returns 404' do + post :create, namespace_id: project.namespace, + project_id: project, + user_ids: project_user.id, + access_level: Gitlab::Access::GUEST - expect(response).to have_http_status(404) - expect(project.users).not_to include project_user - end + expect(response).to have_http_status(404) + expect(project.users).not_to include project_user end + end - context 'when user has enough rights' do - before { project.team << [user, :master] } + context 'when user has enough rights' do + before { project.team << [user, :master] } - it 'adds user to members' do - post :create, namespace_id: project.namespace, - project_id: project, - user_ids: project_user.id, - access_level: Gitlab::Access::GUEST - - expect(response).to set_flash.to 'Users were successfully added.' - expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project)) - expect(project.users).to include project_user - end + it 'adds user to members' do + expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(true) - it 'adds no user to members' do - post :create, namespace_id: project.namespace, - project_id: project, - user_ids: '', - access_level: Gitlab::Access::GUEST + post :create, namespace_id: project.namespace, + project_id: project, + user_ids: project_user.id, + access_level: Gitlab::Access::GUEST - expect(response).to set_flash.to 'No users or groups specified.' - expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project)) - expect(project.users).not_to include project_user - end + expect(response).to set_flash.to 'Users were successfully added.' + expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project)) + end + + it 'adds no user to members' do + expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(false) + + post :create, namespace_id: project.namespace, + project_id: project, + user_ids: '', + access_level: Gitlab::Access::GUEST + + expect(response).to set_flash.to 'No users or groups specified.' + expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project)) end end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 3234fabe288..a92075fec8f 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -53,7 +53,7 @@ describe 'Issue Boards', feature: true, js: true do context 'with lists' do let(:milestone) { create(:milestone, project: project) } - let(:planning) { create(:label, project: project, name: 'Planning') } + let(:planning) { create(:label, project: project, name: 'Planning', description: 'Test') } let(:development) { create(:label, project: project, name: 'Development') } let(:testing) { create(:label, project: project, name: 'Testing') } let(:bug) { create(:label, project: project, name: 'Bug') } @@ -91,6 +91,12 @@ describe 'Issue Boards', feature: true, js: true do expect(page).to have_selector('.board', count: 4) end + it 'shows description tooltip on list title' do + page.within('.board:nth-child(2)') do + expect(find('.board-title span.has-tooltip')[:title]).to eq('Test') + end + end + it 'shows issues in lists' do wait_for_board_cards(2, 2) wait_for_board_cards(3, 2) diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index b963d1305b5..c68e1ea4af9 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -59,4 +59,12 @@ feature 'Create New Merge Request', feature: true, js: true do expect(page).to have_css('a.btn.active', text: 'Side-by-side') end end + + it 'does not allow non-existing branches' do + visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' }) + + expect(page).to have_content('The form contains the following errors') + expect(page).to have_content('Source branch "non-exist-source" does not exist') + expect(page).to have_content('Target branch "non-exist-target" does not exist') + end end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 1d4484a9edd..e796ee570b7 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -41,6 +41,22 @@ describe 'Edit Project Settings', feature: true do end end end + + context "pipelines subtabs" do + it "shows builds when enabled" do + visit namespace_project_pipelines_path(project.namespace, project) + + expect(page).to have_selector(".shortcuts-builds") + end + + it "hides builds when disabled" do + allow(Ability).to receive(:allowed?).with(member, :read_builds, project).and_return(false) + + visit namespace_project_pipelines_path(project.namespace, project) + + expect(page).not_to have_selector(".shortcuts-builds") + end + end end describe 'project features visibility pages' do @@ -148,5 +164,23 @@ describe 'Edit Project Settings', feature: true do expect(page).to have_content "Customize your workflow!" end + + it "hides project activity tabs" do + select "Disabled", from: "project_project_feature_attributes_repository_access_level" + select "Disabled", from: "project_project_feature_attributes_issues_access_level" + select "Disabled", from: "project_project_feature_attributes_wiki_access_level" + + click_button "Save changes" + wait_for_ajax + + visit activity_namespace_project_path(project.namespace, project) + + page.within(".event-filter") do + expect(page).to have_selector("a", count: 2) + expect(page).not_to have_content("Push events") + expect(page).not_to have_content("Merge events") + expect(page).not_to have_content("Comments") + end + end end end diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb new file mode 100644 index 00000000000..b4f5f6b3fc5 --- /dev/null +++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe 'Projects > Wiki > User views wiki in project page', feature: true do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + + before do + project.team << [user, :master] + login_as(user) + end + + context 'when repository is disabled for project' do + before do + project.project_feature.update!( + repository_access_level: ProjectFeature::DISABLED, + merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED + ) + end + + context 'when wiki homepage contains a link' do + before do + WikiPages::CreateService.new( + project, + user, + title: 'home', + content: '[some link](other-page)' + ).execute + end + + it 'displays the correct URL for the link' do + visit namespace_project_path(project.namespace, project) + expect(page).to have_link( + 'some link', + href: namespace_project_wiki_path( + project.namespace, + project, + 'other-page' + ) + ) + end + end + end +end diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 230543cd175..3ae83ac082d 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -13,7 +13,7 @@ describe 'Dashboard Todos', feature: true do visit dashboard_todos_path end it 'shows "All done" message' do - expect(page).to have_content "You're all done!" + expect(page).to have_content "Todos let you see what you should do next." end end @@ -44,7 +44,7 @@ describe 'Dashboard Todos', feature: true do end it 'shows "All done" message' do - expect(page).to have_content("You're all done!") + expect(page).to have_content("Good job! Looks like you don't have any todos left.") end end @@ -64,7 +64,7 @@ describe 'Dashboard Todos', feature: true do end it 'shows "All done" message' do - expect(page).to have_content("You're all done!") + expect(page).to have_content("Good job! Looks like you don't have any todos left.") end end end @@ -152,7 +152,7 @@ describe 'Dashboard Todos', feature: true do within('.todos-pending-count') { expect(page).to have_content '0' } expect(page).to have_content 'To do 0' expect(page).to have_content 'Done 0' - expect(page).to have_content "You're all done!" + expect(page).to have_content "Good job! Looks like you don't have any todos left." end end end diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index 6fce11de30f..db60c01db0d 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -21,7 +21,7 @@ describe BranchesFinder do result = branches_finder.execute recently_updated_branch = repository.branches.max do |a, b| - repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date + repository.commit(a.dereferenced_target).committed_date <=> repository.commit(b.dereferenced_target).committed_date end expect(result.first.name).to eq(recently_updated_branch.name) diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb index 2ac810e478a..98b42e264dc 100644 --- a/spec/finders/tags_finder_spec.rb +++ b/spec/finders/tags_finder_spec.rb @@ -20,7 +20,7 @@ describe TagsFinder do result = tags_finder.execute recently_updated_tag = repository.tags.max do |a, b| - repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date + repository.commit(a.dereferenced_target).committed_date <=> repository.commit(b.dereferenced_target).committed_date end expect(result.first.name).to eq(recently_updated_tag.name) diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js index bdce2465fbf..9cb8243ee2c 100644 --- a/spec/javascripts/spec_helper.js +++ b/spec/javascripts/spec_helper.js @@ -28,7 +28,7 @@ // setTimeout(Teaspoon.execute, 1000) // Matching files // By default Teaspoon will look for files that match -// _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path +// _spec.{js,js.es6}. Add a filename_spec.js file in your spec path // and it'll be included in the default suite automatically. If you want to // customize suites, check out the configuration in teaspoon_env.rb // Manifest diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index 2f9343fadaf..fbf7a461fa5 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -8,6 +8,8 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do end shared_examples_for "external issue tracker" do + it_behaves_like 'a reference containing an element node' + it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index a2025672ad9..8f0b2db3e8e 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -22,6 +22,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end context 'internal reference' do + it_behaves_like 'a reference containing an element node' + let(:reference) { issue.to_reference } it 'ignores valid references when using non-default tracker' do @@ -83,6 +85,20 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do expect(link.attr('data-issue')).to eq issue.id.to_s end + it 'includes a data-original attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-original') + expect(link.attr('data-original')).to eq reference + end + + it 'does not escape the data-original attribute' do + inner_html = 'element <code>node</code> inside' + doc = reference_filter(%{<a href="#{reference}">#{inner_html}</a>}) + expect(doc.children.first.attr('data-original')).to eq inner_html + end + it 'supports an :only_path context' do doc = reference_filter("Issue #{reference}", only_path: true) link = doc.css('a').first.attr('href') @@ -101,6 +117,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end context 'cross-project reference' do + it_behaves_like 'a reference containing an element node' + let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } @@ -141,6 +159,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end context 'cross-project URL reference' do + it_behaves_like 'a reference containing an element node' + let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } @@ -160,39 +180,45 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end context 'cross-project reference in link href' do + it_behaves_like 'a reference containing an element node' + let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } - let(:reference) { %Q{<a href="#{issue.to_reference(project)}">Reference</a>} } + let(:reference) { issue.to_reference(project) } + let(:reference_link) { %{<a href="#{reference}">Reference</a>} } it 'links to a valid reference' do - doc = reference_filter("See #{reference}") + doc = reference_filter("See #{reference_link}") expect(doc.css('a').first.attr('href')). to eq helper.url_for_issue(issue.iid, project2) end it 'links with adjacent text' do - doc = reference_filter("Fixed (#{reference}.)") + doc = reference_filter("Fixed (#{reference_link}.)") expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/) end end context 'cross-project URL in link href' do + it_behaves_like 'a reference containing an element node' + let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } - let(:reference) { %Q{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} } + let(:reference) { "#{helper.url_for_issue(issue.iid, project2) + "#note_123"}" } + let(:reference_link) { %{<a href="#{reference}">Reference</a>} } it 'links to a valid reference' do - doc = reference_filter("See #{reference}") + doc = reference_filter("See #{reference_link}") expect(doc.css('a').first.attr('href')). to eq helper.url_for_issue(issue.iid, project2) + "#note_123" end it 'links with adjacent text' do - doc = reference_filter("Fixed (#{reference}.)") + doc = reference_filter("Fixed (#{reference_link}.)") expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/) end end diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 729e77fd43f..5bfeb82e738 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -24,6 +24,8 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end context 'mentioning @all' do + it_behaves_like 'a reference containing an element node' + let(:reference) { User.reference_prefix + 'all' } before do @@ -60,6 +62,8 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end context 'mentioning a user' do + it_behaves_like 'a reference containing an element node' + it 'links to a User' do doc = reference_filter("Hey #{reference}") expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) @@ -89,6 +93,8 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end context 'mentioning a group' do + it_behaves_like 'a reference containing an element node' + let(:group) { create(:group) } let(:reference) { group.to_reference } diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb new file mode 100644 index 00000000000..2501b638774 --- /dev/null +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +describe Banzai::Pipeline::FullPipeline do + describe 'References' do + let(:project) { create(:empty_project, :public) } + let(:issue) { create(:issue, project: project) } + + it 'handles markdown inside a reference' do + markdown = "[some `code` inside](#{issue.to_reference})" + result = described_class.call(markdown, project: project) + link_content = result[:output].css('a').inner_html + expect(link_content).to eq('some <code>code</code> inside') + end + + it 'sanitizes reference HTML' do + link_label = '<script>bad things</script>' + markdown = "[#{link_label}](#{issue.to_reference})" + result = described_class.to_html(markdown, project: project) + expect(result).not_to include(link_label) + end + + it 'escapes the data-original attribute on a reference' do + markdown = %Q{[">bad things](#{issue.to_reference})} + result = described_class.to_html(markdown, project: project) + expect(result).to include(%{data-original='\">bad things'}) + end + end +end diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb index 254657a881d..6d2c141e18b 100644 --- a/spec/lib/banzai/redactor_spec.rb +++ b/spec/lib/banzai/redactor_spec.rb @@ -6,39 +6,60 @@ describe Banzai::Redactor do let(:redactor) { described_class.new(project, user) } describe '#redact' do - it 'redacts an Array of documents' do - doc1 = Nokogiri::HTML. - fragment('<a class="gfm" data-reference-type="issue">foo</a>') - - doc2 = Nokogiri::HTML. - fragment('<a class="gfm" data-reference-type="issue">bar</a>') - - expect(redactor).to receive(:nodes_visible_to_user).and_return([]) - - redacted_data = redactor.redact([doc1, doc2]) - - expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) - expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0]) - expect(doc1.to_html).to eq('foo') - expect(doc2.to_html).to eq('bar') + context 'when reference not visible to user' do + before do + expect(redactor).to receive(:nodes_visible_to_user).and_return([]) + end + + it 'redacts an array of documents' do + doc1 = Nokogiri::HTML. + fragment('<a class="gfm" data-reference-type="issue">foo</a>') + + doc2 = Nokogiri::HTML. + fragment('<a class="gfm" data-reference-type="issue">bar</a>') + + redacted_data = redactor.redact([doc1, doc2]) + + expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) + expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0]) + expect(doc1.to_html).to eq('foo') + expect(doc2.to_html).to eq('bar') + end + + it 'replaces redacted reference with inner HTML' do + doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue'>foo</a>") + redactor.redact([doc]) + expect(doc.to_html).to eq('foo') + end + + context 'when data-original attribute provided' do + let(:original_content) { '<code>foo</code>' } + it 'replaces redacted reference with original content' do + doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-original='#{original_content}'>bar</a>") + redactor.redact([doc]) + expect(doc.to_html).to eq(original_content) + end + end end - it 'does not redact an Array of documents' do - doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>' - doc1 = Nokogiri::HTML.fragment(doc1_html) + context 'when reference visible to user' do + it 'does not redact an array of documents' do + doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>' + doc1 = Nokogiri::HTML.fragment(doc1_html) - doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>' - doc2 = Nokogiri::HTML.fragment(doc2_html) + doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>' + doc2 = Nokogiri::HTML.fragment(doc2_html) - nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] } - expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten) + nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] } + expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten) - redacted_data = redactor.redact([doc1, doc2]) + redacted_data = redactor.redact([doc1, doc2]) - expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) - expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1]) - expect(doc1.to_html).to eq(doc1_html) - expect(doc2.to_html).to eq(doc2_html) + expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) + expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1]) + expect(doc1.to_html).to eq(doc1_html) + expect(doc2.to_html).to eq(doc2_html) + end end end diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb new file mode 100644 index 00000000000..d5d87310874 --- /dev/null +++ b/spec/lib/gitlab/utils_spec.rb @@ -0,0 +1,35 @@ +describe Gitlab::Utils, lib: true do + def to_boolean(value) + described_class.to_boolean(value) + end + + describe '.to_boolean' do + it 'accepts booleans' do + expect(to_boolean(true)).to be(true) + expect(to_boolean(false)).to be(false) + end + + it 'converts a valid string to a boolean' do + expect(to_boolean(true)).to be(true) + expect(to_boolean('true')).to be(true) + expect(to_boolean('YeS')).to be(true) + expect(to_boolean('t')).to be(true) + expect(to_boolean('1')).to be(true) + expect(to_boolean('ON')).to be(true) + + expect(to_boolean('FaLse')).to be(false) + expect(to_boolean('F')).to be(false) + expect(to_boolean('NO')).to be(false) + expect(to_boolean('n')).to be(false) + expect(to_boolean('0')).to be(false) + expect(to_boolean('oFF')).to be(false) + end + + it 'converts an invalid string to nil' do + expect(to_boolean('fals')).to be_nil + expect(to_boolean('yeah')).to be_nil + expect(to_boolean('')).to be_nil + expect(to_boolean(nil)).to be_nil + end + end +end diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 5363aea4d22..9041690023f 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -22,4 +22,18 @@ describe ProjectFeaturesCompatibility do expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED) end end + + it "converts fields from true to ProjectFeature::ENABLED" do + features.each do |feature| + project.update_attribute("#{feature}_enabled".to_sym, true) + expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED) + end + end + + it "converts fields from false to ProjectFeature::DISABLED" do + features.each do |feature| + project.update_attribute("#{feature}_enabled".to_sym, false) + expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED) + end + end end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index f6b2ec5ae31..68f72f5c86e 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -57,12 +57,12 @@ describe ProjectMember, models: true do it "creates an expired event when left due to expiry" do expired = create(:project_member, project: project, expires_at: Time.now - 6.days) expired.destroy - expect(Event.first.action).to eq(Event::EXPIRED) + expect(Event.recent.first.action).to eq(Event::EXPIRED) end it "creates a left event when left due to leave" do master.destroy - expect(Event.first.action).to eq(Event::LEFT) + expect(Event.recent.first.action).to eq(Event::LEFT) end it "destroys itself and delete associated todos" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f4dda1ee558..aef277357cf 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -67,11 +67,11 @@ describe Project, models: true do it { is_expected.to have_many(:notification_settings).dependent(:destroy) } it { is_expected.to have_many(:forks).through(:forked_project_links) } - context 'after create' do - it "creates project feature" do + context 'after initialized' do + it "has a project_feature" do project = FactoryGirl.build(:project) - expect { project.save }.to change{ project.project_feature.present? }.from(false).to(true) + expect(project.project_feature.present?).to be_present end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index b8204e1bf03..04b7d19d414 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -68,8 +68,8 @@ describe Repository, models: true do double_first = double(committed_date: Time.now) double_last = double(committed_date: Time.now - 1.second) - allow(tag_a).to receive(:target).and_return(double_first) - allow(tag_b).to receive(:target).and_return(double_last) + allow(tag_a).to receive(:dereferenced_target).and_return(double_first) + allow(tag_b).to receive(:dereferenced_target).and_return(double_last) allow(repository).to receive(:tags).and_return([tag_a, tag_b]) end @@ -83,8 +83,8 @@ describe Repository, models: true do double_first = double(committed_date: Time.now - 1.second) double_last = double(committed_date: Time.now) - allow(tag_a).to receive(:target).and_return(double_last) - allow(tag_b).to receive(:target).and_return(double_first) + allow(tag_a).to receive(:dereferenced_target).and_return(double_last) + allow(tag_b).to receive(:dereferenced_target).and_return(double_first) allow(repository).to receive(:tags).and_return([tag_a, tag_b]) end @@ -632,9 +632,9 @@ describe Repository, models: true do context "when the branch wasn't empty" do it 'updates the head' do - expect(repository.find_branch('feature').target.id).to eq(old_rev) + expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev) repository.update_branch_with_hooks(user, 'feature') { new_rev } - expect(repository.find_branch('feature').target.id).to eq(new_rev) + expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev) end end end @@ -659,7 +659,7 @@ describe Repository, models: true do context 'when the update would remove commits from the target branch' do it 'raises an exception' do branch = 'master' - old_rev = repository.find_branch(branch).target.sha + old_rev = repository.find_branch(branch).dereferenced_target.sha # The 'master' branch is NOT an ancestor of new_rev. expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 10c39b90212..d1ed774a914 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -599,6 +599,80 @@ describe User, models: true do end end + describe '.search_with_secondary_emails' do + def search_with_secondary_emails(query) + described_class.search_with_secondary_emails(query) + end + + let!(:user) { create(:user) } + let!(:email) { create(:email) } + + it 'returns users with a matching name' do + expect(search_with_secondary_emails(user.name)).to eq([user]) + end + + it 'returns users with a partially matching name' do + expect(search_with_secondary_emails(user.name[0..2])).to eq([user]) + end + + it 'returns users with a matching name regardless of the casing' do + expect(search_with_secondary_emails(user.name.upcase)).to eq([user]) + end + + it 'returns users with a matching email' do + expect(search_with_secondary_emails(user.email)).to eq([user]) + end + + it 'returns users with a partially matching email' do + expect(search_with_secondary_emails(user.email[0..2])).to eq([user]) + end + + it 'returns users with a matching email regardless of the casing' do + expect(search_with_secondary_emails(user.email.upcase)).to eq([user]) + end + + it 'returns users with a matching username' do + expect(search_with_secondary_emails(user.username)).to eq([user]) + end + + it 'returns users with a partially matching username' do + expect(search_with_secondary_emails(user.username[0..2])).to eq([user]) + end + + it 'returns users with a matching username regardless of the casing' do + expect(search_with_secondary_emails(user.username.upcase)).to eq([user]) + end + + it 'returns users with a matching whole secondary email' do + expect(search_with_secondary_emails(email.email)).to eq([email.user]) + end + + it 'returns users with a matching part of secondary email' do + expect(search_with_secondary_emails(email.email[1..4])).to eq([email.user]) + end + + it 'return users with a matching part of secondary email regardless of case' do + expect(search_with_secondary_emails(email.email[1..4].upcase)).to eq([email.user]) + expect(search_with_secondary_emails(email.email[1..4].downcase)).to eq([email.user]) + expect(search_with_secondary_emails(email.email[1..4].capitalize)).to eq([email.user]) + end + + it 'returns multiple users with matching secondary emails' do + email1 = create(:email, email: '1_testemail@example.com') + email2 = create(:email, email: '2_testemail@example.com') + email3 = create(:email, email: 'other@email.com') + email3.user.update_attributes!(email: 'another@mail.com') + + expect( + search_with_secondary_emails('testemail@example.com').map(&:id) + ).to include(email1.user.id, email2.user.id) + + expect( + search_with_secondary_emails('testemail@example.com').map(&:id) + ).not_to include(email3.user.id) + end + end + describe 'by_username_or_id' do let(:user1) { create(:user, username: 'foo') } diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb index f7fe4c10873..01bb9e955e0 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/api_helpers_spec.rb @@ -265,36 +265,6 @@ describe API::Helpers, api: true do end end - describe '.to_boolean' do - it 'accepts booleans' do - expect(to_boolean(true)).to be(true) - expect(to_boolean(false)).to be(false) - end - - it 'converts a valid string to a boolean' do - expect(to_boolean(true)).to be(true) - expect(to_boolean('true')).to be(true) - expect(to_boolean('YeS')).to be(true) - expect(to_boolean('t')).to be(true) - expect(to_boolean('1')).to be(true) - expect(to_boolean('ON')).to be(true) - - expect(to_boolean('FaLse')).to be(false) - expect(to_boolean('F')).to be(false) - expect(to_boolean('NO')).to be(false) - expect(to_boolean('n')).to be(false) - expect(to_boolean('0')).to be(false) - expect(to_boolean('oFF')).to be(false) - end - - it 'converts an invalid string to nil' do - expect(to_boolean('fals')).to be_nil - expect(to_boolean('yeah')).to be_nil - expect(to_boolean('')).to be_nil - expect(to_boolean(nil)).to be_nil - end - end - describe '.handle_api_exception' do before do allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true) diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 905f762d578..1711096f4bd 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -95,18 +95,6 @@ describe API::API, api: true do expect(json_response['developers_can_push']).to eq(true) expect(json_response['developers_can_merge']).to eq(true) end - - it 'protects a single branch and developers cannot push and merge' do - put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), - developers_can_push: 'tru', developers_can_merge: 'tr' - - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(branch_name) - expect(json_response['commit']['id']).to eq(branch_sha) - expect(json_response['protected']).to eq(true) - expect(json_response['developers_can_push']).to eq(false) - expect(json_response['developers_can_merge']).to eq(false) - end end context 'for an existing protected branch' do diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index a4fcd44882d..0879e3ab4c8 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -37,65 +37,138 @@ describe GitTagPushService, services: true do end describe "Git Tag Push Data" do - before do - service.execute - @push_data = service.push_data - @tag_name = Gitlab::Git.ref_name(ref) - @tag = project.repository.find_tag(@tag_name) - @commit = project.commit(@tag.target) - end - subject { @push_data } + let(:tag) { project.repository.find_tag(tag_name) } + let(:commit) { tag.dereferenced_target } - it { is_expected.to include(object_kind: 'tag_push') } - it { is_expected.to include(ref: ref) } - it { is_expected.to include(before: oldrev) } - it { is_expected.to include(after: newrev) } - it { is_expected.to include(message: @tag.message) } - it { is_expected.to include(user_id: user.id) } - it { is_expected.to include(user_name: user.name) } - it { is_expected.to include(project_id: project.id) } - - context "with repository data" do - subject { @push_data[:repository] } - - it { is_expected.to include(name: project.name) } - it { is_expected.to include(url: project.url_to_repo) } - it { is_expected.to include(description: project.description) } - it { is_expected.to include(homepage: project.web_url) } - end + context 'annotated tag' do + let(:tag_name) { Gitlab::Git.ref_name(ref) } - context "with commits" do - subject { @push_data[:commits] } + before do + service.execute + @push_data = service.push_data + end - it { is_expected.to be_an(Array) } - it 'has 1 element' do - expect(subject.size).to eq(1) + it { is_expected.to include(object_kind: 'tag_push') } + it { is_expected.to include(ref: ref) } + it { is_expected.to include(before: oldrev) } + it { is_expected.to include(after: newrev) } + it { is_expected.to include(message: tag.message) } + it { is_expected.to include(user_id: user.id) } + it { is_expected.to include(user_name: user.name) } + it { is_expected.to include(project_id: project.id) } + + context "with repository data" do + subject { @push_data[:repository] } + + it { is_expected.to include(name: project.name) } + it { is_expected.to include(url: project.url_to_repo) } + it { is_expected.to include(description: project.description) } + it { is_expected.to include(homepage: project.web_url) } end - context "the commit" do - subject { @push_data[:commits].first } - - it { is_expected.to include(id: @commit.id) } - it { is_expected.to include(message: @commit.safe_message) } - it { is_expected.to include(timestamp: @commit.date.xmlschema) } - it do - is_expected.to include( - url: [ - Gitlab.config.gitlab.url, - project.namespace.to_param, - project.to_param, - 'commit', - @commit.id - ].join('/') - ) + context "with commits" do + subject { @push_data[:commits] } + + it { is_expected.to be_an(Array) } + it 'has 1 element' do + expect(subject.size).to eq(1) + end + + context "the commit" do + subject { @push_data[:commits].first } + + it { is_expected.to include(id: commit.id) } + it { is_expected.to include(message: commit.safe_message) } + it { is_expected.to include(timestamp: commit.date.xmlschema) } + it do + is_expected.to include( + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + commit.id + ].join('/') + ) + end + + context "with a author" do + subject { @push_data[:commits].first[:author] } + + it { is_expected.to include(name: commit.author_name) } + it { is_expected.to include(email: commit.author_email) } + end end + end + end - context "with a author" do - subject { @push_data[:commits].first[:author] } + context 'lightweight tag' do + let(:tag_name) { 'light-tag' } + let(:newrev) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } + let(:ref) { "refs/tags/light-tag" } + + before do + # Create the lightweight tag + project.repository.raw_repository.rugged.tags.create(tag_name, newrev) + + # Clear tag list cache + project.repository.expire_tags_cache + + service.execute + @push_data = service.push_data + end + + it { is_expected.to include(object_kind: 'tag_push') } + it { is_expected.to include(ref: ref) } + it { is_expected.to include(before: oldrev) } + it { is_expected.to include(after: newrev) } + it { is_expected.to include(message: tag.message) } + it { is_expected.to include(user_id: user.id) } + it { is_expected.to include(user_name: user.name) } + it { is_expected.to include(project_id: project.id) } + + context "with repository data" do + subject { @push_data[:repository] } + + it { is_expected.to include(name: project.name) } + it { is_expected.to include(url: project.url_to_repo) } + it { is_expected.to include(description: project.description) } + it { is_expected.to include(homepage: project.web_url) } + end + + context "with commits" do + subject { @push_data[:commits] } + + it { is_expected.to be_an(Array) } + it 'has 1 element' do + expect(subject.size).to eq(1) + end - it { is_expected.to include(name: @commit.author_name) } - it { is_expected.to include(email: @commit.author_email) } + context "the commit" do + subject { @push_data[:commits].first } + + it { is_expected.to include(id: commit.id) } + it { is_expected.to include(message: commit.safe_message) } + it { is_expected.to include(timestamp: commit.date.xmlschema) } + it do + is_expected.to include( + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + commit.id + ].join('/') + ) + end + + context "with a author" do + subject { @push_data[:commits].first[:author] } + + it { is_expected.to include(name: commit.author_name) } + it { is_expected.to include(email: commit.author_email) } + end end end end diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb new file mode 100644 index 00000000000..0670ac2faa2 --- /dev/null +++ b/spec/services/members/create_service_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Members::CreateService, services: true do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + let(:project_user) { create(:user) } + + before { project.team << [user, :master] } + + it 'adds user to members' do + params = { user_ids: project_user.id.to_s, access_level: Gitlab::Access::GUEST } + result = described_class.new(project, user, params).execute + + expect(result).to be_truthy + expect(project.users).to include project_user + end + + it 'adds no user to members' do + params = { user_ids: '', access_level: Gitlab::Access::GUEST } + result = described_class.new(project, user, params).execute + + expect(result).to be_falsey + expect(project.users).not_to include project_user + end +end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 3a3f07ddcb9..3f5df049ea2 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -25,6 +25,8 @@ describe MergeRequests::BuildService, services: true do before do allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare) + allow(project).to receive(:commit).and_return(commit_1) + allow(project).to receive(:commit).and_return(commit_2) end describe 'execute' do @@ -193,5 +195,52 @@ describe MergeRequests::BuildService, services: true do end end end + + context 'source branch does not exist' do + before do + allow(project).to receive(:commit).with(source_branch).and_return(nil) + allow(project).to receive(:commit).with(target_branch).and_return(commit_1) + end + + 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('Source branch "feature-branch" does not exist') + end + end + + context 'target branch does not exist' do + before do + allow(project).to receive(:commit).with(source_branch).and_return(commit_1) + allow(project).to receive(:commit).with(target_branch).and_return(nil) + end + + 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('Target branch "master" does not exist') + end + end + + context 'both source and target branches do not exist' do + before do + allow(project).to receive(:commit).and_return(nil) + end + + it 'forbids the merge request from being created' do + expect(merge_request.can_be_created).to eq(false) + end + + it 'adds both error messages to the merge request' do + expect(merge_request.errors).to contain_exactly( + 'Source branch "feature-branch" does not exist', + 'Target branch "master" does not exist' + ) + end + end end end diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index 5d400299be0..92b84308f73 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -18,7 +18,7 @@ describe Milestones::CloseService, services: true do it { expect(milestone).to be_closed } describe :event do - let(:event) { Event.first } + let(:event) { Event.recent.first } it { expect(event.milestone).to be_truthy } it { expect(event.target).to eq(milestone) } diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 3ea1273abc3..876bfaf085c 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -69,7 +69,7 @@ describe Projects::CreateService, services: true do context 'wiki_enabled false does not create wiki repository directory' do before do - @opts.merge!( { project_feature_attributes: { wiki_access_level: ProjectFeature::DISABLED } }) + @opts.merge!(wiki_enabled: false) @project = create_project(@user, @opts) @path = ProjectWiki.new(@project, @user).send(:path_to_repo) end diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb new file mode 100644 index 00000000000..eb5da662ab5 --- /dev/null +++ b/spec/support/banzai/reference_filter_shared_examples.rb @@ -0,0 +1,13 @@ +# Specs for reference links containing HTML. +# +# Requires a reference: +# let(:reference) { '#42' } +shared_examples 'a reference containing an element node' do + let(:inner_html) { 'element <code>node</code> inside' } + let(:reference_with_element) { %{<a href="#{reference}">#{inner_html}</a>} } + + it 'does not escape inner html' do + doc = reference_filter(reference_with_element) + expect(doc.children.first.inner_html).to eq(inner_html) + end +end diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb new file mode 100644 index 00000000000..16bf0698c4b --- /dev/null +++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe 'projects/commit/_commit_box.html.haml' do + include Devise::Test::ControllerHelpers + + let(:project) { create(:project) } + + before do + assign(:project, project) + assign(:commit, project.commit) + end + + it 'shows the commit SHA' do + render + + expect(rendered).to have_text("Commit #{Commit.truncate_sha(project.commit.sha)}") + end + + it 'shows the last pipeline that ran for the commit' do + create(:ci_pipeline, project: project, sha: project.commit.id, status: 'success') + create(:ci_pipeline, project: project, sha: project.commit.id, status: 'canceled') + third_pipeline = create(:ci_pipeline, project: project, sha: project.commit.id, status: 'failed') + + render + + expect(rendered).to have_text("Pipeline ##{third_pipeline.id} for #{Commit.truncate_sha(project.commit.sha)} failed") + end +end diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb index c8a3d02d8fd..889d9a38887 100644 --- a/spec/views/projects/issues/_related_branches.html.haml_spec.rb +++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb @@ -5,7 +5,7 @@ describe 'projects/issues/_related_branches' do let(:project) { create(:project) } let(:branch) { project.repository.find_branch('feature') } - let!(:pipeline) { create(:ci_pipeline, project: project, sha: branch.target.id, ref: 'feature') } + let!(:pipeline) { create(:ci_pipeline, project: project, sha: branch.dereferenced_target.id, ref: 'feature') } before do assign(:project, project) diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb new file mode 100644 index 00000000000..6d42946de38 --- /dev/null +++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe RemoveUnreferencedLfsObjectsWorker do + let(:worker) { RemoveUnreferencedLfsObjectsWorker.new } + + describe '#perform' do + let!(:unreferenced_lfs_object1) { create(:lfs_object, oid: '1') } + let!(:unreferenced_lfs_object2) { create(:lfs_object, oid: '2') } + let!(:project1) { create(:empty_project, lfs_enabled: true) } + let!(:project2) { create(:empty_project, lfs_enabled: true) } + let!(:referenced_lfs_object1) { create(:lfs_object, oid: '3') } + let!(:referenced_lfs_object2) { create(:lfs_object, oid: '4') } + let!(:lfs_objects_project1_1) do + create(:lfs_objects_project, + project: project1, + lfs_object: referenced_lfs_object1 + ) + end + let!(:lfs_objects_project2_1) do + create(:lfs_objects_project, + project: project2, + lfs_object: referenced_lfs_object1 + ) + end + let!(:lfs_objects_project1_2) do + create(:lfs_objects_project, + project: project1, + lfs_object: referenced_lfs_object2 + ) + end + + it 'removes unreferenced lfs objects' do + worker.perform + + expect(LfsObject.where(id: unreferenced_lfs_object1.id)).to be_empty + expect(LfsObject.where(id: unreferenced_lfs_object2.id)).to be_empty + end + + it 'leaves referenced lfs objects' do + worker.perform + + expect(referenced_lfs_object1.reload).to be_present + expect(referenced_lfs_object2.reload).to be_present + end + + it 'removes unreferenced lfs objects after project removal' do + project1.destroy + + worker.perform + + expect(referenced_lfs_object1.reload).to be_present + expect(LfsObject.where(id: referenced_lfs_object2.id)).to be_empty + end + end +end |