summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md402
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/application.js3
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js5
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js.es62
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js2
-rw-r--r--app/assets/javascripts/network/network_bundle.js2
-rw-r--r--app/assets/javascripts/star.js2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/awards.scss8
-rw-r--r--app/assets/stylesheets/pages/commit.scss62
-rw-r--r--app/assets/stylesheets/pages/commits.scss31
-rw-r--r--app/assets/stylesheets/pages/login.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss5
-rw-r--r--app/assets/stylesheets/pages/todos.scss60
-rw-r--r--app/controllers/admin/users_controller.rb2
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/project_members_controller.rb17
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb3
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/helpers/button_helper.rb3
-rw-r--r--app/helpers/events_helper.rb6
-rw-r--r--app/helpers/projects_helper.rb6
-rw-r--r--app/models/concerns/project_features_compatibility.rb2
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/group_label.rb4
-rw-r--r--app/models/label.rb30
-rw-r--r--app/models/lfs_object.rb6
-rw-r--r--app/models/project.rb7
-rw-r--r--app/models/project_label.rb4
-rw-r--r--app/models/repository.rb24
-rw-r--r--app/models/user.rb18
-rw-r--r--app/services/delete_branch_service.rb2
-rw-r--r--app/services/delete_tag_service.rb2
-rw-r--r--app/services/git_tag_push_service.rb4
-rw-r--r--app/services/members/create_service.rb16
-rw-r--r--app/services/merge_requests/build_service.rb50
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml147
-rw-r--r--app/views/groups/labels/index.html.haml2
-rw-r--r--app/views/projects/boards/components/_board.html.haml4
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml4
-rw-r--r--app/views/projects/buttons/_star.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml74
-rw-r--r--app/views/projects/commit/builds.html.haml3
-rw-r--r--app/views/projects/commit/pipelines.html.haml3
-rw-r--r--app/views/projects/commit/show.html.haml3
-rw-r--r--app/views/projects/diffs/_file.html.haml1
-rw-r--r--app/views/projects/diffs/_file_header.html.haml2
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml4
-rw-r--r--app/views/projects/tags/_tag.html.haml2
-rw-r--r--app/views/shared/_event_filter.html.haml9
-rw-r--r--app/views/shared/_label.html.haml13
-rw-r--r--app/views/shared/empty_states/_todos_all_done.svg1
-rw-r--r--app/views/shared/empty_states/_todos_empty.svg110
-rw-r--r--app/workers/project_web_hook_worker.rb2
-rw-r--r--app/workers/remove_unreferenced_lfs_objects_worker.rb8
-rw-r--r--config/dependency_decisions.yml7
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--config/initializers/sidekiq.rb16
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/projects.md6
-rw-r--r--doc/api/tags.md2
-rw-r--r--doc/development/frontend.md11
-rw-r--r--doc/development/gotchas.md6
-rw-r--r--doc/development/licensing.md3
-rw-r--r--doc/development/testing.md4
-rw-r--r--doc/integration/saml.md21
-rw-r--r--features/steps/project/commits/commits.rb4
-rw-r--r--lib/api/branches.rb123
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/helpers.rb10
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb20
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb11
-rw-r--r--lib/banzai/filter/reference_filter.rb6
-rw-r--r--lib/banzai/filter/user_reference_filter.rb41
-rw-r--r--lib/banzai/redactor.rb8
-rw-r--r--lib/gitlab/data_builder/push.rb2
-rw-r--r--lib/gitlab/utils.rb8
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb68
-rw-r--r--spec/features/boards/boards_spec.rb8
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb8
-rw-r--r--spec/features/projects/features_visibility_spec.rb34
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb44
-rw-r--r--spec/features/todos/todos_spec.rb8
-rw-r--r--spec/finders/branches_finder_spec.rb2
-rw-r--r--spec/finders/tags_finder_spec.rb2
-rw-r--r--spec/javascripts/spec_helper.js2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb38
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb28
-rw-r--r--spec/lib/banzai/redactor_spec.rb75
-rw-r--r--spec/lib/gitlab/utils_spec.rb35
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb14
-rw-r--r--spec/models/members/project_member_spec.rb4
-rw-r--r--spec/models/project_spec.rb6
-rw-r--r--spec/models/repository_spec.rb14
-rw-r--r--spec/models/user_spec.rb74
-rw-r--r--spec/requests/api/api_helpers_spec.rb30
-rw-r--r--spec/requests/api/branches_spec.rb12
-rw-r--r--spec/services/git_tag_push_service_spec.rb173
-rw-r--r--spec/services/members/create_service_spec.rb25
-rw-r--r--spec/services/merge_requests/build_service_spec.rb49
-rw-r--r--spec/services/milestones/close_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb2
-rw-r--r--spec/support/banzai/reference_filter_shared_examples.rb13
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb28
-rw-r--r--spec/views/projects/issues/_related_branches.html.haml_spec.rb2
-rw-r--r--spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb55
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
diff --git a/Gemfile b/Gemfile
index 5f16cc063e2..7e94c4051da 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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='\"&gt;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