diff --git a/CHANGELOG.md b/CHANGELOG.md index efd32d44890fe69113e78d01ad8decaeb6d23f34..d174f6e70b7fc8b8b001ed07c6c69fbf9973841c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,302 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.1.6 (2018-01-11) + +### Security (8 changes, 1 of them is from the community) + +- Fix writable shared deploy keys. +- Filter out sensitive fields from the project services API. (Robert Schilling) +- Fix RCE via project import mechanism. +- Prevent OAuth login POST requests when a provider has been disabled. +- Prevent a SQL injection in the MilestonesFinder. +- Check user authorization for source and target projects when creating a merge request. +- Fix path traversal in gitlab-ci.yml cache:key. +- Fix XSS vulnerability in pipeline job trace. + + +## 10.1.5 (2017-12-07) + +### Security (5 changes) + +- Fix e-mail address disclosure through member search fields +- Prevent creating issues through API when user does not have permissions +- Prevent an information disclosure in the Groups API +- Fix user without access to private Wiki being able to see it on the project page +- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment + +## 10.1.4 (2017-11-14) + +### Fixed (4 changes) + +- Don't try to create fork network memberships for forks with a missing source. !15366 +- Formats bytes to human reabale number in registry table. +- Prevent error when authorizing an admin-created OAauth application without a set owner. +- Prevents position update for image diff notes. + + +## 10.1.3 (2017-11-10) + +- [SECURITY] Prevent OAuth phishing attack by presenting detailed wording about app to user during authorization. +- [FIXED] Fix cancel button not working while uploading on the new issue page. !15137 +- [FIXED] Fix webhooks recent deliveries. !15146 (Alexander Randa (@randaalex)) +- [FIXED] Fix issues with forked projects of which the source was deleted. !15150 +- [FIXED] Fix GPG signature popup info in Safari and Firefox. !15228 +- [FIXED] Make sure group and project creation is blocked for new users that are external by default. +- [FIXED] Fix arguments Import/Export error importing project merge requests. +- [FIXED] Fix diff parser so it tolerates to diff special markers in the content. +- [FIXED] Fix a migration that adds merge_requests_ff_only_enabled column to MR table. +- [FIXED] Render 404 when polling commit notes without having permissions. +- [FIXED] Show error message when fast-forward merge is not possible. +- [FIXED] Avoid regenerating the ref path for the environment. +- [PERFORMANCE] Remove Filesystem check metrics that use too much CPU to handle requests. + +## 10.1.2 (2017-11-08) + +- [SECURITY] Add X-Content-Type-Options header in API responses to make it more difficult to find other vulnerabilities. +- [SECURITY] Properly translate IP addresses written in decimal, octal, or other formats in SSRF protections in project imports. +- [FIXED] Fix TRIGGER checks for MySQL. + +## 10.1.1 (2017-10-31) + +- [FIXED] Auto Devops kubernetes default namespace is now correctly built out of gitlab project group-name. !14642 (Mircea Danila Dumitrescu) +- [FIXED] Forbid the usage of `Redis#keys`. !14889 +- [FIXED] Make the circuitbreaker more robust by adding higher thresholds, and multiple access attempts. !14933 +- [FIXED] Only cache last push event for existing projects when pushing to a fork. !14989 +- [FIXED] Fix bug preventing secondary emails from being confirmed. !15010 +- [FIXED] Fix broken wiki pages that link to a wiki file. !15019 +- [FIXED] Don't rename paths that were freed up when upgrading. !15029 +- [FIXED] Fix bitbucket login. !15051 +- [FIXED] Update gitaly in Gitlab 10.1 to 0.43.1 for temp file cleanup. !15055 +- [FIXED] Use the correct visibility attribute for projects in system hooks. !15065 +- [FIXED] Normalize LDAP DN when looking up identity. +- [FIXED] Adds callback functions for initial request in clusters page. +- [FIXED] Fix missing Import/Export issue assignees. +- [FIXED] Allow boards as top level route. +- [FIXED] Fix widget of locked merge requests not being presented. +- [FIXED] Fix editing issue description in mobile view. +- [FIXED] Fix deletion of container registry or images returning an error. +- [FIXED] Fix the writing of invalid environment refs. +- [CHANGED] Store circuitbreaker settings in the database instead of config. !14842 +- [CHANGED] Update default disabled merge request widget message to reflect a general failure. !14960 +- [PERFORMANCE] Stop merge requests with thousands of commits from timing out. !15063 + +## 10.1.0 (2017-10-22) + +- [SECURITY] Use a timeout on certain git operations. !14872 +- [SECURITY] Move project repositories between namespaces when renaming users. +- [SECURITY] Prevent an open redirect on project pages. +- [SECURITY] Prevent a persistent XSS in user-provided markup. +- [REMOVED] Remove the ability to visit the issue edit form directly. !14523 +- [REMOVED] Remove animate.js and label animation. +- [FIXED] Perform prometheus data endpoint requests in parallel. !14003 +- [FIXED] Escape quotes in git username. !14020 (Brandon Everett) +- [FIXED] Fixed non-UTF-8 valid branch names from causing an error. !14090 +- [FIXED] Read import sources from setting at first initialization. !14141 (Visay Keo) +- [FIXED] Display full pre-receive and post-receive hook output in GitLab UI. !14222 (Robin Bobbitt) +- [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258 +- [FIXED] Fix the default branches sorting to actually be 'Last updated'. !14295 +- [FIXED] Fixes project denial of service via gitmodules using Extended ASCII. !14301 +- [FIXED] Fix the filesystem shard health check to check all configured shards. !14341 +- [FIXED] Compare email addresses case insensitively when verifying GPG signatures. !14376 (Tim Bishop) +- [FIXED] Allow the git circuit breaker to correctly handle missing repository storages. !14417 +- [FIXED] Fix `rake gitlab:incoming_email:check` and make it report the actual error. !14423 +- [FIXED] Does not check if an invariant hashed storage path exists on disk when renaming projects. !14428 +- [FIXED] Also reserve refs/replace after importing a project. !14436 +- [FIXED] Fix profile image orientation based on EXIF data gvieira37. !14461 (gvieira37) +- [FIXED] Move the deployment flag content to the left when deployment marker is near the end. !14514 +- [FIXED] Fix notes type created from import. This should fix some missing notes issues from imported projects. !14524 +- [FIXED] Fix bottom spacing for dropdowns that open upwards. !14535 +- [FIXED] Adjusts tag link to avoid underlining spaces. !14544 (Guilherme Vieira) +- [FIXED] Add missing space in Sidekiq memory killer log message. !14553 (Benjamin Drung) +- [FIXED] Ensure no exception is raised when Raven tries to get the current user in API context. !14580 +- [FIXED] Fix edit project service cancel button position. !14596 (Matt Coleman) +- [FIXED] Fix case sensitive email confirmation on signup. !14606 (robdel12) +- [FIXED] Whitelist authorized_keys.lock in the gitlab:check rake task. !14624 +- [FIXED] Allow merge in MR widget with no pipeline but using "Only allow merge requests to be merged if the pipeline succeeds". !14633 +- [FIXED] Fix navigation dropdown close animation on mobile screens. !14649 +- [FIXED] Fix the project import with issues and milestones. !14657 +- [FIXED] Use explicit boolean true attribute for show-disabled-button in Vue files. !14672 +- [FIXED] Make tabs on top scrollable on admin dashboard. !14685 (Takuya Noguchi) +- [FIXED] Fix broken Y-axis scaling in some Prometheus graphs. !14693 +- [FIXED] Search or compare LDAP DNs case-insensitively and ignore excess whitespace. !14697 +- [FIXED] Allow prometheus graphs to correctly handle NaN values. !14741 +- [FIXED] Don't show an "Unsubscribe" link in snippet comment notifications. !14764 +- [FIXED] Fixed duplicate notifications when added multiple labels on an issue. !14798 +- [FIXED] Fix alignment for indeterminate marker in dropdowns. !14809 +- [FIXED] Fix error when updating a forked project with deleted `ForkedProjectLink`. !14916 +- [FIXED] Correctly render asset path for locales with a region. !14924 +- [FIXED] Fix the external URLs generated for online view of HTML artifacts. !14977 +- [FIXED] Reschedule merge request diff background migrations to catch failures from 9.5 run. +- [FIXED] fix merge request widget status icon for failed CI. +- [FIXED] Fix the number representing the amount of commits related to a push event. +- [FIXED] Sync up hover and legend data across all graphs for the prometheus dashboard. +- [FIXED] Fixes mini pipeline graph in commit view. +- [FIXED] Fix comment deletion confirmation dialog typo. +- [FIXED] Fix project snippets breadcrumb link. +- [FIXED] Make usage ping scheduling more robust. +- [FIXED] Make "merge ongoing" check more consistent. +- [FIXED] Add 1000+ counters to job page. +- [FIXED] Fixed issue/merge request breadcrumb titles not having links. +- [FIXED] Fixed commit avatars being centered vertically. +- [FIXED] Tooltips in the commit info box now all face the same direction. (Jedidiah Broadbent) +- [FIXED] Fixed navbar title colors leaking out of the navbar. +- [FIXED] Fix bug that caused merge requests with diff notes imported from Bitbucket to raise errors. +- [FIXED] Correctly detect multiple issue URLs after 'Closes...' in MR descriptions. +- [FIXED] Set default scope on PATs that don't have one set to allow them to be revoked. +- [FIXED] Fix application setting to cache nil object. +- [FIXED] Fix image diff swipe handle offset to correctly align with the frame. +- [FIXED] Force non diff resolved discussion to display when collapse toggled. +- [FIXED] Fix resolved discussions not expanding on side by side view. +- [FIXED] Fixed the sidebar scrollbar overlapping links. +- [FIXED] Issue board tooltips are now the correct width when the column is collapsed. (Jedidiah Broadbent) +- [FIXED] Improve autodevops banner UX and render it only in project page. +- [FIXED] Fix typo in cycle analytics breaking time component. +- [FIXED] Force two up view to load by default for image diffs. +- [FIXED] Fixed milestone breadcrumb links. +- [FIXED] Fixed group sort dropdown defaulting to empty. +- [FIXED] Fixed notes not being scrolled to in merge requests. +- [FIXED] Adds Event polyfill for IE11. +- [FIXED] Update native unicode emojis to always render as normal text (previously could render italicized). (Branka Martinovic) +- [FIXED] Sort JobsController by id, not created_at. +- [FIXED] Fix revision and total size missing for Container Registry. +- [FIXED] Fixed milestone issuable assignee link URL. +- [FIXED] Fixed breadcrumbs container expanding in side-by-side diff view. +- [FIXED] Fixed merge request widget merged & closed date tooltip text. +- [FIXED] Prevent creating multiple ApplicationSetting instances. +- [FIXED] Fix username and ID not logging in production_json.log for Git activity. +- [FIXED] Make Redcarpet Markdown renderer thread-safe. +- [FIXED] Two factor auth messages in settings no longer overlap the button. (Jedidiah Broadbent) +- [FIXED] Made the "remember me" check boxes have consistent styles and alignment. (Jedidiah Broadbent) +- [FIXED] Prevent branches or tags from starting with invalid characters (e.g. -, .). +- [DEPRECATED] Removed two legacy config options. (Daniel Voogsgerd) +- [CHANGED] Show notes number more user-friendly in the graph. !13949 (Vladislav Kaverin) +- [CHANGED] Link SAML users to LDAP by email. !14216 +- [CHANGED] Display whether branch has been merged when deleting protected branch. !14220 +- [CHANGED] Make the labels in the Compare form less confusing. !14225 +- [CHANGED] Confirmation email shows link as text instead of human readable text. !14243 (bitsapien) +- [CHANGED] Return only group's members in user dropdowns on issuables list pages. !14249 +- [CHANGED] Added defaults for protected branches dropdowns on the repository settings. !14278 +- [CHANGED] Show confirmation modal before deleting account. !14360 +- [CHANGED] Allow creating merge requests across a fork network. !14422 +- [CHANGED] Re-arrange @@ -51,20 +58,22 @@
{{ deployKey.fingerprint }}
-
- Write access allowed -
- {{ project.full_name }} + {{ deployKeysProject.project.full_name }} +
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 1edd460f380228691a286519c4e2dd2c130fc9d2..d7995b59a7bae4d1529f8f04ad1116a986604fa7 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -161,9 +161,6 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } - if (page === 'projects:merge_requests:index') { - new UserCallout({ setCalloutPerProject: true }); - } const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; IssuableIndex.init(pagePrefix); @@ -345,7 +342,10 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); - new UserCallout({ setCalloutPerProject: true }); + new UserCallout({ + setCalloutPerProject: true, + className: 'js-autodevops-banner', + }); if ($('#tree-slider').length) new TreeView(); if ($('.blob-viewer').length) new BlobViewer(); @@ -365,9 +365,6 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; case 'projects:pipelines:new': new NewBranchForm($('.js-new-pipeline-form')); break; - case 'projects:pipelines:index': - new UserCallout({ setCalloutPerProject: true }); - break; case 'projects:pipelines:builds': case 'projects:pipelines:failures': case 'projects:pipelines:show': @@ -425,7 +422,6 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; new TreeView(); new BlobViewer(); new NewCommitForm($('.js-create-dir-form')); - new UserCallout({ setCalloutPerProject: true }); $('#tree-slider').waitForImages(function() { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 1cba65d17cdcc9c6bb7b7dbda5b55eb819d7cecb..60d31bf739601c87c13b7b8744b4a8299e6c51b6 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -127,11 +127,9 @@ window.DropzoneInput = (function() { // removeAllFiles(true) stops uploading files (if any) // and remove them from dropzone files queue. $cancelButton.on('click', (e) => { - const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone'); - e.preventDefault(); e.stopPropagation(); - Dropzone.forElement(target).removeAllFiles(true); + Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true); }); // If 'error' event is fired, we store a failed files, @@ -237,9 +235,12 @@ window.DropzoneInput = (function() { }; const insertToTextArea = function(filename, url) { - return $(child).val(function(index, val) { + const $child = $(child); + $child.val(function(index, val) { return val.replace(`{{${filename}}}`, url); }); + + $child.trigger('change'); }; const appendToTextArea = function(url) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 28e8240169d1f2e66bf46d17a97c69456a8681b7..55c153a6d46c11489e940b63491734a97edebb73 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -123,8 +123,8 @@ class FilteredSearchVisualTokens { /* eslint-disable no-param-reassign */ tokenValueContainer.dataset.originalValue = tokenValue; tokenValueElement.innerHTML = ` - ${user.name}'s avatar - ${user.name} + + ${_.escape(user.name)} `; /* eslint-enable no-param-reassign */ }) diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index d479f7ed6824a73a9077c9edcedbba9305f42653..bb81a383d8a37a7c5d090a4db57efffd492bb64e 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -231,7 +231,7 @@ import CreateLabelDropdown from './create_label'; selectedClass.push('label-item'); $a.attr('data-label-id', label.id); } - $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title); + $a.addClass(selectedClass.join(' ')).html(`${colorEl} ${_.escape(label.title)}`); // Return generated html return $li.html($a).prop('outerHTML'); }, diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js new file mode 100644 index 0000000000000000000000000000000000000000..45bff245827f71d7fa7a363bd81cd0df195932c2 --- /dev/null +++ b/app/assets/javascripts/lib/utils/axios_utils.js @@ -0,0 +1,6 @@ +import axios from 'axios'; +import csrf from './csrf'; + +export default function setAxiosCsrfToken() { + axios.defaults.headers.common[csrf.headerKey] = csrf.token; +} diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index 917a45eb06b7442df16c9f64b2a1a5ae923ff350..a02c79b787eed9e31c9d8b21473225ecfeb9a0a3 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -52,3 +52,31 @@ export function bytesToKiB(number) { export function bytesToMiB(number) { return number / (BYTES_IN_KIB * BYTES_IN_KIB); } + +/** + * Utility function that calculates GiB of the given bytes. + * @param {Number} number + * @returns {Number} + */ +export function bytesToGiB(number) { + return number / (BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB); +} + +/** + * Port of rails number_to_human_size + * Formats the bytes in number into a more understandable + * representation (e.g., giving it 1500 yields 1.5 KB). + * + * @param {Number} size + * @returns {String} + */ +export function numberToHumanSize(size) { + if (size < BYTES_IN_KIB) { + return `${size} bytes`; + } else if (size < BYTES_IN_KIB * BYTES_IN_KIB) { + return `${bytesToKiB(size).toFixed(2)} KiB`; + } else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) { + return `${bytesToMiB(size).toFixed(2)} MiB`; + } + return `${bytesToGiB(size).toFixed(2)} GiB`; +} diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue index 82c51a1068c6ae00c9664919e08222beaa45bec2..721753af59573958d8f891a8d08654d1d096d6e3 100644 --- a/app/assets/javascripts/notebook/cells/markdown.vue +++ b/app/assets/javascripts/notebook/cells/markdown.vue @@ -1,6 +1,7 @@ diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 7f972b6f6ee872578fc58acad1bead0e4c50b08d..3ecc0c2a6e58831617695189ed79faed9327f38b 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -29,6 +29,12 @@ const bindEvents = () => { const $newProjectForm = $('#new_project'); const $projectImportUrl = $('#project_import_url'); const $projectPath = $('#project_path'); + const $useTemplateBtn = $('.template-button > input'); + const $projectFieldsForm = $('.project-fields-form'); + const $selectedTemplateText = $('.selected-template'); + const $changeTemplateBtn = $('.change-template'); + const $selectedIcon = $('.selected-icon svg'); + const $templateProjectNameInput = $('#template-project-name #project_path'); if ($newProjectForm.length !== 1) { return; @@ -48,6 +54,40 @@ const bindEvents = () => { $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`); }); + function chooseTemplate() { + $('.template-option').hide(); + $projectFieldsForm.addClass('selected'); + $selectedIcon.removeClass('active'); + const value = $(this).val(); + const templates = { + rails: { + text: 'Ruby on Rails', + icon: '.selected-icon .icon-rails', + }, + express: { + text: 'NodeJS Express', + icon: '.selected-icon .icon-node-express', + }, + spring: { + text: 'Spring', + icon: '.selected-icon .icon-java-spring', + }, + }; + + const selectedTemplate = templates[value]; + $selectedTemplateText.text(selectedTemplate.text); + $(selectedTemplate.icon).addClass('active'); + $templateProjectNameInput.focus(); + } + + $useTemplateBtn.on('change', chooseTemplate); + + $changeTemplateBtn.on('click', () => { + $('.template-option').show(); + $projectFieldsForm.removeClass('selected'); + $useTemplateBtn.prop('checked', false); + }); + $newProjectForm.on('submit', () => { $projectPath.val($projectPath.val().trim()); }); diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index 41ea9742406773460a9b53490117ab85f6351a43..ac1c3ec253c4dec54d78565a6daa34c56258e04c 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -57,7 +57,7 @@ }, showError(message) { - Flash((errorMessages[message])); + Flash(errorMessages[message]); }, }, }; diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index 4ce1571b0aa38132c37f234ee8cf60f6ba805aa9..14d43e135fec3da57a89cfefda42ccbc5a58a3f1 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -8,6 +8,7 @@ import tooltip from '../../vue_shared/directives/tooltip'; import timeagoMixin from '../../vue_shared/mixins/timeago'; import { errorMessages, errorMessagesTypes } from '../constants'; + import { numberToHumanSize } from '../../lib/utils/number_utils'; export default { props: { @@ -41,6 +42,10 @@ return item.layers ? n__('%d layer', '%d layers', item.layers) : ''; }, + formatSize(size) { + return numberToHumanSize(size); + }, + handleDeleteRegistry(registry) { this.deleteRegistry(registry) .then(() => this.fetchList({ repo: this.repo })) @@ -57,7 +62,7 @@ }, showError(message) { - Flash((errorMessages[message])); + Flash(errorMessages[message]); }, }, }; @@ -97,7 +102,7 @@ - {{item.size}} + {{formatSize(item.size)}} diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js index 34ed40b8b655876ffe5f30ca2ea344865520c4ee..795b39bb3dc7a405f8d2e40cafbcac35dfaba1c7 100644 --- a/app/assets/javascripts/registry/stores/actions.js +++ b/app/assets/javascripts/registry/stores/actions.js @@ -29,11 +29,9 @@ export const fetchList = ({ commit }, { repo, page }) => { }); }; -export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath) - .then(res => res.json()); +export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath); -export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath) - .then(res => res.json()); +export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js index e40382e7afc3a7463f4ef80f574de76378d465ca..208c3c39866b0a1cbb11b5cedb399a6102e77794 100644 --- a/app/assets/javascripts/registry/stores/mutations.js +++ b/app/assets/javascripts/registry/stores/mutations.js @@ -38,7 +38,7 @@ export default { tag: element.name, revision: element.revision, shortRevision: element.short_revision, - size: element.size, + size: element.total_size, layers: element.layers, location: element.location, createdAt: element.created_at, diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js index 7483f8bc305834837528f0ec5cb6adfecff10bb6..df9fe6b8555750e45e73d730b47743c971337673 100644 --- a/app/assets/javascripts/repo/helpers/repo_helper.js +++ b/app/assets/javascripts/repo/helpers/repo_helper.js @@ -135,7 +135,7 @@ const RepoHelper = { return Service.getContent() .then((response) => { const data = response.data; - if (response.headers && response.headers['page-title']) data.pageTitle = response.headers['page-title']; + if (response.headers && response.headers['page-title']) data.pageTitle = decodeURI(response.headers['page-title']); Store.isTree = RepoHelper.isTree(data); if (!Store.isTree) { diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js index 8635ccece6ec86c50ff12b7c9ed41e155a643391..d34a21b37e1fdb0e2972dd7ddca90f7ad432cd4b 100644 --- a/app/assets/javascripts/settings_panels.js +++ b/app/assets/javascripts/settings_panels.js @@ -1,34 +1,26 @@ -function expandSectionParent($section, $content) { - $section.addClass('expanded'); - $content.off('animationend.expandSectionParent'); -} - function expandSection($section) { $section.find('.js-settings-toggle').text('Collapse'); - - const $content = $section.find('.settings-content'); - $content.addClass('expanded').off('scroll.expandSection').scrollTop(0); - - if ($content.hasClass('no-animate')) { - expandSectionParent($section, $content); - } else { - $content.on('animationend.expandSectionParent', () => expandSectionParent($section, $content)); + $section.find('.settings-content').off('scroll.expandSection').scrollTop(0); + $section.addClass('expanded'); + if (!$section.hasClass('no-animate')) { + $section.addClass('animating') + .one('animationend.animateSection', () => $section.removeClass('animating')); } } function closeSection($section) { $section.find('.js-settings-toggle').text('Expand'); - - const $content = $section.find('.settings-content'); - $content.removeClass('expanded').on('scroll.expandSection', () => expandSection($section)); - + $section.find('.settings-content').on('scroll.expandSection', () => expandSection($section)); $section.removeClass('expanded'); + if (!$section.hasClass('no-animate')) { + $section.addClass('animating') + .one('animationend.animateSection', () => $section.removeClass('animating')); + } } function toggleSection($section) { - const $content = $section.find('.settings-content'); - $content.removeClass('no-animate'); - if ($content.hasClass('expanded')) { + $section.removeClass('no-animate'); + if ($section.hasClass('expanded')) { closeSection($section); } else { expandSection($section); @@ -39,10 +31,19 @@ export default function initSettingsPanels() { $('.settings').each((i, elm) => { const $section = $(elm); $section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section)); - $section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section)); + + if (!$section.hasClass('expanded')) { + $section.find('.settings-content').on('scroll.expandSection', () => { + $section.removeClass('no-animate'); + expandSection($section); + }); + } }); if (location.hash) { - expandSection($(location.hash)); + const $target = $(location.hash); + if ($target.length && $target.hasClass('.settings')) { + expandSection($target); + } } } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index 61734163b6ef41409c911609ca4df7f68f82800b..d8e1a4629879722398bf5a6b00cbbaabb3a0292b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -286,6 +286,7 @@ export default { Remove source branch @@ -311,8 +312,8 @@ export default {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c7be94e2c8ea9821f5d6fa58ef5ed3a3510bf9dd..aa61ddc6a2c6e422692866cad53e60baeb27f128 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -7,6 +7,7 @@ @import "framework/animations"; @import "framework/avatar"; @import "framework/asciidoctor"; +@import "framework/banner"; @import "framework/blocks"; @import "framework/buttons"; @import "framework/badges"; @@ -38,6 +39,7 @@ @import "framework/new-sidebar"; @import "framework/tables"; @import "framework/notes"; +@import "framework/tabs"; @import "framework/timeline"; @import "framework/tooltips"; @import "framework/typography"; diff --git a/app/assets/stylesheets/framework/banner.scss b/app/assets/stylesheets/framework/banner.scss new file mode 100644 index 0000000000000000000000000000000000000000..6433b0c785574d38a44b8148139452793ec113cf --- /dev/null +++ b/app/assets/stylesheets/framework/banner.scss @@ -0,0 +1,25 @@ +.banner-callout { + display: flex; + position: relative; + flex-wrap: wrap; + + .banner-close { + position: absolute; + top: 10px; + right: 10px; + opacity: 1; + + .dismiss-icon { + color: $gl-text-color; + font-size: $gl-font-size; + } + } + + .banner-graphic { + margin: 20px auto; + } + + &.banner-non-empty-state { + border-bottom: 1px solid $border-color; + } +} diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 5b950ae0ba02da034c5431a4e6b969db5e28799e..a9d804e735d8a69dd8cbd39986f4bf1d6dea9ee3 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -749,7 +749,7 @@ margin-bottom: $dropdown-vertical-offset; } - li:not(.dropdown-bold-header) { + li { display: block; padding: 0 1px; @@ -838,6 +838,7 @@ a { padding: 8px 40px; + &.is-indeterminate::before, &.is-active::before { left: 16px; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 22945e935ef0f9cd4c7471d1111ddcee62328627..d79444fad79197160fb112b0a96c8dd4523609c0 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -161,9 +161,10 @@ } } - .dropdown-bold-header { + li.dropdown-bold-header { color: $gl-text-color-secondary; font-size: 12px; + padding: 0 16px; } .navbar-collapse { diff --git a/app/assets/stylesheets/framework/secondary-navigation-elements.scss b/app/assets/stylesheets/framework/secondary-navigation-elements.scss index 5c96b3b78e7422cd91c996681ed018987c92e630..3fd2549b1439fd030b7c108084a3e7173d13aea3 100644 --- a/app/assets/stylesheets/framework/secondary-navigation-elements.scss +++ b/app/assets/stylesheets/framework/secondary-navigation-elements.scss @@ -6,6 +6,7 @@ margin: 0; list-style: none; height: auto; + border-bottom: 1px solid $border-color; li { display: flex; @@ -24,6 +25,7 @@ &:focus { text-decoration: none; color: $black; + border-bottom: 2px solid $gray-darkest; .badge { color: $black; diff --git a/app/assets/stylesheets/framework/tabs.scss b/app/assets/stylesheets/framework/tabs.scss new file mode 100644 index 0000000000000000000000000000000000000000..c8ba14b7066f6394ff882aa4964cbc492a7308f7 --- /dev/null +++ b/app/assets/stylesheets/framework/tabs.scss @@ -0,0 +1,35 @@ +.gitlab-tabs { + background: $gray-light; + border: 1px solid $border-color; + + li { + width: 50%; + + &:not(:last-child) { + border-right: 1px solid $border-color; + } + + &.active { + background: $white-light; + } + + a { + width: 100%; + text-align: center; + } + } +} + +.gitlab-tab-content { + border: 1px solid $border-color; + border-top: 0; + margin-bottom: $gl-padding; + + .tab-pane { + padding: $gl-padding; + + &.no-padding { + padding: 0; + } + } +} diff --git a/app/assets/stylesheets/framework/tooltips.scss b/app/assets/stylesheets/framework/tooltips.scss index 93baf73cb787877e98ce1df78e718f77fb9b8346..98f28987a8266925a842d4cd306877188260a77e 100644 --- a/app/assets/stylesheets/framework/tooltips.scss +++ b/app/assets/stylesheets/framework/tooltips.scss @@ -3,5 +3,5 @@ border-radius: $border-radius-default; line-height: 16px; font-weight: $gl-font-weight-normal; - padding: $gl-btn-padding; + padding: 8px; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index bbbd16322eb55ec151b5fc71dc21c0c783ae2d97..089a67a7c989e06f0f2183b700fa63728843cdee 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -699,14 +699,6 @@ $perf-bar-bucket-color: #ccc; $perf-bar-bucket-box-shadow-from: rgba($white-light, .2); $perf-bar-bucket-box-shadow-to: rgba($black, .25); - -/* -Project Templates Icons -*/ -$rails: #c00; -$node: #353535; -$java: #70ad51; - /* Issuable warning */ diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index ffb5fc944759c85f8f1ebc19133b97e5a014ceda..09f831dcb29329155c77999b2439477eb28872b1 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -707,11 +707,11 @@ .frame.click-to-comment { position: relative; - cursor: url(icon_image_comment.svg) + cursor: image-url('icon_image_comment.svg') $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; // Retina cursor - cursor: -webkit-image-set(url(icon_image_comment.svg) 1x, url(icon_image_comment@2x.svg) 2x) + cursor: -webkit-image-set(image-url('icon_image_comment.svg') 1x, image-url('icon_image_comment@2x.svg') 2x) $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; .comment-indicator { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index b3bab082a35cf826bcd3fb0196c8033d0e9cc0a4..692acf74a58c56d2feaf82f304f9ea726ba7cb6e 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -3,41 +3,12 @@ border-bottom: 1px solid $border-color; } -.project-member-tabs { - background: $gray-light; - border: 1px solid $border-color; - - li { - width: 50%; - - &.active { - background: $white-light; - } - - &:first-child { - border-right: 1px solid $border-color; - } - - a { - width: 100%; - text-align: center; - } - } -} - .users-project-form { .btn-create { margin-right: 10px; } } -.project-member-tab-content { - padding: $gl-padding; - border: 1px solid $border-color; - border-top: 0; - margin-bottom: $gl-padding; -} - .member { .list-item-name { @media (min-width: $screen-sm-min) { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 04b132415eb2e711d221a42615129d29330ceecb..f0cad30f4f36b205167da751398ad69104911c3f 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -249,13 +249,12 @@ width: 100%; padding-right: 5px; } - } .discussion-actions { display: table; - .new-issue-for-discussion path { + .btn-default path { fill: $gray-darkest; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 96b7db3b85dcb09d8d252bcba61b5c6fb5518b03..36a7fc0d3e10521143fde078cd32e340322be412 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -466,6 +466,10 @@ ul.notes { float: right; margin-left: 10px; color: $gray-darkest; + + .btn-group > .discussion-next-btn { + margin-left: -1px; + } } .note-actions { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index a086c11324d0df9a3e79d5607bfadc3dc82f1b82..bd385db96929869799bc384fa27459e155161bdb 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -48,7 +48,8 @@ border: 1px solid $border-color; } - + .select2 a { + + .select2 a, + + .btn-default { border-top-left-radius: 0; border-bottom-left-radius: 0; } @@ -549,10 +550,96 @@ a.deploy-project-label { } } -.project-template, +.project-template { + > .form-group { + margin-bottom: 0; + } + + .template-option { + padding: $gl-padding $gl-padding $gl-padding ($gl-padding * 4); + position: relative; + + &:not(:first-child) { + border-top: 1px solid $border-color; + } + } + + .template-title { + font-size: 16px; + } + + .template-description { + margin: 6px 0 12px; + } + + .template-button { + input { + position: absolute; + clip: rect(0, 0, 0, 0); + } + } + + svg { + position: absolute; + left: $gl-padding; + top: $gl-padding; + } + + .project-fields-form { + display: none; + + &.selected { + display: block; + padding: $gl-padding; + } + } + + .template-input-group { + position: relative; + + @media (min-width: $screen-sm-min) { + display: flex; + } + + .input-group-addon { + flex: 1; + text-align: left; + padding-left: ($gl-padding * 3); + background-color: $white-light; + } + + .selected-template { + line-height: 20px; + } + + .selected-icon { + svg { + display: none; + top: 7px; + height: 20px; + width: 20px; + + &.active { + display: block; + } + } + } + } +} + +.gitlab-tab-content { + .import-project-pane { + padding-bottom: 6px; + } +} + .project-import { - .form-group { - margin-bottom: 5px; + .import-btn-container { + margin-bottom: 0; + } + + .toggle-import-form { + padding-bottom: 10px; } .import-buttons { @@ -567,10 +654,6 @@ a.deploy-project-label { margin-right: 10px; } - .blank-option { - min-width: 70px; - } - .btn-template-icon { height: 24px; width: inherit; @@ -592,18 +675,6 @@ a.deploy-project-label { } } - .icon-rails path { - fill: $rails; - } - - .icon-node-express path { - fill: $node; - } - - .icon-java-spring path { - fill: $java; - } - > div { margin-bottom: 10px; padding-left: 0; @@ -611,10 +682,6 @@ a.deploy-project-label { } } -.project-templates-buttons .btn:last-child { - margin-right: 0; -} - .create-project-options { display: flex; @@ -1053,6 +1120,12 @@ pre.light-well { min-width: 100px; } + &.form-group { + @media (min-width: $screen-sm-min) { + margin-bottom: 0; + } + } + .select2-choice { border-top-right-radius: 0; border-bottom-right-radius: 0; diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 41a6ba2023ac0831c4be658b6d5aa77c8f3b5237..add2999cd6375a92f192a19be92408887cd7d1e1 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -23,15 +23,14 @@ } .settings { - overflow: hidden; border-bottom: 1px solid $gray-darker; &:first-of-type { margin-top: 10px; } - &.expanded { - overflow: visible; + &.animating { + overflow: hidden; } } @@ -56,14 +55,18 @@ overflow-y: scroll; padding-right: 110px; animation: collapseMaxHeight 300ms ease-out; + // Keep the section from expanding when we scroll over it + pointer-events: none; - &.expanded { + .settings.expanded & { max-height: none; overflow-y: visible; animation: expandMaxHeight 300ms ease-in; + // Reset and allow clicks again when expanded + pointer-events: auto; } - &.no-animate { + .settings.no-animate & { animation: none; } @@ -246,3 +249,22 @@ } } } + +.modal-doorkeepr-auth, +.doorkeeper-app-form { + .scope-description { + color: $theme-gray-700; + } +} + +.modal-doorkeepr-auth { + .modal-body { + padding: $gl-padding; + } +} + +.doorkeeper-app-form { + .scope-description { + margin: 0 0 5px 17px; + } +} diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index a7ab481519d99505e9f075e71b4365738063550b..b0c4c31cffc83d2463e074d95de8220900c747db 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -50,10 +50,10 @@ class Admin::DeployKeysController < Admin::ApplicationController end def create_params - params.require(:deploy_key).permit(:key, :title, :can_push) + params.require(:deploy_key).permit(:key, :title) end def update_params - params.require(:deploy_key).permit(:title, :can_push) + params.require(:deploy_key).permit(:title) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 967fe39256ab92683d5bc3fcbdb4d4a53a753b8a..391a0519195721b75a15f4be4770e443d5578d0d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -349,6 +349,6 @@ class ApplicationController < ActionController::Base def set_page_title_header # Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8 - response.headers['Page-Title'] = page_title('GitLab').encode('ISO-8859-1') + response.headers['Page-Title'] = URI.escape(page_title('GitLab')) end end diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 2b6afaa6233a00f5105f227fdd4dbd07da916635..738afd612f04a360050b4f835a2ed0899d7a78c7 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -94,10 +94,9 @@ module LfsRequest @storage_project ||= begin result = project - loop do - break unless result.forked? - result = result.forked_from_project - end + # TODO: Make this go to the fork_network root immeadiatly + # dependant on the discussion in: https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 + result = result.fork_source while result.forked? result end diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 1126f706393ce07da86a3763095c952c023342b5..fb9c942d3021a3a689cb6bc2e76ae72fe2d8cffd 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -4,6 +4,7 @@ module NotesActions included do before_action :set_polling_interval_header, only: [:index] + before_action :noteable, only: :index before_action :authorize_admin_note!, only: [:update, :destroy] before_action :note_project, only: [:create] end @@ -188,7 +189,7 @@ module NotesActions end def noteable - @noteable ||= notes_finder.target + @noteable ||= notes_finder.target || render_404 end def last_fetched_at diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 80ab681ed879faa5257993fb932ab685794fd75f..bc0948cd3fb87075f70f14f199f970326b5a1a3a 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -10,7 +10,7 @@ class ConfirmationsController < Devise::ConfirmationsController users_almost_there_path end - def after_confirmation_path_for(_resource_name, resource) + def after_confirmation_path_for(resource_name, resource) # incoming resource can either be a :user or an :email if signed_in?(:user) after_sign_in(resource) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 7a7bcb1a3d2e2e5c6991a61602d93b4d28bc4ae1..310aeb0f0d6f0383aa70692cf7202c90f6ed9067 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -75,8 +75,6 @@ class Groups::MilestonesController < Groups::ApplicationController end def milestones - search_params = params.merge(group_ids: group.id) - milestones = MilestonesFinder.new(search_params).execute legacy_milestones = GroupMilestone.build_collection(group, group_projects, params) @@ -93,4 +91,8 @@ class Groups::MilestonesController < Groups::ApplicationController render_404 unless @milestone end + + def search_params + params.permit(:state).merge(group_ids: group.id) + end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 9612b8d8514b00cfc8654def8624580a2110c8d9..b87348fe4e631f7894b666f3636b89972e183c35 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -108,6 +108,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController continue_login_process end + rescue Gitlab::OAuth::SigninDisabledForProviderError + handle_disabled_provider rescue Gitlab::OAuth::SignupDisabledError handle_signup_error end @@ -163,6 +165,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController redirect_to new_user_session_path end + def handle_disabled_provider + label = Gitlab::OAuth::Provider.label_for(oauth['provider']) + flash[:alert] = "Signing in using #{label} has been disabled" + + redirect_to new_user_session_path + end + def log_audit_event(user, options = {}) AuditEventService.new(user, user, options) .for_authentication.security_event diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index d7dd8ddcb7d888ba55dde202ad9b787c609a5bc3..9e79852e378519e648467e6666a1c51b9777a2ec 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -2,7 +2,6 @@ class Projects::ApplicationController < ApplicationController include RoutableActions skip_before_action :authenticate_user! - before_action :redirect_git_extension before_action :project before_action :repository layout 'project' @@ -11,15 +10,6 @@ class Projects::ApplicationController < ApplicationController private - def redirect_git_extension - # Redirect from - # localhost/group/project.git - # to - # localhost/group/project - # - redirect_to url_for(params.merge(format: nil)) if params[:format] == 'git' - end - def project return @project if @project return nil unless params[:project_id] || params[:id] diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index cf8829ba95b9ae3636a3f44df6272478f23206b6..103079888b23dbb875e2ee50ed7d011dce28fe5f 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController def create @key = DeployKeys::CreateService.new(current_user, create_params).execute - unless @key.valid? && @project.deploy_keys << @key + unless @key.valid? flash[:alert] = @key.errors.full_messages.join(', ').html_safe end redirect_to_repository_settings(@project) @@ -70,11 +70,14 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create_params - params.require(:deploy_key).permit(:key, :title, :can_push) + create_params = params.require(:deploy_key) + .permit(:key, :title, deploy_keys_projects_attributes: [:can_push]) + create_params.dig(:deploy_keys_projects_attributes, '0')&.merge!(project_id: @project.id) + create_params end def update_params - params.require(:deploy_key).permit(:title, :can_push) + params.require(:deploy_key).permit(:title, deploy_keys_projects_attributes: [:id, :can_push]) end def authorize_update_deploy_key! diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index b7a108a0ebd4c2d6a3846b2f97e12298eb58a3b2..fe1334c0cfe2d79d33d256dbcb00fcf23e80151f 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action :authorize_create_issue!, only: [:new, :create] # Allow modify issue - before_action :authorize_update_issue!, only: [:update, :move] + before_action :authorize_update_issue!, only: [:edit, :update, :move] # Allow create a new branch and empty WIP merge request from current issue before_action :authorize_create_merge_request!, only: [:create_merge_request] @@ -63,6 +63,10 @@ class Projects::IssuesController < Projects::ApplicationController respond_with(@issue) end + def edit + respond_with(@issue) + end + def show @noteable = @issue @note = @project.notes.new(noteable: @issue) @@ -122,6 +126,10 @@ class Projects::IssuesController < Projects::ApplicationController @issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue) respond_to do |format| + format.html do + recaptcha_check_with_fallback { render :edit } + end + format.json do render_issue_json end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index c94384d2a1a82c7ff8447adef811cd091e2bb579..9219a9551df5bdbdcfee6c37767148c812e7d370 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -84,12 +84,6 @@ class Projects::MilestonesController < Projects::ApplicationController def milestones @milestones ||= begin - if @project.group && can?(current_user, :read_group, @project.group) - group = @project.group - end - - search_params = params.merge(project_ids: @project.id, group_ids: group&.id) - MilestonesFinder.new(search_params).execute end end @@ -105,4 +99,12 @@ class Projects::MilestonesController < Projects::ApplicationController def milestone_params params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) end + + def search_params + if @project.group && can?(current_user, :read_group, @project.group) + group = @project.group + end + + params.permit(:state).merge(project_ids: @project.id, group_ids: group&.id) + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a738ca9f3618471e0bcdde912d25e9ed35d7149e..41f748475d91e5c30890d01b121d99189ff42267 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -3,6 +3,7 @@ class ProjectsController < Projects::ApplicationController include ExtractsPath before_action :authenticate_user!, except: [:index, :show, :activity, :refs] + before_action :redirect_git_extension, only: [:show] before_action :project, except: [:index, :new, :create] before_action :repository, except: [:index, :new, :create] before_action :assign_ref_vars, only: [:show], if: :repo_exists? @@ -400,4 +401,13 @@ class ProjectsController < Projects::ApplicationController def project_export_enabled render_404 unless current_application_settings.project_export_enabled? end + + def redirect_git_extension + # Redirect from + # localhost/group/project.git + # to + # localhost/group/project + # + redirect_to request.original_url.sub(/\.git\/?\Z/, '') if params[:format] == 'git' + end end diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index 0a5a0ea2f35e373e486a64e9b2cd4f9353c01d02..b4605fca1933184f547435036a0ff41b57bc4cf2 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -46,11 +46,7 @@ class MilestonesFinder end def order(items) - if params.has_key?(:order) - items.reorder(params[:order]) - else - order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC') - items.reorder(order_statement) - end + order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC') + items.reorder(order_statement).order('title ASC') end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8d02d5de5c30841b3580d49e2db14990a169cd2c..4754a67450fbb3b91bc7a2214e3f37c49a9d9dac 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -309,4 +309,8 @@ module ApplicationHelper def show_new_repo? cookies["new_repo"] == "true" && body_data_page != 'projects:show' end + + def locale_path + asset_path("locale/#{Gitlab::I18n.locale}/app.js") + end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 7bd34df5c954667e85333a04f4300f269ba59c3f..cd1ecaadb8588da7725a3232fb09415a5229f215 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -108,6 +108,43 @@ module ApplicationSettingsHelper options_for_select(Sidekiq::Queue.all.map(&:name), @application_setting.sidekiq_throttling_queues) end + def circuitbreaker_failure_count_help_text + health_link = link_to(s_('AdminHealthPageLink|health page'), admin_health_check_path) + api_link = link_to(s_('CircuitBreakerApiLink|circuitbreaker api'), help_page_path("api/repository_storage_health")) + message = _("The number of failures of after which GitLab will completely "\ + "prevent access to the storage. The number of failures can be "\ + "reset in the admin interface: %{link_to_health_page} or using "\ + "the %{api_documentation_link}.") + message = message % { link_to_health_page: health_link, api_documentation_link: api_link } + + message.html_safe + end + + def circuitbreaker_access_retries_help_text + _('The number of attempts GitLab will make to access a storage.') + end + + def circuitbreaker_backoff_threshold_help_text + _("The number of failures after which GitLab will start temporarily "\ + "disabling access to a storage shard on a host") + end + + def circuitbreaker_failure_wait_time_help_text + _("When access to a storage fails. GitLab will prevent access to the "\ + "storage for the time specified here. This allows the filesystem to "\ + "recover. Repositories on failing shards are temporarly unavailable") + end + + def circuitbreaker_failure_reset_time_help_text + _("The time in seconds GitLab will keep failure information. When no "\ + "failures occur during this time, information about the mount is reset.") + end + + def circuitbreaker_storage_timeout_help_text + _("The time in seconds GitLab will try to access storage. After this time a "\ + "timeout error will be raised.") + end + def visible_attributes [ :admin_notification_email, @@ -116,6 +153,12 @@ module ApplicationSettingsHelper :akismet_api_key, :akismet_enabled, :auto_devops_enabled, + :circuitbreaker_access_retries, + :circuitbreaker_backoff_threshold, + :circuitbreaker_failure_count_threshold, + :circuitbreaker_failure_reset_time, + :circuitbreaker_failure_wait_time, + :circuitbreaker_storage_timeout, :clientside_sentry_dsn, :clientside_sentry_enabled, :container_registry_token_expire_delay, diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 82bceddf1f0a5a3a27b26d07310413bb46fbfcc6..676c1d1988b636cdfc67bf7e0b8e7476b440fc72 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -7,7 +7,12 @@ module GroupsHelper can?(current_user, :change_share_with_group_lock, group) end - def group_icon(group) + def group_icon(group, options = {}) + img_path = group_icon_url(group, options) + image_tag img_path, options + end + + def group_icon_url(group, options = {}) if group.is_a?(String) group = Group.find_by_full_path(group) end @@ -89,7 +94,7 @@ module GroupsHelper link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do output = if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? - image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) + group_icon(group, class: "avatar-tile", width: 15, height: 15) else "" end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 7713fb0b9f8512a4915d701edc454278220d5d70..baa2d6e375eb88f39ae326db1d8366faf394c3a1 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -314,20 +314,12 @@ module IssuablesHelper @issuable_templates ||= case issuable when Issue - issue_template_names + ref_project.repository.issue_template_names when MergeRequest - merge_request_template_names + ref_project.repository.merge_request_template_names end end - def merge_request_template_names - @merge_request_templates ||= Gitlab::Template::MergeRequestTemplate.dropdown_names(ref_project) - end - - def issue_template_names - @issue_templates ||= Gitlab::Template::IssueTemplate.dropdown_names(ref_project) - end - def selected_template(issuable) params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } end diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index 2c5619ac41bafd80b5a6d62ede463b58c6e9be53..603b9438e35ce1ac80d1ddc5ef0361f4f239bffa 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -10,6 +10,7 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} options[:data][:src] = path_to_image(source) + options[:class] ||= "" options[:class] << " lazy" diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 20e050195ea37873dcc1721e216eb5ceba61cdfa..d21c01cc27f14e7ab77d6101285bf0af1f6a1418 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -97,7 +97,15 @@ module ProjectsHelper def remove_fork_project_message(project) _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") % - { forked_from_project: @project.forked_from_project.name_with_namespace } + { forked_from_project: fork_source_name(project) } + end + + def fork_source_name(project) + if @project.fork_source + @project.fork_source.full_name + else + @project.fork_network&.deleted_root_project_name + end end def project_nav_tabs @@ -127,8 +135,8 @@ module ProjectsHelper def can_change_visibility_level?(project, current_user) return false unless can?(current_user, :change_visibility_level, project) - if project.forked? - project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE + if project.fork_source + project.fork_source.visibility_level > Gitlab::VisibilityLevel::PRIVATE else true end diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb index 544c9efb84543ba6bb6656c4bfd70498d5d465b9..4d2180f7eeeaea834b4340dad0680b21e58d1bf7 100644 --- a/app/helpers/storage_health_helper.rb +++ b/app/helpers/storage_health_helper.rb @@ -16,17 +16,16 @@ module StorageHealthHelper def message_for_circuit_breaker(circuit_breaker) maximum_failures = circuit_breaker.failure_count_threshold current_failures = circuit_breaker.failure_count - permanently_broken = circuit_breaker.circuit_broken? && current_failures >= maximum_failures translation_params = { number_of_failures: current_failures, maximum_failures: maximum_failures, number_of_seconds: circuit_breaker.failure_wait_time } - if permanently_broken + if circuit_breaker.circuit_broken? s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\ "retry automatically. Reset storage information when the problem is "\ "resolved.") % translation_params - elsif circuit_breaker.circuit_broken? + elsif circuit_breaker.backing_off? _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\ "block access for %{number_of_seconds} seconds.") % translation_params else diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index c0cc60d5ebf3be68a3a0591946e4940eee261704..f266e7db6da9acc9cfd7072099e3a14f3186f92b 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -33,6 +33,8 @@ class ApplicationSetting < ActiveRecord::Base attr_accessor :domain_whitelist_raw, :domain_blacklist_raw + default_value_for :id, 1 + validates :uuid, presence: true validates :session_expire_delay, @@ -151,6 +153,25 @@ class ApplicationSetting < ActiveRecord::Base presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :circuitbreaker_backoff_threshold, + :circuitbreaker_failure_count_threshold, + :circuitbreaker_failure_wait_time, + :circuitbreaker_failure_reset_time, + :circuitbreaker_storage_timeout, + presence: true, + numericality: { only_integer: true, greater_than_or_equal_to: 0 } + + validates :circuitbreaker_access_retries, + presence: true, + numericality: { only_integer: true, greater_than_or_equal_to: 1 } + + validates_each :circuitbreaker_backoff_threshold do |record, attr, value| + if value.to_i >= record.circuitbreaker_failure_count_threshold + record.errors.add(attr, _("The circuitbreaker backoff threshold should be "\ + "lower than the failure count threshold")) + end + end + SUPPORTED_KEY_TYPES.each do |type| validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } end @@ -194,7 +215,10 @@ class ApplicationSetting < ActiveRecord::Base ensure_cache_setup Rails.cache.fetch(CACHE_KEY) do - ApplicationSetting.last + ApplicationSetting.last.tap do |settings| + # do not cache nils + raise 'missing settings' unless settings + end end rescue # Fall back to an uncached value if there are any problems (e.g. redis down) diff --git a/app/models/blob.rb b/app/models/blob.rb index 954d4e4d779c95fec71d61248293032dad720b4a..ad0bc2e2ead6d2c7096c40702538c983470a5f14 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -156,7 +156,9 @@ class Blob < SimpleDelegator end def file_type - Gitlab::FileDetector.type_of(path) + name = File.basename(path) + + Gitlab::FileDetector.type_of(path) || Gitlab::FileDetector.type_of(name) end def video? diff --git a/app/models/ci/artifact_blob.rb b/app/models/ci/artifact_blob.rb index 8b66531ec7bb9bc8bcebe7ef52648a1287feade5..ec56cc53aea7bfacf5925202283e4f81e35370b6 100644 --- a/app/models/ci/artifact_blob.rb +++ b/app/models/ci/artifact_blob.rb @@ -2,7 +2,7 @@ module Ci class ArtifactBlob include BlobLike - EXTENTIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json].freeze + EXTENSIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json].freeze attr_reader :entry @@ -36,17 +36,22 @@ module Ci def external_url(project, job) return unless external_link?(job) - components = project.full_path_components - components << "-/jobs/#{job.id}/artifacts/file/#{path}" - artifact_path = components[1..-1].join('/') + full_path_parts = project.full_path_components + top_level_group = full_path_parts.shift - "#{pages_config.protocol}://#{components[0]}.#{pages_config.host}/#{artifact_path}" + artifact_path = [ + '-', *full_path_parts, '-', + 'jobs', job.id, + 'artifacts', path + ].join('/') + + "#{pages_config.protocol}://#{top_level_group}.#{pages_config.host}/#{artifact_path}" end def external_link?(job) pages_config.enabled && pages_config.artifacts_server && - EXTENTIONS_SERVED_BY_PAGES.include?(File.extname(name)) && + EXTENSIONS_SERVED_BY_PAGES.include?(File.extname(name)) && job.project.public? end diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index 8fbfed11bdf50c0dd7c180d851148b6f77dfcd98..2ec70203710fd6d006629246b993a83ed8018c9b 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -11,7 +11,7 @@ module Avatarable # If asset_host is set then it is expected that assets are handled by a standalone host. # That means we do not want to get GitLab's relative_url_root option anymore. - host = asset_host.present? ? asset_host : gitlab_host + host = (asset_host.present? && (!respond_to?(:public?) || public?)) ? asset_host : gitlab_host [host, avatar.url].join end diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index 5ab5c80a2f5ce597c26970400ffb446790f064e4..b3020484738e033467908f9c477fcc474fea2655 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -7,6 +7,8 @@ module Storage raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry') end + expires_full_path_cache + # Move the namespace directory in all storage paths used by member projects repository_storage_paths.each do |repository_storage_path| # Ensure old directory exists before moving it diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index eae5eee4feea67198a969016c483342d8f442d24..c2e0a5fa12688a219bc5c6b1ad0a94dbe435b912 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,10 +1,16 @@ class DeployKey < Key - has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + include IgnorableColumn + + has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :deploy_keys_projects scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } scope :are_public, -> { where(public: true) } + ignore_column :can_push + + accepts_nested_attributes_for :deploy_keys_projects + def private? !public? end @@ -22,10 +28,18 @@ class DeployKey < Key end def has_access_to?(project) - projects.include?(project) + deploy_keys_project_for(project).present? end def can_push_to?(project) - can_push? && has_access_to?(project) + !!deploy_keys_project_for(project)&.can_push? + end + + def deploy_keys_project_for(project) + deploy_keys_projects.find_by(project: project) + end + + def projects_with_write_access + Project.preload(:route).where(id: deploy_keys_projects.with_write_access.select(:project_id)) end end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index b37b9bfbdac2faf8fca0f410324adf5e0879f2fe..6eef12c437399508bf3cb33064241277dbeb2daf 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -1,8 +1,14 @@ class DeployKeysProject < ActiveRecord::Base belongs_to :project - belongs_to :deploy_key + belongs_to :deploy_key, inverse_of: :deploy_keys_projects - validates :deploy_key_id, presence: true + scope :without_project_deleted, -> { joins(:project).where(projects: { pending_delete: false }) } + scope :in_project, ->(project) { where(project: project) } + scope :with_write_access, -> { where(can_push: true) } + + accepts_nested_attributes_for :deploy_key + + validates :deploy_key, presence: true validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" } validates :project_id, presence: true diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index d88a92dc027572234ced39b512bedec96de56eab..ae5f138a9208a99e22f40ea17ca68f9910a6fbea 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -18,7 +18,8 @@ class DiffNote < Note validate :positions_complete validate :verify_supported - before_validation :set_original_position, :update_position, on: :create + before_validation :set_original_position, on: :create + before_validation :update_position, on: :create, if: :on_text? before_validation :set_line_code after_save :keep_around_commits diff --git a/app/models/email.rb b/app/models/email.rb index 384f38f2db7a45eb0668ded6a9d0351ad9ece561..2da8b050149cebef5b5a1ebed97a7b4f39cc3c21 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -14,6 +14,8 @@ class Email < ActiveRecord::Base devise :confirmable self.reconfirmable = false # currently email can't be changed, no need to reconfirm + delegate :username, to: :user + def email=(value) write_attribute(:email, value.downcase.strip) end diff --git a/app/models/environment.rb b/app/models/environment.rb index b6868ccbe8f362639f4af392d6a180ceafb0a7cf..8d6b0a32c13143bb69e0530cd723fda1701fe2d3 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -110,7 +110,7 @@ class Environment < ActiveRecord::Base end def ref_path - "refs/#{Repository::REF_ENVIRONMENTS}/#{Shellwords.shellescape(name)}" + "refs/#{Repository::REF_ENVIRONMENTS}/#{slug}" end def formatted_external_url @@ -164,6 +164,10 @@ class Environment < ActiveRecord::Base end end + def slug + super.presence || generate_slug + end + # An environment name is not necessarily suitable for use in URLs, DNS # or other third-party contexts, so provide a slugified version. A slug has # the following properties: diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb index 218e37a531231c10c2443d2c63c1dd80b5a7fc0c..7f1728e8c772ba76555cfa5ca0d6aae1c6ad56de 100644 --- a/app/models/fork_network.rb +++ b/app/models/fork_network.rb @@ -12,4 +12,8 @@ class ForkNetwork < ActiveRecord::Base def find_forks_in(other_projects) projects.where(id: other_projects) end + + def merge_requests + MergeRequest.where(target_project: projects) + end end diff --git a/app/models/gcp/cluster.rb b/app/models/gcp/cluster.rb index 18bd6a6dcb47e6d8f17f4c9b66a99715999d4728..162a690c0e3e956f437b127a1bd062f3bbc03678 100644 --- a/app/models/gcp/cluster.rb +++ b/app/models/gcp/cluster.rb @@ -7,6 +7,9 @@ module Gcp belongs_to :user belongs_to :service + scope :enabled, -> { where(enabled: true) } + scope :disabled, -> { where(enabled: false) } + default_value_for :gcp_cluster_zone, 'us-central1-a' default_value_for :gcp_cluster_size, 3 default_value_for :gcp_machine_type, 'n1-standard-4' diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index c0864769314f8de9646c974fc09e10d4102d47f2..dc2f68171903b4c2374f0aea1fd22dc9cdaaaef4 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -44,10 +44,10 @@ class GlobalMilestone def self.group_milestones_states_count(group) return STATE_COUNT_HASH unless group - params = { group_ids: [group.id], state: 'all', order: nil } + params = { group_ids: [group.id], state: 'all' } relation = MilestonesFinder.new(params).execute - grouped_by_state = relation.group(:state).count + grouped_by_state = relation.reorder(nil).group(:state).count { opened: grouped_by_state['active'] || 0, @@ -60,10 +60,10 @@ class GlobalMilestone def self.legacy_group_milestone_states_count(projects) return STATE_COUNT_HASH unless projects - params = { project_ids: projects.map(&:id), state: 'all', order: nil } + params = { project_ids: projects.map(&:id), state: 'all' } relation = MilestonesFinder.new(params).execute - project_milestones_by_state_and_title = relation.group(:state, :title).count + project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count opened = count_by_state(project_milestones_by_state_and_title, 'active') closed = count_by_state(project_milestones_by_state_and_title, 'closed') diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 5a70e114f562e635e7ea2c3e46c6b04ace13f66d..27729deeac9e0081d990281a50258c8ef4365c01 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :url, presence: true, url: true + validates :token, format: { without: /\n/ } def execute(data, hook_name) WebHookService.new(self, data, hook_name).execute diff --git a/app/models/identity.rb b/app/models/identity.rb index 920a25932b433cc7e31e72b4188e68558ef9ec11..ac8094b610ebcd9ddfba34769cd83e14be3ecf86 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -7,7 +7,10 @@ class Identity < ActiveRecord::Base validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider } validates :user_id, uniqueness: { scope: :provider } - scope :with_extern_uid, ->(provider, extern_uid) { where(extern_uid: extern_uid, provider: provider) } + scope :with_extern_uid, ->(provider, extern_uid) do + extern_uid = Gitlab::LDAP::Person.normalize_dn(extern_uid) if provider.starts_with?('ldap') + where(extern_uid: extern_uid, provider: provider) + end def ldap? provider.starts_with?('ldap') diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 292122f779eb354d9328a95befa77e54ffec459c..e1780072f5f29a7762c1ce29f71266cd5f85a92b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -392,7 +392,11 @@ class MergeRequest < ActiveRecord::Base end def merge_ongoing? - !!merge_jid && !merged? + # While the MergeRequest is locked, it should present itself as 'merge ongoing'. + # The unlocking process is handled by StuckMergeJobsWorker scheduled in Cron. + return true if locked? + + !!merge_jid && !merged? && Gitlab::SidekiqStatus.running?(merge_jid) end def closed_without_fork? @@ -888,7 +892,7 @@ class MergeRequest < ActiveRecord::Base # def all_commit_shas if persisted? - column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).pluck('DISTINCT(sha)') + column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).limit(10_000).pluck('sha') serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas) (column_shas + serialised_shas).uniq diff --git a/app/models/note.rb b/app/models/note.rb index ceded9f2aefbc3875a5fb2cbda0dbe4ed6abfe94..8939e590ef16e6c1e513d30031bbd430e92007c5 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -169,7 +169,7 @@ class Note < ActiveRecord::Base end def cross_reference? - system? && SystemNoteService.cross_reference?(note) + system? && matches_cross_reference_regex? end def diff_note? diff --git a/app/models/project.rb b/app/models/project.rb index 57e91ab3b8823f10de61cf8e1ab52665fbf901fd..4ee6db9211ff29f25a371406beb47585add51d62 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1017,6 +1017,10 @@ class Project < ActiveRecord::Base !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) end + def fork_source + forked_from_project || fork_network&.root_project + end + def personal? !group end @@ -1262,7 +1266,7 @@ class Project < ActiveRecord::Base # self.forked_from_project will be nil before the project is saved, so # we need to go through the relation - original_project = forked_project_link.forked_from_project + original_project = forked_project_link&.forked_from_project return true unless original_project level <= original_project.visibility_level diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 8ba07173c74b360f5028800e9295eddb4dd72e78..5c0b3338a622b9b4112d3b22dd640e2fd3a49064 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -153,7 +153,10 @@ class KubernetesService < DeploymentService end def default_namespace - "#{project.path}-#{project.id}" if project.present? + return unless project + + slug = "#{project.path}-#{project.id}".downcase + slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '') end def build_kubeclient!(api_path: 'api', api_version: 'v1') diff --git a/app/models/repository.rb b/app/models/repository.rb index d725c65081dc502eeb02bf540f65be633b184cd7..43521a0ddd3599749253d02f6bddb86abdf332ac 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -34,7 +34,8 @@ class Repository CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide changelog license_blob license_key gitignore koding_yml gitlab_ci_yml branch_names tag_names branch_count - tag_count avatar exists? empty? root_ref has_visible_content?).freeze + tag_count avatar exists? empty? root_ref has_visible_content? + issue_template_names merge_request_template_names).freeze # Methods that use cache_method but only memoize the value MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze @@ -50,7 +51,9 @@ class Repository gitignore: :gitignore, koding: :koding_yml, gitlab_ci: :gitlab_ci_yml, - avatar: :avatar + avatar: :avatar, + issue_template: :issue_template_names, + merge_request_template: :merge_request_template_names }.freeze # Wraps around the given method and caches its output in Redis and an instance @@ -535,6 +538,16 @@ class Repository end cache_method :avatar + def issue_template_names + Gitlab::Template::IssueTemplate.dropdown_names(project) + end + cache_method :issue_template_names, fallback: [] + + def merge_request_template_names + Gitlab::Template::MergeRequestTemplate.dropdown_names(project) + end + cache_method :merge_request_template_names, fallback: [] + def readme if readme = tree(:head)&.readme ReadmeBlob.new(readme, self) @@ -1114,7 +1127,7 @@ class Repository def last_commit_id_for_path_by_shelling_out(sha, path) args = %W(rev-list --max-count=1 #{sha} -- #{path}) - run_git(args).first.strip + raw_repository.run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip end def repository_storage_path diff --git a/app/models/service.rb b/app/models/service.rb index 6b64079215f7d9e0783ee5bdb20e52a2f10dcb07..ebc6407a4149b960eae971c211b0e47a6bde0918 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -117,6 +117,11 @@ class Service < ActiveRecord::Base nil end + def api_field_names + fields.map { |field| field[:name] } + .reject { |field_name| field_name =~ /(password|token|key)/ } + end + def global_fields fields end diff --git a/app/models/user.rb b/app/models/user.rb index 533a776bc65971bf112632ed8d5cbecd511a7719..91dbe4c70230d524d4fa2104f1c5953f3816e4d8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -164,7 +164,7 @@ class User < ActiveRecord::Base before_validation :set_notification_email, if: :email_changed? before_validation :set_public_email, if: :public_email_changed? before_save :ensure_authentication_token, :ensure_incoming_email_token - before_save :ensure_user_rights_and_limits, if: :external_changed? + before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? } before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? } after_save :ensure_namespace_correct @@ -1141,8 +1141,9 @@ class User < ActiveRecord::Base self.can_create_group = false self.projects_limit = 0 else - self.can_create_group = gitlab_config.default_can_create_group - self.projects_limit = current_application_settings.default_projects_limit + # Only revert these back to the default if they weren't specifically changed in this update. + self.can_create_group = gitlab_config.default_can_create_group unless can_create_group_changed? + self.projects_limit = current_application_settings.default_projects_limit unless projects_limit_changed? end end diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb index 229311eb6ee9a4a34515a69752e60d555b532276..c226586fba5ee57ee67dfddbb71e2bcbe7b33765 100644 --- a/app/presenters/projects/settings/deploy_keys_presenter.rb +++ b/app/presenters/projects/settings/deploy_keys_presenter.rb @@ -7,7 +7,7 @@ module Projects delegate :size, to: :available_public_keys, prefix: true def new_key - @key ||= DeployKey.new + @key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build } end def enabled_keys diff --git a/app/serializers/container_tag_entity.rb b/app/serializers/container_tag_entity.rb index ec1fc3495868a8124088457f046c4bb4bb9c5671..8f1488e6cbb8f0da429d5cebda236b87ce346f5d 100644 --- a/app/serializers/container_tag_entity.rb +++ b/app/serializers/container_tag_entity.rb @@ -1,10 +1,10 @@ class ContainerTagEntity < Grape::Entity include RequestAwareEntity - expose :name, :location, :revision, :total_size, :created_at + expose :name, :location, :revision, :short_revision, :total_size, :created_at expose :destroy_path, if: -> (*) { can_destroy? } do |tag| - project_registry_repository_tag_path(project, tag.repository, tag.name, format: :json) + project_registry_repository_tag_path(project, tag.repository, tag.name) end private diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb index c75431a79aec728db295d454b95dde3c04a33435..2678f99510c0e80edc4c091488e794b204925fbe 100644 --- a/app/serializers/deploy_key_entity.rb +++ b/app/serializers/deploy_key_entity.rb @@ -3,19 +3,20 @@ class DeployKeyEntity < Grape::Entity expose :user_id expose :title expose :fingerprint - expose :can_push expose :destroyed_when_orphaned?, as: :destroyed_when_orphaned expose :almost_orphaned?, as: :almost_orphaned expose :created_at expose :updated_at - expose :projects, using: ProjectEntity do |deploy_key| - deploy_key.projects.without_deleted.select { |project| options[:user].can?(:read_project, project) } + expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key| + deploy_key.deploy_keys_projects + .without_project_deleted + .select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) } end expose :can_edit private def can_edit - options[:user].can?(:update_deploy_key, object) + Ability.allowed?(options[:user], :update_deploy_key, object) end end diff --git a/app/serializers/deploy_keys_project_entity.rb b/app/serializers/deploy_keys_project_entity.rb new file mode 100644 index 0000000000000000000000000000000000000000..568ef5ab75e3859cffebcd1216dbde6e8474226d --- /dev/null +++ b/app/serializers/deploy_keys_project_entity.rb @@ -0,0 +1,4 @@ +class DeployKeysProjectEntity < Grape::Entity + expose :can_push + expose :project, using: ProjectEntity +end diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb index 7c872a3e9863de1018b42f780660bf5b9c47138f..6d8466da902d6c6c834693b80fa4cb7502830060 100644 --- a/app/serializers/group_entity.rb +++ b/app/serializers/group_entity.rb @@ -45,6 +45,6 @@ class GroupEntity < Grape::Entity end expose :avatar_url do |group| - group_icon(group) + group_icon_url(group) end end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 820709583fa9229bf1e0ef9be7547cf538525f19..2e98e0a78b76f44f195ea3d230dd51a334067662 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -1,15 +1,11 @@ module MergeRequests class CreateService < MergeRequests::BaseService def execute - # @project is used to determine whether the user can set the merge request's - # assignee, milestone and labels. Whether they can depends on their - # permissions on the target project. - source_project = @project - @project = Project.find(params[:target_project_id]) if params[:target_project_id] + set_projects! merge_request = MergeRequest.new merge_request.target_project = @project - merge_request.source_project = source_project + merge_request.source_project = @source_project merge_request.source_branch = params[:source_branch] merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) @@ -52,5 +48,25 @@ module MergeRequests pipelines.order(id: :desc).first end + + def set_projects! + # @project is used to determine whether the user can set the merge request's + # assignee, milestone and labels. Whether they can depends on their + # permissions on the target project. + @source_project = @project + @project = Project.find(params[:target_project_id]) if params[:target_project_id] + + # make sure that source/target project ids are not in + # params so it can't be overridden later when updating attributes + # from params when applying quick actions + params.delete(:source_project_id) + params.delete(:target_project_id) + + unless can?(current_user, :read_project, @source_project) && + can?(current_user, :read_project, @project) + + raise Gitlab::Access::AccessDeniedError + end + end end end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index a110abf825647415c6773dd3019e40c6d99a83f6..8087f09b6c737e3f3b45c8f8ff263b4636ea7a9b 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -18,15 +18,7 @@ module MergeRequests @merge_request = merge_request - unless @merge_request.mergeable? - return handle_merge_error(log_message: 'Merge request is not mergeable', save_message_on_model: true) - end - - @source = find_merge_source - - unless @source - return handle_merge_error(log_message: 'No source for merge', save_message_on_model: true) - end + error_check! merge_request.in_locked_state do if commit @@ -41,6 +33,19 @@ module MergeRequests private + def error_check! + error = + if @merge_request.should_be_rebased? + 'Only fast-forward merge is allowed for your project. Please update your source branch' + elsif !@merge_request.mergeable? + 'Merge request is not mergeable' + elsif !source + 'No source for merge' + end + + raise MergeError, error if error + end + def commit message = params[:commit_message] || merge_request.merge_commit_message @@ -78,24 +83,25 @@ module MergeRequests @merge_request.force_remove_source_branch? ? @merge_request.author : current_user end - # Logs merge error message and cleans `MergeRequest#merge_jid`. + # Verify again that the source branch can be removed, since branch may be protected, + # or the source branch may have been updated, or the user may not have permission # + def delete_source_branch? + params.fetch('should_remove_source_branch', @merge_request.force_remove_source_branch?) && + @merge_request.can_remove_source_branch?(branch_deletion_user) + end + def handle_merge_error(log_message:, save_message_on_model: false) Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}") - - if save_message_on_model - @merge_request.update(merge_error: log_message, merge_jid: nil) - else - clean_merge_jid - end + @merge_request.update(merge_error: log_message) if save_message_on_model end def merge_request_info merge_request.to_reference(full: true) end - def find_merge_source - merge_request.diff_head_sha + def source + @source ||= @merge_request.diff_head_sha end end end diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index a02eee4961b6ef0ac13517f4f42977603f141ee5..6b3939aeba5ed8286cfbc2a79d0cc565da22d564 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -6,8 +6,7 @@ class MetricsService Gitlab::HealthChecks::Redis::RedisCheck, Gitlab::HealthChecks::Redis::CacheCheck, Gitlab::HealthChecks::Redis::QueuesCheck, - Gitlab::HealthChecks::Redis::SharedStateCheck, - Gitlab::HealthChecks::FsShardsCheck + Gitlab::HealthChecks::Redis::SharedStateCheck ].freeze def prometheus_metrics_text diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 8d5da4598822aa8dc8bc0e504036458ec4ace15c..be3b4b2ba0759151c63064345a2a00643860df65 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -390,7 +390,7 @@ class NotificationService end def relabeled_resource_email(target, labels, current_user, method) - recipients = labels.flat_map { |l| l.subscribers(target.project) } + recipients = labels.flat_map { |l| l.subscribers(target.project) }.uniq recipients = notifiable_users( recipients, :subscription, target: target, diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb index 4ca6414b73b6ed6bbdad0eba8f51e3bc88670d2e..a3d7f5cbed59da6e8d54b1e37cf5f17b75e969dd 100644 --- a/app/services/projects/gitlab_projects_import_service.rb +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -26,7 +26,7 @@ module Projects end def tmp_filename - "#{SecureRandom.hex}_#{params[:path]}" + SecureRandom.hex end def file diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index abe414d0c050773880bf741f8ee74377026dca7a..c499f38442670b061e3a19cdf3dc878818668632 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -3,20 +3,26 @@ module Projects def execute return unless @project.forked? - @project.forked_from_project.lfs_objects.find_each do |lfs_object| - lfs_object.projects << @project + if fork_source = @project.fork_source + fork_source.lfs_objects.find_each do |lfs_object| + lfs_object.projects << @project + end + + refresh_forks_count(fork_source) end - merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project) + merge_requests = @project.fork_network + .merge_requests + .opened + .where.not(target_project: @project) + .from_project(@project) merge_requests.each do |mr| ::MergeRequests::CloseService.new(@project, @current_user).execute(mr) end - refresh_forks_count(@project.forked_from_project) - - @project.forked_project_link.destroy @project.fork_network_member.destroy + @project.forked_project_link.destroy end def refresh_forks_count(project) diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index a1c2f8d018016bc54fd84d481f4e9682e185ff96..5d27596782145484459ee7b41fc176829399c03e 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -83,7 +83,7 @@ class SystemHooksService project_id: model.id, owner_name: owner.name, owner_email: owner.respond_to?(:email) ? owner.email : "", - project_visibility: Project.visibility_levels.key(model.visibility_level_value).downcase + project_visibility: model.visibility.downcase } end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 7b32e215c7fc35716b22b7201907c2fde559c999..b1d34090dcd69b545dd77f05369331b535742a0c 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -162,7 +162,6 @@ module SystemNoteService # "changed time estimate to 3d 5h" # # Returns the created Note object - def change_time_estimate(noteable, project, author) parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate) body = if noteable.time_estimate == 0 @@ -188,7 +187,6 @@ module SystemNoteService # "added 2h 30m of time spent" # # Returns the created Note object - def change_time_spent(noteable, project, author) time_spent = noteable.time_spent @@ -451,10 +449,6 @@ module SystemNoteService end end - def cross_reference?(note_text) - note_text =~ /\A#{cross_reference_note_prefix}/i - end - # Check if a cross-reference is disallowed # # This method prevents adding a "mentioned in !1" note on every single commit @@ -484,7 +478,6 @@ module SystemNoteService # mentioner - Mentionable object # # Returns Boolean - def cross_reference_exists?(noteable, mentioner) # Initial scope should be system notes of this noteable type notes = Note.system.where(noteable_type: noteable.class) @@ -593,7 +586,7 @@ module SystemNoteService def discussion_lock(issuable, author) action = issuable.discussion_locked? ? 'locked' : 'unlocked' - body = "#{action} this issue" + body = "#{action} this #{issuable.class.to_s.titleize.downcase}" create_note(NoteSummary.new(issuable, issuable.project, author, body, action: action)) end diff --git a/app/services/users/last_push_event_service.rb b/app/services/users/last_push_event_service.rb index f2bfb60604fcc2a9c3f457b2269819894cc77479..57e446d7f3099efadf0d013fd171d5f7ecbd94cb 100644 --- a/app/services/users/last_push_event_service.rb +++ b/app/services/users/last_push_event_service.rb @@ -16,8 +16,8 @@ module Users user_cache_key ] - if event.project.forked? - keys << project_cache_key(event.project.forked_from_project) + if forked_from = event.project.forked_from_project + keys << project_cache_key(forked_from) end keys.each { |key| set_key(key, event.id) } diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index cd99e0b90f9f0fa629a5aab5bd764d231b62b1d5..a3c90e4f9085c40b28f0d4b67db2f7e0f6a6a2dc 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -113,7 +113,7 @@ class WebHookService 'Content-Type' => 'application/json', 'X-Gitlab-Event' => hook_name.singularize.titleize }.tap do |hash| - hash['X-Gitlab-Token'] = hook.token if hook.token.present? + hash['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present? end end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index dbaed1d09fbe9ad2afaf2034423d15112f4e29d5..3a4d5ce0b5c064fe5213ffd22a1a39ecb551bbd3 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -530,6 +530,44 @@ = succeed "." do = link_to "repository storages documentation", help_page_path("administration/repository_storages") + %fieldset + %legend Git Storage Circuitbreaker settings + .form-group + = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :circuitbreaker_access_retries, class: 'form-control' + .help-block + = circuitbreaker_access_retries_help_text + .form-group + = f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :circuitbreaker_storage_timeout, class: 'form-control' + .help-block + = circuitbreaker_storage_timeout_help_text + .form-group + = f.label :circuitbreaker_backoff_threshold, _('Number of failures before backing off'), class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :circuitbreaker_backoff_threshold, class: 'form-control' + .help-block + = circuitbreaker_backoff_threshold_help_text + .form-group + = f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :circuitbreaker_failure_wait_time, class: 'form-control' + .help-block + = circuitbreaker_failure_wait_time_help_text + .form-group + = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control' + .help-block + = circuitbreaker_failure_count_help_text + .form-group + = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :circuitbreaker_failure_reset_time, class: 'form-control' + .help-block + = circuitbreaker_failure_reset_time_help_text %fieldset %legend Repository Checks diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 92370034baaf03bc2ea76c1f38457e3b996edce1..09d13334b6e7ad855228e09aabb819cc5e7993b1 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -12,7 +12,7 @@ %tr %th.col-sm-2 Title %th.col-sm-4 Fingerprint - %th.col-sm-2 Write access allowed + %th.col-sm-2 Projects with write access %th.col-sm-2 Added at %th.col-sm-2 %tbody @@ -23,10 +23,9 @@ %td %code.key-fingerprint= deploy_key.fingerprint %td - - if deploy_key.can_push? - Yes - - else - No + - deploy_key.projects_with_write_access.each do |project| + = link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label' + %td %span.cgray added #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index e3a77dfdf10ecf8fca92a42c447f9b9ad6e61968..47cc2d4d27e078e4c880a399e56a745759fe1468 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -20,7 +20,7 @@ = visibility_level_icon(group.visibility_level, fw: false) .avatar-container.s40 - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + = group_icon(group, class: "avatar s40 hidden-xs") .title = link_to [:admin, group], class: 'group-name' do = group.full_name diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 3e02f7b1e16fb1f43f1376e29707f62f81a4854f..2545cecc7218f74416e1100f59a0b91880f45419 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -16,7 +16,7 @@ %ul.well-list %li .avatar-container.s60 - = image_tag group_icon(@group), class: "avatar s60" + = group_icon(@group, class: "avatar s60") %li %span.light Name: %strong= @group.name diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml index 7dd9943190f8325baee482a13e12655478b8104a..91a8c0c62fe0d41f6fb083d6aeb385ee3069e470 100644 --- a/app/views/admin/hook_logs/_index.html.haml +++ b/app/views/admin/hook_logs/_index.html.haml @@ -24,7 +24,7 @@ %td = truncate(hook_log.url, length: 50) %td.light - #{number_with_precision(hook_log.execution_duration, precision: 2)} ms + #{number_with_precision(hook_log.execution_duration, precision: 2)} sec %td.light = time_ago_with_tooltip(hook_log.created_at) %td diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml index 52279d0a8700f1dbe34a442de5797e70490cb01a..4b6c4581eb391e3c02da20273065de01b79ff20c 100644 --- a/app/views/discussions/_diff_discussion.html.haml +++ b/app/views/discussions/_diff_discussion.html.haml @@ -7,4 +7,4 @@ %td.notes_line{ colspan: 2 } %td.notes_content .content{ class: ('hide' unless expanded) } - = render partial: "discussions/notes", collection: discussions, as: :discussion + = render partial: "discussions/notes", collection: discussions, as: :discussion, locals: { disable_collapse_class: true } diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 636d06cab533bc0ebb86638a009720b2ff0f0ad6..f9bfc01f21376e88bdce49d42d9abb8c27777e55 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -24,4 +24,4 @@ = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } .note-container - = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true } + = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse_class: true } diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index 578e751ab47ecb248c9cfa7383fb5451a872e88c..0f03163a2e8675f7f700597272712c25a9d25ef6 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -44,4 +44,4 @@ = render "discussions/diff_with_notes", discussion: discussion - else .panel.panel-default - = render "discussions/notes", discussion: discussion + = render partial: "discussions/notes", locals: { discussion: discussion, disable_collapse_class: true } diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 9efcfef690f3b3bf1129332aa9207869f042381a..1cc227428e9dda6f48af1ba036283827168ccfb9 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,5 +1,5 @@ -- disable_collapse = local_assigns.fetch(:disable_collapse, false) -- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse +- disable_collapse_class = local_assigns.fetch(:disable_collapse_class, false) +- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse_class - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] - show_toggle = local_assigns.fetch(:show_toggle, true) - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml index 253cd33688285a2f9b930a96f5f5a1ec8daa5266..079d9083dff1d66ef79cfd851ca0745969dc9399 100644 --- a/app/views/discussions/_parallel_diff_discussion.html.haml +++ b/app/views/discussions/_parallel_diff_discussion.html.haml @@ -4,7 +4,7 @@ %td.notes_line.old %td.notes_content.parallel.old .content{ class: ('hide' unless discussions_left.any?(&:expanded?)) } - = render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old' + = render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old', locals: { disable_collapse_class: true } - else %td.notes_line.old= ("") %td.notes_content.parallel.old @@ -14,7 +14,7 @@ %td.notes_line.new %td.notes_content.parallel.new .content{ class: ('hide' unless discussions_right.any?(&:expanded?)) } - = render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new' + = render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new', locals: { disable_collapse_class: true } - else %td.notes_line.new= ("") %td.notes_content.parallel.new diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml index b3313c7c985683c58c723303058ca3e4db3bba53..cf0e0de1ca49bf45ccf44d3b34e59c1098c7174c 100644 --- a/app/views/doorkeeper/applications/_form.html.haml +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -1,4 +1,4 @@ -= form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f| += form_for application, url: doorkeeper_submit_path(application), html: { role: 'form', class: 'doorkeeper-app-form' } do |f| = form_errors(application) .form-group diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index 8ba88906714c65e157a6fb64852560365dbe4ec7..6d9c6b5572ac799eae315529b1e1110963624600 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -1,5 +1,5 @@ %main{ :role => "main" } - .modal-no-backdrop + .modal-no-backdrop.modal-doorkeepr-auth .modal-content .modal-header %h3.page-title @@ -16,14 +16,26 @@ %strong= @pre_auth.client.name will allow them to interact with GitLab as an admin as well. Proceed with caution. %p - You are about to authorize + An application called = link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer' - to use your account. - - if @pre_auth.scopes + is requesting access to your GitLab account. + + - auth_app_owner = @pre_auth.client.application.owner + - if auth_app_owner + This application was created by + = succeed "." do + = link_to auth_app_owner.name, user_path(auth_app_owner) + + Please note that this application is not provided by GitLab and you should verify its authenticity before + allowing access. + - if @pre_auth.scopes + %p This application will be able to: %ul - @pre_auth.scopes.each do |scope| - %li= t scope, scope: [:doorkeeper, :scopes] + %li + %strong= t scope, scope: [:doorkeeper, :scopes] + .scope-description= t scope, scope: [:doorkeeper, :scope_desc] .form-actions.text-right = form_tag oauth_authorization_path, method: :delete, class: 'inline' do = hidden_field_tag :client_id, @pre_auth.client.uid diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index 181c7bee702657bbaa97eeb5a43f05cfa1aa87d8..a0760c2073b4e5641b7ba04d3c9efac22f7c4d37 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -1,7 +1,7 @@ .group-home-panel.text-center %div{ class: container_class } .avatar-container.s70.group-avatar - = image_tag group_icon(@group), class: "avatar s70 avatar-tile" + = group_icon(@group, class: "avatar s70 avatar-tile") %h1.group-title = @group.name %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 15606dd30fd733998f77f047b04bf90870e654d3..16038ef2f7938d998091030401c0a636805d5dee 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -10,7 +10,7 @@ .form-group .col-sm-offset-2.col-sm-10 .avatar-container.s160 - = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' + = group_icon(@group, alt: '', class: 'avatar group-avatar s160') %p.light - if @group.avatar? You can change your group avatar here diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index b18b3dd57660bae653074b5b6cfffb11f19ef9a5..29b23ae2e52e825e7d226226e8cdebd2666bd239 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -15,10 +15,6 @@ %tr %th %th Global Shortcuts - %tr - %td.shortcut - .key n - %td Main Navigation %tr %td.shortcut .key s diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index f1b322746641ed573da7284f2fc8eb3707cf6147..1597621fa78d169e40452a6e69f828af98b45f56 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -37,7 +37,7 @@ - if content_for?(:library_javascripts) = yield :library_javascripts - = javascript_include_tag asset_path("locale/#{I18n.locale.to_s || I18n.default_locale.to_s}/app.js") unless I18n.locale == :en + = javascript_include_tag locale_path unless I18n.locale == :en = webpack_bundle_tag "webpack_runtime" = webpack_bundle_tag "common" = webpack_bundle_tag "main" diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 8cba495f7e4cf9691861c1f0aed858bde3e2ff09..0bf318b0b66ca66d92685af8c56162fbd9a5ab79 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -6,7 +6,7 @@ .context-header = link_to group_path(@group), title: @group.name do .avatar-container.s40.group-avatar - = image_tag group_icon(@group), class: "avatar s40 avatar-tile" + = group_icon(@group, class: "avatar s40 avatar-tile") .sidebar-context-title = @group.name %ul.sidebar-top-level-items diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml index 623d3bc91c6606f42d4cbc3ea12c542e6064d025..c5b1897c4926930060d6b4297168db296a3150a2 100644 --- a/app/views/projects/_export.html.haml +++ b/app/views/projects/_export.html.haml @@ -3,7 +3,7 @@ - project = local_assigns.fetch(:project) - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Export project @@ -11,7 +11,7 @@ = expanded ? 'Collapse' : 'Expand' %p Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content .bs-callout.bs-callout-info %p.append-bottom-0 %p diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 619b632918e04e32d602f738e33d28d510f403fb..1d644dda17794db90ae7117e94781f1c41a86f36 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,6 +1,5 @@ - empty_repo = @project.empty_repo? - fork_network = @project.fork_network -- forked_from_project = @project.forked_from_project || fork_network&.root_project .project-home-panel.text-center{ class: ("empty-project" if empty_repo) } .limit-container-width{ class: container_class } .avatar-container.s70.project-avatar @@ -16,13 +15,13 @@ - if @project.forked? %p - - if forked_from_project + - if @project.fork_source #{ s_('ForkedFromProjectPath|Forked from') } - = link_to project_path(forked_from_project) do - = forked_from_project.full_name + = link_to project_path(@project.fork_source) do + = fork_source_name(@project) - else - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)') - = deleted_message % { project_name: fork_network.deleted_root_project_name } + = deleted_message % { project_name: fork_source_name(@project) } .project-repo-buttons .count-buttons diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..a78a8e5d628a75980b50a4f07bda8977ea886d1b --- /dev/null +++ b/app/views/projects/_new_project_fields.html.haml @@ -0,0 +1,41 @@ +- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility + +.row{ id: project_name_id } + .form-group.project-path.col-sm-6 + = f.label :namespace_id, class: 'label-light' do + %span + Project path + .input-group + - if current_user.can_select_namespace? + .input-group-addon + = root_url + = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} + + - else + .input-group-addon.static-namespace + #{user_url(current_user.username)}/ + = f.hidden_field :namespace_id, value: current_user.namespace_id + .form-group.project-path.col-sm-6 + = f.label :path, class: 'label-light' do + %span + Project name + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true +- if current_user.can_create_group? + .help-block + Want to house several dependent projects under the same namespace? + = link_to "Create a group", new_group_path + +.form-group + = f.label :description, class: 'label-light' do + Project description + %span (optional) + = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250 + +.form-group.visibility-level-setting + = f.label :visibility_level, class: 'label-light' do + Visibility Level + = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' } + = render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false + += f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 += link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel' diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index 5638b7da1b0bd2e649784b8c7112d88c40eaf432..d50175727be4201b26a57136aeaba9dfd5a16b87 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -1,10 +1,24 @@ -.project-templates-buttons.import-buttons{ data: { toggle: "buttons" } } - .btn.blank-option.active - %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: "blank", checked: "true", value: "" } - = icon('file-o', class: 'btn-template-icon') - Blank +.project-templates-buttons.import-buttons - Gitlab::ProjectTemplate.all.each do |template| - .btn - %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name } + .template-option = custom_icon(template.logo) - = template.title + .template-title= template.title + .template-description= template.description + %label.btn.btn-success.template-button.choose-template.append-right-10{ for: template.name } + %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name } + %span Use template + %a.btn.btn-default{ href: template.preview, rel: 'noopener noreferrer', target: '_blank' } Preview + + .project-fields-form + .form-group + %label.label-light + Template + .input-group.template-input-group + .input-group-addon + .selected-icon + - Gitlab::ProjectTemplate.all.each do |template| + = custom_icon(template.logo) + .selected-template + %button.btn.btn-default.change-template{ type: "button" } Change template + + = render 'new_project_fields', f: f, project_name_id: "template-project-name" diff --git a/app/views/projects/clusters/_header.html.haml b/app/views/projects/clusters/_header.html.haml index 0134d46491cff05e297b58dab1558c13f790e928..beb798e71549b051605b10b0b24c331fd635f5fe 100644 --- a/app/views/projects/clusters/_header.html.haml +++ b/app/views/projects/clusters/_header.html.haml @@ -11,4 +11,4 @@ = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements } %li - link_to_container_project = link_to(s_('ClusterIntegration|Google Container Engine project'), target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|A %{link_to_container_project} must have been created under this account').html_safe % { link_to_container_project: link_to_container_project } + = s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project } diff --git a/app/views/projects/clusters/login.html.haml b/app/views/projects/clusters/login.html.haml index ae132672b7e25aaee93e992ef7f4b92599d84647..fde030b500b8a341c28f716f7d6d5d2ed2ee8613 100644 --- a/app/views/projects/clusters/login.html.haml +++ b/app/views/projects/clusters/login.html.haml @@ -10,7 +10,7 @@ .col-sm-8.col-sm-offset-4.signin-with-google - if @authorize_url = link_to @authorize_url do - = image_tag('auth_buttons/signin_with_google.png') + = image_tag('auth_buttons/signin_with_google.png', width: '191px') - else - link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer') = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link } diff --git a/app/views/projects/commit/_ajax_signature.html.haml b/app/views/projects/commit/_ajax_signature.html.haml index 83821326aece5df897686fa6a23acddb15d8d25d..1d6a0fa38ca4ea3a1d3fae6c0f464f1ac170a52c 100644 --- a/app/views/projects/commit/_ajax_signature.html.haml +++ b/app/views/projects/commit/_ajax_signature.html.haml @@ -1,2 +1,2 @@ - if commit.has_signature? - %button{ class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'auto top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } } + %a{ href: '#', tabindex: 0, class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'auto top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } } diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml index edff018ba6da8078d81726eb667cc0be7386484d..b6b7aae6f9a85db32f78ed733a232bdaa29ac435 100644 --- a/app/views/projects/commit/_signature_badge.html.haml +++ b/app/views/projects/commit/_signature_badge.html.haml @@ -24,5 +24,5 @@ = link_to('Learn more about signing commits', help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link') -%button{ class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'auto top', title: title, content: content } } +%a{ href: '#', tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'auto top', title: title, content: content } } = label diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index edaa3a1119edca127bc9066337025719ecbf888e..f9355a31b651e764213fcacc4761ca3175ac7be0 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -10,13 +10,15 @@ %p.light.append-bottom-0 Paste a machine public key here. Read more about how to generate it = link_to "here", help_page_path("ssh/README") - .form-group - .checkbox - = f.label :can_push do - = f.check_box :can_push - %strong Write access allowed - .form-group - %p.light.append-bottom-0 - Allow this key to push to repository as well? (Default only allows pull access.) + = f.fields_for :deploy_keys_projects do |deploy_keys_project_form| + .form-group + .checkbox + = deploy_keys_project_form.label :can_push do + = deploy_keys_project_form.check_box :can_push + %strong Write access allowed + .form-group + %p.light.append-bottom-0 + Allow this key to push to repository as well? (Default only allows pull access.) + = f.submit "Add key", class: "btn-create btn" diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml index 45985a5ecefa04904d548fed9170edb0d3446df0..e75ae87e771bddd43c586760d2cc594bf3981890 100644 --- a/app/views/projects/deploy_keys/_index.html.haml +++ b/app/views/projects/deploy_keys/_index.html.haml @@ -1,5 +1,5 @@ - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Deploy Keys @@ -7,7 +7,7 @@ = expanded ? 'Collapse' : 'Expand' %p Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content %h5.prepend-top-0 Create a new deploy key for this project = render @deploy_keys.form_partial_path diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 8ae4fd941463e6ff27caa2657e3eb118db2a0e5b..5ebeae5c35fc24ea46d9eb6f7090de99771c3c4c 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -4,7 +4,7 @@ - expanded = Rails.env.test? .project-edit-container - %section.settings.general-settings + %section.settings.general-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 General project settings @@ -12,7 +12,7 @@ = expanded ? 'Collapse' : 'Expand' %p Update your project name, description, avatar, and other general settings. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content .project-edit-errors = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f| %fieldset @@ -61,7 +61,7 @@ = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = f.submit 'Save changes', class: "btn btn-save" - %section.settings.sharing-permissions + %section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Permissions @@ -69,13 +69,13 @@ = expanded ? 'Collapse' : 'Expand' %p Enable or disable certain project features and choose access levels. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f| %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project) .js-project-permissions-form = f.submit 'Save changes', class: "btn btn-save" - %section.settings.merge-requests-feature{ class: ("hidden" if @project.project_feature.send(:merge_requests_access_level) == 0) } + %section.settings.merge-requests-feature.no-animate{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] } .settings-header %h4 Merge request settings @@ -83,22 +83,22 @@ = expanded ? 'Collapse' : 'Expand' %p Customize your merge request restrictions. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f| = render 'merge_request_settings', form: f = f.submit 'Save changes', class: "btn btn-save" = render 'export', project: @project - %section.settings.advanced-settings + %section.settings.advanced-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Advanced settings %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' %p - Perform advanced options such as housekeeping, exporting, archiving, renaming, transferring, or removing your project. - .settings-content.no-animate{ class: ('expanded' if expanded) } + Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project. + .settings-content .sub-section %h4 Housekeeping %p @@ -173,7 +173,10 @@ %p This will remove the fork relationship to source project = succeed "." do - = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) + - if @project.fork_source + = link_to(fork_source_name(@project), project_path(@project.fork_source)) + - else + = fork_source_name(@project) = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f| %p %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source. diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 3f3ce10419fa11ca44ef3ba4b79c90bede4fe2da..c9956183e12a97b9d68f694ca0b1fd47a81cbc3f 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -24,10 +24,15 @@ %p You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. + - if show_auto_devops_callout?(@project) + %p + - link = link_to(s_('AutoDevOps|Auto DevOps (Beta)'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings')) + = s_('AutoDevOps|You can activate %{link_to_settings} for this project.').html_safe % { link_to_settings: link } + %p + = s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') + - if can?(current_user, :push_code, @project) %div{ class: container_class } - - if show_auto_devops_callout?(@project) - = render 'shared/auto_devops_callout' .prepend-top-20 .empty_wrapper %h3.page-title-empty diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml index 05b06cfc8b292070f853130d877590d2418ab7bb..8096d9530c32d0ca3f5c1eb202be38e1d3889775 100644 --- a/app/views/projects/hook_logs/_index.html.haml +++ b/app/views/projects/hook_logs/_index.html.haml @@ -24,7 +24,7 @@ %td = truncate(hook_log.url, length: 50) %td.light - #{number_with_precision(hook_log.execution_duration, precision: 2)} ms + #{number_with_precision(hook_log.execution_duration, precision: 2)} sec %td.light = time_ago_with_tooltip(hook_log.created_at) %td diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..1b7d878c38c0db14eb88a6146d7f048798a3d426 --- /dev/null +++ b/app/views/projects/issues/edit.html.haml @@ -0,0 +1,7 @@ +- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues" + +%h3.page-title + Edit Issue ##{@issue.iid} +%hr + += render "form" diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 6b8dcb3e60bab88dcca57df43a2f243de0fc8acb..8da2243adefdfa38c95a78c63f6d8383a3e033c9 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -13,8 +13,6 @@ - if @project.merge_requests.exists? %div{ class: container_class } - - if show_auto_devops_callout?(@project) - = render 'shared/auto_devops_callout' .top-area = render 'shared/issuable/nav', type: :merge_requests .nav-controls diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index cc41b908946c0ced39aa4d53ae25ece8fd860b42..0a835dcdeb091360d99f19f082cd8e74376da16a 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -14,114 +14,88 @@ .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 New project - - if import_sources_enabled? - %p - A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}. - %p - All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings. + %p + A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}. + %p + All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings. .col-lg-9.js-toggle-container - = form_for @project, html: { class: 'new_project' } do |f| - .create-project-options - .first-column + %ul.nav-links.gitlab-tabs{ role: 'tablist' } + %li.active{ role: 'presentation' } + %a{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' } + %span.hidden-xs Blank project + %span.visible-xs Blank + %li{ role: 'presentation' } + %a{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' } + %span.hidden-xs Create from template + %span.visible-xs Template + %li{ role: 'presentation' } + %a{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' } + %span.hidden-xs Import project + %span.visible-xs Import + + .tab-content.gitlab-tab-content + .tab-pane.active{ id: 'blank-project-pane', role: 'tabpanel' } + = form_for @project, html: { class: 'new_project' } do |f| + = render 'new_project_fields', f: f, project_name_id: "blank-project-name" + + .tab-pane.no-padding{ id: 'create-from-template-pane', role: 'tabpanel' } + = form_for @project, html: { class: 'new_project' } do |f| .project-template .form-group - = f.label :template_project, class: 'label-light' do - Create from template - = link_to icon('question-circle'), help_page_path("gitlab-basics/create-project"), target: '_blank', aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'} %div = render 'project_templates', f: f - - if import_sources_enabled? - .second-column - .project-import - .form-group.clearfix - = f.label :visibility_level, class: 'label-light' do #the label here seems wrong - Import project from - .col-sm-12.import-buttons - %div - - if github_import_enabled? - = link_to new_import_github_path, class: 'btn import_github' do - = icon('github', text: 'GitHub') - %div - - if bitbucket_import_enabled? - = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do - = icon('bitbucket', text: 'Bitbucket') - - unless bitbucket_import_configured? - = render 'bitbucket_import_modal' - %div - - if gitlab_import_enabled? - = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do - = icon('gitlab', text: 'GitLab.com') - - unless gitlab_import_configured? - = render 'gitlab_import_modal' - %div - - if google_code_import_enabled? - = link_to new_import_google_code_path, class: 'btn import_google_code' do - = icon('google', text: 'Google Code') - %div - - if fogbugz_import_enabled? - = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do - = icon('bug', text: 'Fogbugz') - %div - - if gitea_import_enabled? - = link_to new_import_gitea_url, class: 'btn import_gitea' do - = custom_icon('go_logo') - Gitea - %div - - if git_import_enabled? - %button.btn.js-toggle-button.import_git{ type: "button" } - = icon('git', text: 'Repo by URL') - - if gitlab_project_import_enabled? - .import_gitlab_project.has-tooltip{ data: { container: 'body' } } - = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do - = icon('gitlab', text: 'GitLab export') - - .row - .col-lg-12 - .js-toggle-content.hide - %hr - = render "shared/import_form", f: f - %hr - - .row - .form-group.col-xs-12.col-sm-6 - = f.label :namespace_id, class: 'label-light' do - %span - Project path - .form-group - .input-group - - if current_user.can_select_namespace? - .input-group-addon - = root_url - = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} - - - else - .input-group-addon.static-namespace - #{root_url}#{current_user.username}/ - = f.hidden_field :namespace_id, value: current_user.namespace_id - .form-group.col-xs-12.col-sm-6.project-path - = f.label :path, class: 'label-light' do - %span - Project name - = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true - - if current_user.can_create_group? - .help-block - Want to house several dependent projects under the same namespace? - = link_to "Create a group", new_group_path - - .form-group - = f.label :description, class: 'label-light' do - Project description - %span.light (optional) - = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250 - - .form-group.visibility-level-setting - = f.label :visibility_level, class: 'label-light' do - Visibility Level - = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' } - = render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false - = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 - = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel' + .tab-pane.import-project-pane{ id: 'import-project-pane', role: 'tabpanel' } + = form_for @project, html: { class: 'new_project' } do |f| + - if import_sources_enabled? + .project-import.row + .col-sm-12 + .form-group.import-btn-container.clearfix + = f.label :visibility_level, class: 'label-light' do #the label here seems wrong + Import project from + .import-buttons + %div + - if github_import_enabled? + = link_to new_import_github_path, class: 'btn import_github' do + = icon('github', text: 'GitHub') + %div + - if bitbucket_import_enabled? + = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do + = icon('bitbucket', text: 'Bitbucket') + - unless bitbucket_import_configured? + = render 'bitbucket_import_modal' + %div + - if gitlab_import_enabled? + = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do + = icon('gitlab', text: 'GitLab.com') + - unless gitlab_import_configured? + = render 'gitlab_import_modal' + %div + - if google_code_import_enabled? + = link_to new_import_google_code_path, class: 'btn import_google_code' do + = icon('google', text: 'Google Code') + %div + - if fogbugz_import_enabled? + = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do + = icon('bug', text: 'Fogbugz') + %div + - if gitea_import_enabled? + = link_to new_import_gitea_url, class: 'btn import_gitea' do + = custom_icon('go_logo') + Gitea + %div + - if git_import_enabled? + %button.btn.js-toggle-button.import_git{ type: "button" } + = icon('git', text: 'Repo by URL') + - if gitlab_project_import_enabled? + .import_gitlab_project.has-tooltip{ data: { container: 'body' } } + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do + = icon('gitlab', text: 'GitLab export') + .col-lg-12 + .js-toggle-content.hide.toggle-import-form + %hr + = render "shared/import_form", f: f + = render 'new_project_fields', f: f, project_name_id: "import-url-name" .save-project-loader.hide .center diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index a10a7c239243612620d2071f22fa9cb39065b324..f8627a3818b47aa4232c39619177947c0e2e9d86 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -2,8 +2,6 @@ - page_title "Pipelines" %div{ 'class' => container_class } - - if show_auto_devops_callout?(@project) - = render 'shared/auto_devops_callout' #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), "help-page-path" => help_page_path('ci/quick_start/README'), "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 25153fd0b6ff4456c920f11cc9f1f8ac70ec96b6..fd5d3ec56daf31d7b2b89225340b8176a82d573b 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -17,14 +17,14 @@ %i Owners .light - if can?(current_user, :admin_project_member, @project) - %ul.nav-links.project-member-tabs{ role: 'tablist' } + %ul.nav-links.gitlab-tabs{ role: 'tablist' } %li.active{ role: 'presentation' } %a{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member - if @project.allowed_to_share_with_group? %li{ role: 'presentation' } %a{ href: '#share-with-group-pane', id: 'share-with-group-tab', data: { toggle: 'tab' }, role: 'tab' } Share with group - .tab-content.project-member-tab-content + .tab-content.gitlab-tab-content .tab-pane.active{ id: 'add-member-pane', role: 'tabpanel' } = render 'projects/project_members/new_project_member', tab_title: 'Add member' .tab-pane{ id: 'share-with-group-pane', role: 'tabpanel' } diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml index 6a47cbdf7249c98175262599897f1d88c4afef44..ba7d98228c345de63f27cd63a85d205f7d2209cb 100644 --- a/app/views/projects/protected_branches/shared/_index.html.haml +++ b/app/views/projects/protected_branches/shared/_index.html.haml @@ -1,6 +1,6 @@ - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Protected Branches @@ -8,7 +8,7 @@ = expanded ? 'Collapse' : 'Expand' %p Keep stable branches secure and force developers to use merge requests. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content %p By default, protected branches are designed to: %ul diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml index c07bd454ff655236becc830ddfd718ed6347b92d..e764a37bbd723f4385b251d9555497f26f7bdcac 100644 --- a/app/views/projects/protected_tags/shared/_index.html.haml +++ b/app/views/projects/protected_tags/shared/_index.html.haml @@ -1,6 +1,6 @@ - expanded = Rails.env.test? -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Protected Tags @@ -8,7 +8,7 @@ = expanded ? 'Collapse' : 'Expand' %p Limit access to creating and updating tags. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content %p By default, protected tags are designed to: %ul diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 62455d0d40de24ba4f7efc45957285436fb600f6..664a455469238cf5caa6fe52a1532abaa876c1e6 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -4,7 +4,7 @@ - expanded = Rails.env.test? -%section.settings#js-general-pipeline-settings +%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 General pipelines settings @@ -12,10 +12,10 @@ = expanded ? 'Collapse' : 'Expand' %p Update your CI/CD configuration, like job timeout or Auto DevOps. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'projects/pipelines_settings/show' -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Runners settings @@ -23,10 +23,10 @@ = expanded ? 'Collapse' : 'Expand' %p Register and see your runners for this project. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'projects/runners/index' -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Secret variables @@ -35,10 +35,10 @@ = expanded ? 'Collapse' : 'Expand' %p = render "ci/variables/content" - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'ci/variables/index' -%section.settings +%section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 Pipeline triggers @@ -48,5 +48,5 @@ Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions. - .settings-content.no-animate{ class: ('expanded' if expanded) } + .settings-content = render 'projects/triggers/index' diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 0cc6674842a62f8a7ea5e45608a8e7c368bf8941..745a6040488ffd8123727086e146f26744c72440 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -12,7 +12,5 @@ = webpack_bundle_tag 'repo' %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - - if show_auto_devops_callout?(@project) && !show_new_repo? - = render 'shared/auto_devops_callout' = render 'projects/last_push' = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) diff --git a/app/views/projects/wikis/empty.html.haml b/app/views/projects/wikis/empty.html.haml index 911e1339541a7d1702c5d7f625926b3a019424d7..d6e568bac944473a7c483cb1250f822ecdf3b2ea 100644 --- a/app/views/projects/wikis/empty.html.haml +++ b/app/views/projects/wikis/empty.html.haml @@ -1,6 +1,6 @@ - page_title _("Wiki") -%h3.page-title= _("Wiki|Empty page") +%h3.page-title= s_("Wiki|Empty page") %hr .error_message = s_("WikiEmptyPageError|You are not allowed to create wiki pages") diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml index 7c633175a068edd47ddb01da7f1b6885242d5fc0..934d65e8b42f52382c517cfb221bcff9c2d2200a 100644 --- a/app/views/shared/_auto_devops_callout.html.haml +++ b/app/views/shared/_auto_devops_callout.html.haml @@ -1,15 +1,16 @@ -.user-callout{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } } - .bordered-box.landing.content-block - %button.btn.btn-default.close.js-close-callout{ type: 'button', - 'aria-label' => 'Dismiss Auto DevOps box' } - = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') - .svg-container - = custom_icon('icon_autodevops') - .user-callout-copy - %h4= s_('AutoDevOps|Auto DevOps (Beta)') - %p= s_('AutoDevOps|Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') - %p - - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') - = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } +.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } } + .banner-graphic + = custom_icon('icon_autodevops') - = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout' + .prepend-top-10.prepend-left-10.append-bottom-10 + %h5= s_('AutoDevOps|Auto DevOps (Beta)') + %p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') + %p + - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') + = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } + .prepend-top-10 + = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout' + + %button.btn-transparent.banner-close.close.js-close-callout{ type: 'button', + 'aria-label' => 'Dismiss Auto DevOps box' } + = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml index 1f540bdaf935725569130f404ee74759c2673af7..dfc0f9be32100e1d741e8537696569d095ddd0e6 100644 --- a/app/views/shared/boards/components/sidebar/_labels.html.haml +++ b/app/views/shared/boards/components/sidebar/_labels.html.haml @@ -25,7 +25,7 @@ show_any: "true", project_id: @project&.try(:id), labels: labels_filter_path(false), - namespace_path: @project.try(:namespace).try(:full_path), + namespace_path: @namespace_path, project_path: @project.try(:path) }, ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" } %span.dropdown-toggle-text diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml index e6075c3ae3a32adebbe423a06cdfc8150f475b05..87c2965bb216146f89be1a574b8014b7692fcbd1 100644 --- a/app/views/shared/deploy_keys/_form.html.haml +++ b/app/views/shared/deploy_keys/_form.html.haml @@ -1,5 +1,6 @@ - form = local_assigns.fetch(:form) - deploy_key = local_assigns.fetch(:deploy_key) +- deploy_keys_project = deploy_key.deploy_keys_project_for(@project) = form_errors(deploy_key) @@ -20,11 +21,13 @@ .col-sm-10 = form.text_field :fingerprint, class: 'form-control', readonly: 'readonly' -.form-group - .control-label - .col-sm-10 - = form.label :can_push do - = form.check_box :can_push - %strong Write access allowed - %p.light.append-bottom-0 - Allow this key to push to repository as well? (Default only allows pull access.) +- if deploy_keys_project.present? + = form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form| + .form-group + .control-label + .col-sm-10 + = deploy_keys_project_form.label :can_push do + = deploy_keys_project_form.check_box :can_push + %strong Write access allowed + %p.light.append-bottom-0 + Allow this key to push to repository as well? (Default only allows pull access.) diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index b361ec86cedd20f4d5d2e43e57a36eeb18683d6b..63f62eb476e5fe2da8e5cc75bd2b5e6637f0ea80 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -28,7 +28,7 @@ .avatar-container.s40 = link_to group do - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + = group_icon(group, class: "avatar s40 hidden-xs") .title = link_to group_name, group, class: 'group-name' diff --git a/app/views/shared/hook_logs/_content.html.haml b/app/views/shared/hook_logs/_content.html.haml index af6a499fadb065e0d0d089a904b90d95a12a067c..c80b179d525d8c8682d319fdb6cffe2ebef4dd5c 100644 --- a/app/views/shared/hook_logs/_content.html.haml +++ b/app/views/shared/hook_logs/_content.html.haml @@ -11,7 +11,7 @@ = hook_log.trigger.singularize.titleize %p %strong Elapsed time: - #{number_with_precision(hook_log.execution_duration, precision: 2)} ms + #{number_with_precision(hook_log.execution_duration, precision: 2)} sec %p %strong Request time: = time_ago_with_tooltip(hook_log.created_at) diff --git a/app/views/shared/icons/_express.svg b/app/views/shared/icons/_express.svg index f2c94319f19377e38db250e8b5409152d2ee2836..a51e81e556855aba2c848733cded5c4cf58874d9 100644 --- a/app/views/shared/icons/_express.svg +++ b/app/views/shared/icons/_express.svg @@ -1,6 +1 @@ - - - - - - + diff --git a/app/views/shared/icons/_icon_autodevops.svg b/app/views/shared/icons/_icon_autodevops.svg index 807ff27bb674a3678803539ff9cedacd658330db..7e47c084bde6e1c75884e20bc17c487f32aa0038 100644 --- a/app/views/shared/icons/_icon_autodevops.svg +++ b/app/views/shared/icons/_icon_autodevops.svg @@ -1,4 +1,4 @@ - + diff --git a/app/views/shared/icons/_rails.svg b/app/views/shared/icons/_rails.svg index 0bb09a705df4b1c648d066db8d99901fefa118bf..852bd183cc7dd370b5dcc9c279d7021da8e5190b 100644 --- a/app/views/shared/icons/_rails.svg +++ b/app/views/shared/icons/_rails.svg @@ -1,6 +1 @@ - - - - - - + diff --git a/app/views/shared/icons/_spring.svg b/app/views/shared/icons/_spring.svg index 508349aa456fa57974b1db178e613875725e5fb2..ccf18749029bb31802f053fff4019b67a545f46c 100644 --- a/app/views/shared/icons/_spring.svg +++ b/app/views/shared/icons/_spring.svg @@ -1,6 +1 @@ - - - - - - + diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index bcdad3c153a622629d1db465b3f1b76a2be2e45b..5868c52566d09f2c8ab302ca2f68a168fbe4f9c7 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -4,7 +4,7 @@ - dom_id = "group_member_#{group_link.id}" %li.member.group_member{ id: dom_id } %span.list-item-name - = image_tag group_icon(group), class: "avatar s40", alt: '' + = group_icon(group, class: "avatar s40", alt: '') %strong = link_to group.full_name, group_path(group) .cgray diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml index 8bbaf431536dc19b9de7a56b9c47226dcc1a6a1f..ae437dd16d6452936a7d8b4c7300d25bdac370c6 100644 --- a/app/views/shared/tokens/_scopes_form.html.haml +++ b/app/views/shared/tokens/_scopes_form.html.haml @@ -7,3 +7,4 @@ = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}" = label_tag ("#{prefix}_scopes_#{scope}"), scope %span= t(scope, scope: [:doorkeeper, :scopes]) + .scope-description= t scope, scope: [:doorkeeper, :scope_desc] diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index eff6c80d1442b93c3bab6dc628f3309d81902ec4..55799e10a46374788a61deed87f6c03069e014ac 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -2,4 +2,4 @@ - groups.each do |group| = link_to group, class: 'profile-groups-avatars inline', title: group.name do .avatar-container.s40 - = image_tag group_icon(group), class: 'avatar group-avatar s40' + = group_icon(group, class: 'avatar group-avatar s40') diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb index 7843179d77c92c3a067b6a65131a03a7f7b9f7ad..a396c0f27b209935780ac01f741201b522044d1f 100644 --- a/app/workers/stuck_merge_jobs_worker.rb +++ b/app/workers/stuck_merge_jobs_worker.rb @@ -23,7 +23,7 @@ class StuckMergeJobsWorker merge_requests = MergeRequest.where(id: completed_ids) merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged) - merge_requests.where(merge_commit_sha: nil).update_all(state: :opened) + merge_requests.where(merge_commit_sha: nil).update_all(state: :opened, merge_jid: nil) Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}") end diff --git a/changelogs/unreleased/13637-show-account-confirmation-link-in-e-mail-text.yml b/changelogs/unreleased/13637-show-account-confirmation-link-in-e-mail-text.yml deleted file mode 100644 index 5f98d0cc76696cdcc0263dfdac802053bf79fef7..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/13637-show-account-confirmation-link-in-e-mail-text.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Confirmation email shows link as text instead of human readable text -merge_request: 14243 -author: bitsapien -type: changed diff --git a/changelogs/unreleased/13711-allow-same-period-housekeeping.yml b/changelogs/unreleased/13711-allow-same-period-housekeeping.yml deleted file mode 100644 index 607a8683aff6a98a1bf1a1c97f0012310cc14810..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/13711-allow-same-period-housekeeping.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Allow to use same periods for different housekeeping tasks (effectively - skipping the lesser task) -merge_request: 13711 -author: cernvcs -type: added diff --git a/changelogs/unreleased/14395-upgrade-gitlab-markup.yml b/changelogs/unreleased/14395-upgrade-gitlab-markup.yml deleted file mode 100644 index d1f90fe5eb13bf0479a4b309d958b532fb96b2f8..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/14395-upgrade-gitlab-markup.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade gitlab-markup gem -merge_request: 14395 -author: Markus Koller -type: other diff --git a/changelogs/unreleased/14553-missing-space-in-log-msg.yml b/changelogs/unreleased/14553-missing-space-in-log-msg.yml deleted file mode 100644 index a0420d49770154b025a86b1a1893605f1a8a5f55..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/14553-missing-space-in-log-msg.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Add missing space in Sidekiq memory killer log message" -merge_request: 14553 -author: Benjamin Drung -type: fixed diff --git a/changelogs/unreleased/18308-escape-characters.yml b/changelogs/unreleased/18308-escape-characters.yml deleted file mode 100644 index 8766e971490fe991927f47047fccab210a61fc6a..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/18308-escape-characters.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Escape quotes in git username -merge_request: 14020 -author: Brandon Everett -type: fixed diff --git a/changelogs/unreleased/18608-lock-issues.yml b/changelogs/unreleased/18608-lock-issues.yml deleted file mode 100644 index 7d907f744f6ee520b04e15fab455acc6cf8f0f88..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/18608-lock-issues.yml +++ /dev/null @@ -1,4 +0,0 @@ -title: Discussion lock for issues and merge requests -merge_request: -author: -type: added diff --git a/changelogs/unreleased/20049-projects-api-forks.yml b/changelogs/unreleased/20049-projects-api-forks.yml deleted file mode 100644 index c6470620f576b17c7bcc344e366ce0a9b6208971..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/20049-projects-api-forks.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add an API endpoint to determine the forks of a project -merge_request: -author: -type: added diff --git a/changelogs/unreleased/20824-scope-users-to-members-in-group-issuable-list.yml b/changelogs/unreleased/20824-scope-users-to-members-in-group-issuable-list.yml deleted file mode 100644 index 245b8129de8d2c158c8ffa8c8cd0d79ad329e588..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/20824-scope-users-to-members-in-group-issuable-list.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Return only group's members in user dropdowns on issuables list pages -merge_request: 14249 -author: -type: changed diff --git a/changelogs/unreleased/21331-improve-confusing-compare-page.yml b/changelogs/unreleased/21331-improve-confusing-compare-page.yml deleted file mode 100644 index 469cc04930b2b87ce9d70349a5b4f3a466262edd..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/21331-improve-confusing-compare-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make the labels in the Compare form less confusing -merge_request: 14225 -author: -type: changed diff --git a/changelogs/unreleased/23888-fix-unsubscription-link-for-snippet-notification.yml b/changelogs/unreleased/23888-fix-unsubscription-link-for-snippet-notification.yml deleted file mode 100644 index 36bed0371600e106d316fcfe00e0297f39dd8ce0..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/23888-fix-unsubscription-link-for-snippet-notification.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Don't show an "Unsubscribe" link in snippet comment notifications -merge_request: 14764 -author: -type: fixed diff --git a/changelogs/unreleased/24121_extract_yet_another_users_finder.yml b/changelogs/unreleased/24121_extract_yet_another_users_finder.yml deleted file mode 100644 index e43e97303e28b8482d0b6894e1181eb6c4a6d1da..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/24121_extract_yet_another_users_finder.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Extract AutocompleteController#users into finder -merge_request: 13778 -author: Maxim Rydkin, Mayra Cabrera -type: other diff --git a/changelogs/unreleased/26890-fix-default-branches-sorting.yml b/changelogs/unreleased/26890-fix-default-branches-sorting.yml deleted file mode 100644 index cf7060190b3e14d15d8267ebf8b0a4f65fb46e73..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/26890-fix-default-branches-sorting.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix the default branches sorting to actually be 'Last updated' -merge_request: 14295 -author: -type: fixed diff --git a/changelogs/unreleased/31358_decrease_perceived_complexity_threshold_step2.yml b/changelogs/unreleased/31358_decrease_perceived_complexity_threshold_step2.yml deleted file mode 100644 index 6036e1a43a090c02abd4615cda9a3ab0e98bf3db..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/31358_decrease_perceived_complexity_threshold_step2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Decrease Perceived Complexity threshold to 15 -merge_request: 14160 -author: Maxim Rydkin -type: other diff --git a/changelogs/unreleased/31362_decrease_cyclomatic_complexity_threshold_step4.yml b/changelogs/unreleased/31362_decrease_cyclomatic_complexity_threshold_step4.yml deleted file mode 100644 index a404456198a25449f8cf43239076bd9e9cb1e324..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/31362_decrease_cyclomatic_complexity_threshold_step4.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Decrease Cyclomatic Complexity threshold to 13 -merge_request: 14152 -author: Maxim Rydkin -type: other diff --git a/changelogs/unreleased/32163-protected-branch-form-should-have-sane-defaults-for-dropdowns.yml b/changelogs/unreleased/32163-protected-branch-form-should-have-sane-defaults-for-dropdowns.yml deleted file mode 100644 index 6110e2450139a1083a8798858974054315013db0..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/32163-protected-branch-form-should-have-sane-defaults-for-dropdowns.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added defaults for protected branches dropdowns on the repository settings -merge_request: 14278 -author: -type: changed diff --git a/changelogs/unreleased/33328-usage-ping-for-gitlab-features-and-components.yml b/changelogs/unreleased/33328-usage-ping-for-gitlab-features-and-components.yml deleted file mode 100644 index d3aac241b7511eca34df1850b6f0358121e68595..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/33328-usage-ping-for-gitlab-features-and-components.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds gitlab features and components to usage ping data. -merge_request: 14305 -author: -type: other diff --git a/changelogs/unreleased/33493-attempt-to-link-saml-users-to-ldap-by-email.yml b/changelogs/unreleased/33493-attempt-to-link-saml-users-to-ldap-by-email.yml deleted file mode 100644 index 727f3cecd521cfce2e65ea81dec90283d742d3e6..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/33493-attempt-to-link-saml-users-to-ldap-by-email.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Link SAML users to LDAP by email. -merge_request: 14216 -author: -type: changed diff --git a/changelogs/unreleased/34102-online-view-of-artifacts-fe.yml b/changelogs/unreleased/34102-online-view-of-artifacts-fe.yml deleted file mode 100644 index ce83b140eb699febf74450505184dbcfb83f5629..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/34102-online-view-of-artifacts-fe.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add online view of HTML artifacts for public projects -merge_request: 14399 -author: -type: added diff --git a/changelogs/unreleased/34259-project-denial-of-service-via-gitmodules-fix.yml b/changelogs/unreleased/34259-project-denial-of-service-via-gitmodules-fix.yml deleted file mode 100644 index 8260f7fa4b23db27806ac0348c81316bead542a6..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/34259-project-denial-of-service-via-gitmodules-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes project denial of service via gitmodules using Extended ASCII. -merge_request: 14301 -author: -type: fixed diff --git a/changelogs/unreleased/34366-issue-sidebar-don-t-render-participants-in-collapsed-state.yml b/changelogs/unreleased/34366-issue-sidebar-don-t-render-participants-in-collapsed-state.yml deleted file mode 100644 index d34e685b5f5f8159548daeeaca7aaaac04cdc434..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/34366-issue-sidebar-don-t-render-participants-in-collapsed-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Load sidebar participants avatars only when visible -merge_request: 14270 -author: -type: other diff --git a/changelogs/unreleased/34371-cycle-analitcs-global.yml b/changelogs/unreleased/34371-cycle-analitcs-global.yml deleted file mode 100644 index 5e9f0a85e9a4bded0fe52c5ce5dbf51521f97490..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/34371-cycle-analitcs-global.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removes cycle analytics service and store from global namespace -merge_request: -author: -type: other diff --git a/changelogs/unreleased/34510-board-issues-sql-speedup.yml b/changelogs/unreleased/34510-board-issues-sql-speedup.yml deleted file mode 100644 index 244ff7e9dfa4cf189bbb42177c78609db006e6e2..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/34510-board-issues-sql-speedup.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Optimize the boards' issues fetching. -merge_request: 14198 -author: -type: other diff --git a/changelogs/unreleased/3523-i18n-autodevops.yml b/changelogs/unreleased/3523-i18n-autodevops.yml deleted file mode 100644 index 10cb22b42a03ae5634c336858695616f0e40eebc..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/3523-i18n-autodevops.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improves i18n for Auto Devops callout -merge_request: -author: -type: other diff --git a/changelogs/unreleased/35290_allow_public_project_apis.yml b/changelogs/unreleased/35290_allow_public_project_apis.yml deleted file mode 100644 index 1968eee0a531fb61a589909715379a166d71e560..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/35290_allow_public_project_apis.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: made read-only APIs for public merge requests available without authentication -merge_request: 13291 -author: haseebeqx diff --git a/changelogs/unreleased/35917_create_services_for_keys.yml b/changelogs/unreleased/35917_create_services_for_keys.yml deleted file mode 100644 index e7cad5a11d53f595cbe838740792e3912f1dc9cc..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/35917_create_services_for_keys.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: creation of keys moved to services -merge_request: 13331 -author: haseebeqx diff --git a/changelogs/unreleased/3612-update-script-template-order-in-vue-files.yml b/changelogs/unreleased/3612-update-script-template-order-in-vue-files.yml deleted file mode 100644 index cea6cb2e48b59912607090b6b23fa4905a2d03d4..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/3612-update-script-template-order-in-vue-files.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Re-arrange " - allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) - avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "" @@ -307,4 +309,12 @@ describe ApplicationHelper do end end end + + describe '#locale_path' do + it 'returns the locale path with an `_`' do + Gitlab::I18n.with_locale('pt-BR') do + expect(helper.locale_path).to include('assets/locale/pt_BR/app') + end + end + end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 76e5964ccf75b24f42466a43a0f6b61d08df89b6..97f0ed4904e415e38c8f74a3fc7badc9fe027669 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe GroupsHelper do include ApplicationHelper + let(:asset_host) { 'http://assets' } + describe 'group_icon' do avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') @@ -10,14 +12,53 @@ describe GroupsHelper do group = create(:group) group.avatar = fixture_file_upload(avatar_file_path) group.save! - expect(group_icon(group.path).to_s) + + avatar_url = "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(helper.group_icon(group).to_s) + .to eq "" + + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(helper.group_icon(group).to_s) + .to eq "" + end + end + + describe 'group_icon_url' do + avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') + + it 'returns an url for the avatar' do + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an CDN url for the avatar' do + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an based url for the avatar if private' do + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + group = create(:group, :private) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") end it 'gives default avatar_icon when no avatar is present' do group = create(:group) group.save! - expect(group_icon(group.path)).to match_asset_path('group_avatar.png') + expect(group_icon_url(group.path)).to match_asset_path('group_avatar.png') end end diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb index 9a974e70e8c79fe54e45f54a26b2bf45b4aa5174..a11824d0ac5741ec1c08f6255e0524d21abd0142 100644 --- a/spec/initializers/settings_spec.rb +++ b/spec/initializers/settings_spec.rb @@ -18,26 +18,6 @@ describe Settings do end end - describe '#repositories' do - it 'assigns the default failure attributes' do - repository_settings = Gitlab.config.repositories.storages['broken'] - - expect(repository_settings['failure_count_threshold']).to eq(10) - expect(repository_settings['failure_wait_time']).to eq(30) - expect(repository_settings['failure_reset_time']).to eq(1800) - expect(repository_settings['storage_timeout']).to eq(5) - end - - it 'can be accessed with dot syntax all the way down' do - expect(Gitlab.config.repositories.storages.broken.failure_count_threshold).to eq(10) - end - - it 'can be accessed in a very specific way that breaks without reassigning each element with Settingslogic' do - storage_settings = Gitlab.config.repositories.storages['broken'] - expect(storage_settings.failure_count_threshold).to eq(10) - end - end - describe '#host_without_www' do context 'URL with protocol' do it 'returns the host' do diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js index 5b64cbb2dfc6f06723ffe86251afb8944f332099..c253648007f262598fa8378a4adeda51dba778b7 100644 --- a/spec/javascripts/deploy_keys/components/key_spec.js +++ b/spec/javascripts/deploy_keys/components/key_spec.js @@ -52,18 +52,24 @@ describe('Deploy keys key', () => { ).toBe('Remove'); }); - it('shows write access text when key has write access', (done) => { - vm.deployKey.can_push = true; + it('shows write access title when key has write access', (done) => { + vm.deployKey.deploy_keys_projects[0].can_push = true; Vue.nextTick(() => { expect( - vm.$el.querySelector('.write-access-allowed'), - ).not.toBeNull(); - - expect( - vm.$el.querySelector('.write-access-allowed').textContent.trim(), + vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'), ).toBe('Write access allowed'); + done(); + }); + }); + + it('does not show write access title when key has write access', (done) => { + vm.deployKey.deploy_keys_projects[0].can_push = false; + Vue.nextTick(() => { + expect( + vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'), + ).toBe('Read access only'); done(); }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 67166802c70551914bdf49bcae7684c87c3ded71..2ecb64d84b54f44ba425ffbc56a7e6b47e61c5a9 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -791,6 +791,29 @@ describe('Filtered Search Visual Tokens', () => { expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name); const avatar = tokenValueElement.querySelector('img.avatar'); expect(avatar.src).toBe(dummyUser.avatar_url); + expect(avatar.alt).toBe(''); + }) + .then(done) + .catch(done.fail); + }); + + it('escapes user name when creating token', (done) => { + const dummyUser = { + name: '#{section_end}" + + expect(convert_html(text)).not_to include('