diff options
Diffstat (limited to 'app')
26 files changed, 424 insertions, 329 deletions
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js new file mode 100644 index 00000000000..800ca05cd11 --- /dev/null +++ b/app/assets/javascripts/feature_highlight/feature_highlight.js @@ -0,0 +1,61 @@ +import Cookies from 'js-cookie'; +import _ from 'underscore'; +import { + getCookieName, + getSelector, + hidePopover, + setupDismissButton, + mouseenter, + mouseleave, +} from './feature_highlight_helper'; + +export const setupFeatureHighlightPopover = (id, debounceTimeout = 300) => { + const $selector = $(getSelector(id)); + const $parent = $selector.parent(); + const $popoverContent = $parent.siblings('.feature-highlight-popover-content'); + const hideOnScroll = hidePopover.bind($selector); + const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout); + + $selector + // Setup popover + .data('content', $popoverContent.prop('outerHTML')) + .popover({ + html: true, + // Override the existing template to add custom CSS classes + template: ` + <div class="popover feature-highlight-popover" role="tooltip"> + <div class="arrow"></div> + <div class="popover-content"></div> + </div> + `, + }) + .on('mouseenter', mouseenter) + .on('mouseleave', debouncedMouseleave) + .on('inserted.bs.popover', setupDismissButton) + .on('show.bs.popover', () => { + window.addEventListener('scroll', hideOnScroll); + }) + .on('hide.bs.popover', () => { + window.removeEventListener('scroll', hideOnScroll); + }) + // Display feature highlight + .removeAttr('disabled'); +}; + +export const shouldHighlightFeature = (id) => { + const element = document.querySelector(getSelector(id)); + const previouslyDismissed = Cookies.get(getCookieName(id)) === 'true'; + + return element && !previouslyDismissed; +}; + +export const highlightFeatures = (highlightOrder) => { + const featureId = highlightOrder.find(shouldHighlightFeature); + + if (featureId) { + setupFeatureHighlightPopover(featureId); + return true; + } + + return false; +}; diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js new file mode 100644 index 00000000000..9f741355cd7 --- /dev/null +++ b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js @@ -0,0 +1,57 @@ +import Cookies from 'js-cookie'; + +export const getCookieName = cookieId => `feature-highlighted-${cookieId}`; +export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; + +export const showPopover = function showPopover() { + if (this.hasClass('js-popover-show')) { + return false; + } + this.popover('show'); + this.addClass('disable-animation js-popover-show'); + + return true; +}; + +export const hidePopover = function hidePopover() { + if (!this.hasClass('js-popover-show')) { + return false; + } + this.popover('hide'); + this.removeClass('disable-animation js-popover-show'); + + return true; +}; + +export const dismiss = function dismiss(cookieId) { + Cookies.set(getCookieName(cookieId), true); + hidePopover.call(this); + this.hide(); +}; + +export const mouseleave = function mouseleave() { + if (!$('.popover:hover').length > 0) { + const $featureHighlight = $(this); + hidePopover.call($featureHighlight); + } +}; + +export const mouseenter = function mouseenter() { + const $featureHighlight = $(this); + + const showedPopover = showPopover.call($featureHighlight); + if (showedPopover) { + $('.popover') + .on('mouseleave', mouseleave.bind($featureHighlight)); + } +}; + +export const setupDismissButton = function setupDismissButton() { + const popoverId = this.getAttribute('aria-describedby'); + const cookieId = this.dataset.highlight; + const $popover = $(this); + const dismissWrapper = dismiss.bind($popover, cookieId); + + $(`#${popoverId} .dismiss-feature-highlight`) + .on('click', dismissWrapper); +}; diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_options.js b/app/assets/javascripts/feature_highlight/feature_highlight_options.js new file mode 100644 index 00000000000..fd48f2e87cc --- /dev/null +++ b/app/assets/javascripts/feature_highlight/feature_highlight_options.js @@ -0,0 +1,12 @@ +import { highlightFeatures } from './feature_highlight'; +import bp from '../breakpoints'; + +const highlightOrder = ['issue-boards']; + +export default function domContentLoaded(order) { + if (bp.getBreakpointSize() === 'lg') { + highlightFeatures(order); + } +} + +document.addEventListener('DOMContentLoaded', domContentLoaded.bind(this, highlightOrder)); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 928394f4fae..66f8cbd7139 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -102,6 +102,7 @@ import './label_manager'; import './labels'; import './labels_select'; import './layout_nav'; +import './feature_highlight/feature_highlight_options'; import LazyLoader from './lazy_loader'; import './line_highlighter'; import './logo'; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index b2b3297e880..c0524bf6aa3 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -51,3 +51,4 @@ @import "framework/snippets"; @import "framework/memory_graph"; @import "framework/responsive-tables"; +@import "framework/feature_highlight"; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index b4a6b214e98..82350c36df0 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -46,6 +46,15 @@ } } +@mixin btn-svg { + svg { + height: 15px; + width: 15px; + position: relative; + top: 2px; + } +} + @mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) { background-color: $light; border-color: $border-light; @@ -123,6 +132,7 @@ .btn { @include btn-default; @include btn-white; + @include btn-svg; color: $gl-text-color; @@ -222,13 +232,6 @@ } } - svg { - height: 15px; - width: 15px; - position: relative; - top: 2px; - } - svg, .fa { &:not(:last-child) { diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 12fc3867c32..b0d6c2d9d22 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -737,6 +737,8 @@ @mixin new-style-dropdown($selector: '') { #{$selector}.dropdown-menu, #{$selector}.dropdown-menu-nav { + margin-bottom: 24px; + li { display: block; padding: 0 1px; @@ -768,7 +770,7 @@ // make sure the text color is not overriden &.text-danger { - // @extend .text-danger; + color: $brand-danger; } &.is-focused, @@ -777,6 +779,11 @@ &:focus { background-color: $dropdown-item-hover-bg; color: $gl-text-color; + + // make sure the text color is not overriden + &.text-danger { + color: $brand-danger; + } } &.is-active { diff --git a/app/assets/stylesheets/framework/feature_highlight.scss b/app/assets/stylesheets/framework/feature_highlight.scss new file mode 100644 index 00000000000..ebae473df50 --- /dev/null +++ b/app/assets/stylesheets/framework/feature_highlight.scss @@ -0,0 +1,94 @@ +.feature-highlight { + position: relative; + margin-left: $gl-padding; + width: 20px; + height: 20px; + cursor: pointer; + + &::before { + content: ''; + display: block; + position: absolute; + top: 6px; + left: 6px; + width: 8px; + height: 8px; + background-color: $blue-500; + border-radius: 50%; + box-shadow: 0 0 0 rgba($blue-500, 0.4); + animation: pulse-highlight 2s infinite; + } + + &:hover::before, + &.disable-animation::before { + animation: none; + } + + &[disabled]::before { + display: none; + } +} + +.is-showing-fly-out { + .feature-highlight { + display: none; + } +} + +.feature-highlight-popover-content { + display: none; + + hr { + margin: $gl-padding * 0.5 0; + } + + .btn-link { + @include btn-svg; + + svg path { + fill: currentColor; + } + } + + .dismiss-feature-highlight { + padding: 0; + } + + svg:first-child { + width: 100%; + background-color: $indigo-50; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + border-bottom: 1px solid darken($gray-normal, 8%); + } +} + +.popover .feature-highlight-popover-content { + display: block; +} + +.feature-highlight-popover { + padding: 0; + + .popover-content { + padding: 0; + } +} + +.feature-highlight-popover-sub-content { + padding: 9px 14px; +} + +@include keyframes(pulse-highlight) { + 0% { + box-shadow: 0 0 0 0 rgba($blue-200, 0.4); + } + + 70% { + box-shadow: 0 0 0 10px transparent; + } + + 100% { + box-shadow: 0 0 0 0 transparent; + } +} diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index a39927eb0df..c5c890b835f 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -267,14 +267,23 @@ // TODO: change global style .ajax-project-dropdown, +body[data-page="projects:edit"] #select2-drop, body[data-page="projects:new"] #select2-drop, +body[data-page="projects:merge_requests:edit"] #select2-drop, body[data-page="projects:blob:new"] #select2-drop, body[data-page="profiles:show"] #select2-drop, body[data-page="projects:blob:edit"] #select2-drop { &.select2-drop { + border: 1px solid $dropdown-border-color; + border-radius: $border-radius-base; color: $gl-text-color; } + &.select2-drop-above { + border-top: none; + margin-top: -4px; + } + .select2-results { .select2-no-results, .select2-searching, diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 0213e7aa9d9..e8ca5cedaee 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -143,8 +143,12 @@ ul.related-merge-requests > li { } } -.issue-form .select2-container { - width: 250px !important; +.issue-form { + @include new-style-dropdown; + + .select2-container { + width: 250px !important; + } } .issues-footer { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 8932cff22a8..5d7c85b16ef 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -23,6 +23,8 @@ .new-note, .note-edit-form { .note-form-actions { + @include new-style-dropdown; + position: relative; margin: $gl-padding 0 0; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 19caefa1961..dd600a27545 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -800,8 +800,10 @@ pre.light-well { } } -.new_protected_branch, +.new-protected-branch, .new-protected-tag { + @include new-style-dropdown; + label { margin-top: 6px; font-weight: $gl-font-weight-normal; @@ -821,19 +823,9 @@ pre.light-well { .protected-branches-list, .protected-tags-list { - margin-bottom: 30px; - - a { - color: $gl-text-color; - - &:hover { - color: $gl-link-color; - } + @include new-style-dropdown; - &.is-active { - font-weight: $gl-font-weight-bold; - } - } + margin-bottom: 30px; .settings-message { margin: 0; diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a6282187d34..ef9cdd058a9 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -305,14 +305,6 @@ module IssuablesHelper cookies[:collapsed_gutter] == 'true' end - def issuable_state_scope(issuable) - if issuable.respond_to?(:merged?) && issuable.merged? - :merged - else - issuable.open? ? :opened : :closed - end - end - def issuable_templates(issuable) @issuable_templates ||= case issuable diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 853ce827061..3d0fdce6a43 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -47,13 +47,6 @@ module IssuesHelper end end - def bulk_update_milestone_options - milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a - milestones.unshift(Milestone::None) - - options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id]) - end - def milestone_options(object) milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a milestones.unshift(object.milestone) if object.milestone.present? && object.milestone.closed? @@ -93,14 +86,6 @@ module IssuesHelper return 'hidden' if issue.closed? == closed end - def merge_requests_sentence(merge_requests) - # Sorting based on the `!123` or `group/project!123` reference will sort - # local merge requests first. - merge_requests.map do |merge_request| - merge_request.to_reference(@project) - end.sort.to_sentence(last_word_connector: ', or ') - end - def confidential_icon(issue) icon('eye-slash') if issue.confidential? end @@ -148,18 +133,6 @@ module IssuesHelper end.to_h end - def due_date_options - options = [ - Issue::AnyDueDate, - Issue::NoDueDate, - Issue::DueThisWeek, - Issue::DueThisMonth, - Issue::Overdue - ] - - options_from_collection_for_select(options, 'name', 'title', params[:due_date]) - end - def link_to_discussions_to_resolve(merge_request, single_discussion = nil) link_text = merge_request.to_reference link_text += " (discussion #{single_discussion.first_note.id})" if single_discussion diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index ca9a350ea79..35d14b6e297 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -305,6 +305,10 @@ module Ci @stage_seeds ||= config_processor.stage_seeds(self) end + def has_kubernetes_active? + project.kubernetes_service&.active? + end + def has_stage_seeds? stage_seeds.any? end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7a817eedec2..724fb4ccef1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -957,13 +957,6 @@ class MergeRequest < ActiveRecord::Base private def write_ref - target_project.repository.with_repo_branch_commit( - source_project.repository, source_branch) do |commit| - if commit - target_project.repository.write_ref(ref_path, commit.sha) - else - raise Rugged::ReferenceError, 'source repository is empty' - end - end + target_project.repository.fetch_source_branch(source_project.repository, source_branch, ref_path) end end diff --git a/app/models/project.rb b/app/models/project.rb index b9247fb535a..051c4c8e2ec 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -68,7 +68,6 @@ class Project < ActiveRecord::Base acts_as_taggable - attr_accessor :new_default_branch attr_accessor :old_path_with_namespace attr_accessor :template_name attr_writer :pipeline_status diff --git a/app/models/repository.rb b/app/models/repository.rb index 5474c8eeb68..05f2f851162 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -20,7 +20,6 @@ class Repository delegate :ref_name_for_sha, to: :raw_repository - CommitError = Class.new(StandardError) CreateTreeError = Class.new(StandardError) # Methods that cache data from the Git repository. @@ -95,19 +94,6 @@ class Repository "#<#{self.class.name}:#{@disk_path}>" end - # - # Git repository can contains some hidden refs like: - # /refs/notes/* - # /refs/git-as-svn/* - # /refs/pulls/* - # This refs by default not visible in project page and not cloned to client side. - # - # This method return true if repository contains some content visible in project page. - # - def has_visible_content? - branch_count > 0 - end - def commit(ref = 'HEAD') return nil unless exists? @@ -184,7 +170,7 @@ class Repository return false unless newrev - GitOperationService.new(user, self).add_branch(branch_name, newrev) + Gitlab::Git::OperationService.new(user, raw_repository).add_branch(branch_name, newrev) after_create_branch find_branch(branch_name) @@ -196,7 +182,7 @@ class Repository return false unless newrev - GitOperationService.new(user, self).add_tag(tag_name, newrev, options) + Gitlab::Git::OperationService.new(user, raw_repository).add_tag(tag_name, newrev, options) find_tag(tag_name) end @@ -205,7 +191,7 @@ class Repository before_remove_branch branch = find_branch(branch_name) - GitOperationService.new(user, self).rm_branch(branch) + Gitlab::Git::OperationService.new(user, raw_repository).rm_branch(branch) after_remove_branch true @@ -215,7 +201,7 @@ class Repository before_remove_tag tag = find_tag(tag_name) - GitOperationService.new(user, self).rm_tag(tag) + Gitlab::Git::OperationService.new(user, raw_repository).rm_tag(tag) after_remove_tag true @@ -784,16 +770,30 @@ class Repository multi_action(**options) end + def with_branch(user, *args) + result = Gitlab::Git::OperationService.new(user, raw_repository).with_branch(*args) do |start_commit| + yield start_commit + end + + newrev, should_run_after_create, should_run_after_create_branch = result + + after_create if should_run_after_create + after_create_branch if should_run_after_create_branch + + newrev + end + # rubocop:disable Metrics/ParameterLists def multi_action( user:, branch_name:, message:, actions:, author_email: nil, author_name: nil, start_branch_name: nil, start_project: project) - GitOperationService.new(user, self).with_branch( + with_branch( + user, branch_name, start_branch_name: start_branch_name, - start_project: start_project) do |start_commit| + start_repository: start_project.repository.raw_repository) do |start_commit| index = Gitlab::Git::Index.new(raw_repository) @@ -846,7 +846,8 @@ class Repository end def merge(user, source, merge_request, options = {}) - GitOperationService.new(user, self).with_branch( + with_branch( + user, merge_request.target_branch) do |start_commit| our_commit = start_commit.sha their_commit = source @@ -866,17 +867,18 @@ class Repository merge_request.update(in_progress_merge_commit_sha: commit_id) commit_id end - rescue Repository::CommitError # when merge_index.conflicts? + rescue Gitlab::Git::CommitError # when merge_index.conflicts? false end def revert( user, commit, branch_name, start_branch_name: nil, start_project: project) - GitOperationService.new(user, self).with_branch( + with_branch( + user, branch_name, start_branch_name: start_branch_name, - start_project: start_project) do |start_commit| + start_repository: start_project.repository.raw_repository) do |start_commit| revert_tree_id = check_revert_content(commit, start_commit.sha) unless revert_tree_id @@ -896,10 +898,11 @@ class Repository def cherry_pick( user, commit, branch_name, start_branch_name: nil, start_project: project) - GitOperationService.new(user, self).with_branch( + with_branch( + user, branch_name, start_branch_name: start_branch_name, - start_project: start_project) do |start_commit| + start_repository: start_project.repository.raw_repository) do |start_commit| cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha) unless cherry_pick_tree_id @@ -921,7 +924,7 @@ class Repository end def resolve_conflicts(user, branch_name, params) - GitOperationService.new(user, self).with_branch(branch_name) do + with_branch(user, branch_name) do committer = user_to_committer(user) create_commit(params.merge(author: committer, committer: committer)) @@ -1011,25 +1014,6 @@ class Repository run_git(args).first.lines.map(&:strip) end - def with_repo_branch_commit(start_repository, start_branch_name) - return yield nil if start_repository.empty_repo? - - if start_repository == self - yield commit(start_branch_name) - else - sha = start_repository.commit(start_branch_name).sha - - if branch_commit = commit(sha) - yield branch_commit - else - with_repo_tmp_commit( - start_repository, start_branch_name, sha) do |tmp_commit| - yield tmp_commit - end - end - end - end - def add_remote(name, url) raw_repository.remote_add(name, url) rescue Rugged::ConfigError @@ -1047,14 +1031,12 @@ class Repository gitlab_shell.fetch_remote(raw_repository, remote, forced: forced, no_tags: no_tags) end - def fetch_ref(source_path, source_ref, target_ref) - args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) - message, status = run_git(args) - - # Make sure ref was created, and raise Rugged::ReferenceError when not - raise Rugged::ReferenceError, message if status != 0 + def fetch_source_branch(source_repository, source_branch, local_ref) + raw_repository.fetch_source_branch(source_repository.raw_repository, source_branch, local_ref) + end - target_ref + def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:) + raw_repository.compare_source_branch(target_branch_name, source_repository.raw_repository, source_branch_name, straight: straight) end def create_ref(ref, ref_path) @@ -1135,12 +1117,6 @@ class Repository private - def run_git(args) - circuit_breaker.perform do - Gitlab::Popen.popen([Gitlab.config.git.bin_path, *args], path_to_repo) - end - end - def blob_data_at(sha, path) blob = blob_at(sha, path) return unless blob @@ -1236,16 +1212,4 @@ class Repository .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset) .map { |c| commit(c) } end - - def with_repo_tmp_commit(start_repository, start_branch_name, sha) - tmp_ref = fetch_ref( - start_repository.path_to_repo, - "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", - "refs/tmp/#{SecureRandom.hex}/head" - ) - - yield commit(sha) - ensure - delete_refs(tmp_ref) if tmp_ref - end end diff --git a/app/models/user.rb b/app/models/user.rb index 68ec93a3ec5..9d48c82e861 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -644,11 +644,6 @@ class User < ActiveRecord::Base @personal_projects_count ||= personal_projects.count end - def projects_limit_percent - return 100 if projects_limit.zero? - (personal_projects.count.to_f / projects_limit) * 100 - end - def recent_push(project_ids = nil) # Get push events not earlier than 2 hours ago events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) @@ -666,10 +661,6 @@ class User < ActiveRecord::Base end end - def projects_sorted_by_activity - authorized_projects.sorted_by_activity - end - def several_namespaces? owned_groups.any? || masters_groups.any? end diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb index dbd0b9ef43a..f96f2931508 100644 --- a/app/services/commits/create_service.rb +++ b/app/services/commits/create_service.rb @@ -17,7 +17,7 @@ module Commits new_commit = create_commit! success(result: new_commit) - rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Repository::CommitError, Gitlab::Git::HooksService::PreReceiveError => ex + rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Gitlab::Git::CommitError, Gitlab::Git::HooksService::PreReceiveError => ex error(ex.message) end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index a5ae4927412..53f16a236d2 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -11,26 +11,8 @@ class CompareService end def execute(target_project, target_branch, straight: false) - # If compare with other project we need to fetch ref first - target_project.repository.with_repo_branch_commit( - start_project.repository, - start_branch_name) do |commit| - break unless commit + raw_compare = target_project.repository.compare_source_branch(target_branch, start_project.repository, start_branch_name, straight: straight) - compare(commit.sha, target_project, target_branch, straight: straight) - end - end - - private - - def compare(source_sha, target_project, target_branch, straight:) - raw_compare = Gitlab::Git::Compare.new( - target_project.repository.raw_repository, - target_branch, - source_sha, - straight: straight - ) - - Compare.new(raw_compare, target_project, straight: straight) + Compare.new(raw_compare, target_project, straight: straight) if raw_compare end end diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb deleted file mode 100644 index 6b7a56e6922..00000000000 --- a/app/services/git_operation_service.rb +++ /dev/null @@ -1,159 +0,0 @@ -class GitOperationService - attr_reader :committer, :repository - - def initialize(committer, new_repository) - committer = Gitlab::Git::Committer.from_user(committer) if committer.is_a?(User) - @committer = committer - - @repository = new_repository - end - - def add_branch(branch_name, newrev) - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - oldrev = Gitlab::Git::BLANK_SHA - - update_ref_in_hooks(ref, newrev, oldrev) - end - - def rm_branch(branch) - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name - oldrev = branch.target - newrev = Gitlab::Git::BLANK_SHA - - update_ref_in_hooks(ref, newrev, oldrev) - end - - def add_tag(tag_name, newrev, options = {}) - ref = Gitlab::Git::TAG_REF_PREFIX + tag_name - oldrev = Gitlab::Git::BLANK_SHA - - with_hooks(ref, newrev, oldrev) do |service| - # We want to pass the OID of the tag object to the hooks. For an - # annotated tag we don't know that OID until after the tag object - # (raw_tag) is created in the repository. That is why we have to - # update the value after creating the tag object. Only the - # "post-receive" hook will receive the correct value in this case. - raw_tag = repository.rugged.tags.create(tag_name, newrev, options) - service.newrev = raw_tag.target_id - end - end - - def rm_tag(tag) - ref = Gitlab::Git::TAG_REF_PREFIX + tag.name - oldrev = tag.target - newrev = Gitlab::Git::BLANK_SHA - - update_ref_in_hooks(ref, newrev, oldrev) do - repository.rugged.tags.delete(tag_name) - end - end - - # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist, - # it would be created from `start_branch_name`. - # If `start_project` is passed, and the branch doesn't exist, - # it would try to find the commits from it instead of current repository. - def with_branch( - branch_name, - start_branch_name: nil, - start_project: repository.project, - &block) - - start_repository = start_project.repository - start_branch_name = nil if start_repository.empty_repo? - - if start_branch_name && !start_repository.branch_exists?(start_branch_name) - raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}" - end - - update_branch_with_hooks(branch_name) do - repository.with_repo_branch_commit( - start_repository, - start_branch_name || branch_name, - &block) - end - end - - private - - def update_branch_with_hooks(branch_name) - update_autocrlf_option - - was_empty = repository.empty? - - # Make commit - newrev = yield - - unless newrev - raise Repository::CommitError.new('Failed to create commit') - end - - branch = repository.find_branch(branch_name) - oldrev = find_oldrev_from_branch(newrev, branch) - - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name - update_ref_in_hooks(ref, newrev, oldrev) - - # If repo was empty expire cache - repository.after_create if was_empty - repository.after_create_branch if - was_empty || Gitlab::Git.blank_ref?(oldrev) - - newrev - end - - def find_oldrev_from_branch(newrev, branch) - return Gitlab::Git::BLANK_SHA unless branch - - oldrev = branch.target - - if oldrev == repository.rugged.merge_base(newrev, branch.target) - oldrev - else - raise Repository::CommitError.new('Branch diverged') - end - end - - def update_ref_in_hooks(ref, newrev, oldrev) - with_hooks(ref, newrev, oldrev) do - update_ref(ref, newrev, oldrev) - end - end - - def with_hooks(ref, newrev, oldrev) - Gitlab::Git::HooksService.new.execute( - committer, - repository, - oldrev, - newrev, - ref) do |service| - - yield(service) - end - end - - # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites. - def update_ref(ref, newrev, oldrev) - # We use 'git update-ref' because libgit2/rugged currently does not - # offer 'compare and swap' ref updates. Without compare-and-swap we can - # (and have!) accidentally reset the ref to an earlier state, clobbering - # commits. See also https://github.com/libgit2/libgit2/issues/1534. - command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z] - _, status = Gitlab::Popen.popen( - command, - repository.path_to_repo) do |stdin| - stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00") - end - - unless status.zero? - raise Repository::CommitError.new( - "Could not update branch #{Gitlab::Git.branch_name(ref)}." \ - " Please refresh and try again.") - end - end - - def update_autocrlf_option - if repository.raw_repository.autocrlf != :input - repository.raw_repository.autocrlf = :input - end - end -end diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index c91602fcff7..30bf1384b22 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -22,10 +22,10 @@ %b Tag list: = build[:tag_list].to_a.join(", ") %br - %b Refs only: + %b Only policy: = @jobs[build[:name].to_sym][:only].to_a.join(", ") %br - %b Refs except: + %b Except policy: = @jobs[build[:name].to_sym][:except].to_a.join(", ") %br %b Environment: diff --git a/app/views/feature_highlight/_issue_boards.svg b/app/views/feature_highlight/_issue_boards.svg new file mode 100644 index 00000000000..1522c9d51c9 --- /dev/null +++ b/app/views/feature_highlight/_issue_boards.svg @@ -0,0 +1,98 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="214" height="102" viewBox="0 0 214 102" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <path id="b" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,27 C48,28.1045695 47.1045695,29 46,29 L2,29 C0.8954305,29 1.3527075e-16,28.1045695 0,27 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> + <filter id="a" width="102.1%" height="106.9%" x="-1%" y="-1.7%" filterUnits="objectBoundingBox"> + <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> + </filter> + <path id="d" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> + <filter id="c" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> + <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> + </filter> + <path id="e" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/> + <path id="h" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> + <filter id="g" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> + <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> + </filter> + <path id="j" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> + <filter id="i" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> + <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> + </filter> + <path id="l" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> + <filter id="k" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> + <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> + </filter> + <path id="n" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> + <filter id="m" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> + <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> + </filter> + <path id="p" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> + <filter id="o" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> + <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> + </filter> + </defs> + <g fill="none" fill-rule="evenodd"> + <path fill="#D6D4DE" d="M14,21 L62,21 C64.7614237,21 67,23.2385763 67,26 L67,112 C67,114.761424 64.7614237,117 62,117 L14,117 C11.2385763,117 9,114.761424 9,112 L9,26 C9,23.2385763 11.2385763,21 14,21 Z"/> + <g transform="translate(11 23)"> + <path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/> + <path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/> + <g transform="translate(5 10)"> + <use fill="black" filter="url(#a)" xlink:href="#b"/> + <use fill="#F9F9F9" xlink:href="#b"/> + </g> + <g transform="translate(5 42)"> + <use fill="black" filter="url(#c)" xlink:href="#d"/> + <use fill="#FEF0E8" xlink:href="#d"/> + <path fill="#FEE1D3" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/> + <path fill="#FDC4A8" d="M9,17 L17,17 C18.1045695,17 19,17.8954305 19,19 C19,20.1045695 18.1045695,21 17,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/> + <path fill="#FC6D26" d="M24,17 L32,17 C33.1045695,17 34,17.8954305 34,19 C34,20.1045695 33.1045695,21 32,21 L24,21 C22.8954305,21 22,20.1045695 22,19 C22,17.8954305 22.8954305,17 24,17 Z"/> + </g> + </g> + <path fill="#D6D4DE" d="M148,26 L196,26 C198.761424,26 201,28.2385763 201,31 L201,117 C201,119.761424 198.761424,122 196,122 L148,122 C145.238576,122 143,119.761424 143,117 L143,31 C143,28.2385763 145.238576,26 148,26 Z"/> + <g transform="translate(145 28)"> + <mask id="f" fill="white"> + <use xlink:href="#e"/> + </mask> + <use fill="#FFFFFF" xlink:href="#e"/> + <path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z" mask="url(#f)"/> + <g transform="translate(5 10)"> + <use fill="black" filter="url(#g)" xlink:href="#h"/> + <use fill="#F9F9F9" xlink:href="#h"/> + </g> + <g transform="translate(5 42)"> + <use fill="black" filter="url(#i)" xlink:href="#j"/> + <use fill="#FEF0E8" xlink:href="#j"/> + <path fill="#FEE1D3" d="M9 8L33 8C34.1045695 8 35 8.8954305 35 10 35 11.1045695 34.1045695 12 33 12L9 12C7.8954305 12 7 11.1045695 7 10 7 8.8954305 7.8954305 8 9 8zM9 17L13 17C14.1045695 17 15 17.8954305 15 19 15 20.1045695 14.1045695 21 13 21L9 21C7.8954305 21 7 20.1045695 7 19 7 17.8954305 7.8954305 17 9 17z"/> + <path fill="#FC6D26" d="M20,17 L24,17 C25.1045695,17 26,17.8954305 26,19 C26,20.1045695 25.1045695,21 24,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/> + <path fill="#FDC4A8" d="M31,17 L35,17 C36.1045695,17 37,17.8954305 37,19 C37,20.1045695 36.1045695,21 35,21 L31,21 C29.8954305,21 29,20.1045695 29,19 C29,17.8954305 29.8954305,17 31,17 Z"/> + </g> + </g> + <path fill="#D6D4DE" d="M81,14 L129,14 C131.761424,14 134,16.2385763 134,19 L134,105 C134,107.761424 131.761424,110 129,110 L81,110 C78.2385763,110 76,107.761424 76,105 L76,19 C76,16.2385763 78.2385763,14 81,14 Z"/> + <g transform="translate(78 16)"> + <path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/> + <g transform="translate(5 10)"> + <use fill="black" filter="url(#k)" xlink:href="#l"/> + <use fill="#EFEDF8" xlink:href="#l"/> + <path fill="#E1DBF1" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/> + <path fill="#6B4FBB" d="M9,17 L13,17 C14.1045695,17 15,17.8954305 15,19 C15,20.1045695 14.1045695,21 13,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/> + <path fill="#C3B8E3" d="M20,17 L28,17 C29.1045695,17 30,17.8954305 30,19 C30,20.1045695 29.1045695,21 28,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/> + </g> + <g transform="translate(5 42)"> + <use fill="black" filter="url(#m)" xlink:href="#n"/> + <use fill="#F9F9F9" xlink:href="#n"/> + </g> + <g transform="translate(5 74)"> + <rect width="34" height="4" x="7" y="7" fill="#E1DBF1" rx="2"/> + <use fill="black" filter="url(#o)" xlink:href="#p"/> + <use fill="#F9F9F9" xlink:href="#p"/> + </g> + <path fill="#6B4FBB" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/> + </g> + </g> +</svg> diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index f5361c7af0c..760c4c97c33 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -99,6 +99,20 @@ = link_to project_boards_path(@project), title: 'Board' do %span Board + .feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } } + .feature-highlight-popover-content + = render 'feature_highlight/issue_boards.svg' + .feature-highlight-popover-sub-content + %span= _('Use') + = link_to 'Issue Boards', project_boards_path(@project) + %span= _('to create customized software development workflows like') + %strong= _('Scrum') + %span= _('or') + %strong= _('Kanban') + %hr + %button.btn-link.dismiss-feature-highlight{ type: 'button' } + %span= _("Got it! Don't show this again") + = custom_icon('thumbs_up') = nav_link(controller: :labels) do = link_to project_labels_path(@project), title: 'Labels' do diff --git a/app/views/shared/icons/_thumbs_up.svg b/app/views/shared/icons/_thumbs_up.svg new file mode 100644 index 00000000000..7267462418e --- /dev/null +++ b/app/views/shared/icons/_thumbs_up.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.104 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.486.5l.138.137a1 1 0 0 1 .28.87L8.33 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></svg> |