diff options
author | barthc <mackintosh02@gmail.com> | 2016-08-30 23:17:13 +0300 |
---|---|---|
committer | barthc <mackintosh02@gmail.com> | 2016-08-30 23:17:13 +0300 |
commit | 5c5d13c42d152ba58818a572a51e796cba4a281d (patch) | |
tree | 139f502c4887e7669c323cb335a2e2ee03f1f4db /app | |
parent | 29f818e6165e2e6d4a523270d115a491e261478a (diff) | |
parent | 51087cfa1a6b0bb5a7abf35081ed3b669253eb4f (diff) |
Merge branch 'master' into prevent_authored_awardable_votes
Diffstat (limited to 'app')
43 files changed, 381 insertions, 270 deletions
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 0179b320a3b..5a2a8523d9f 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -117,7 +117,7 @@ } }); } else { - return elements.show(); + return elements.show().removeClass('option-hidden'); } } }; @@ -190,9 +190,9 @@ currentIndex = -1; - NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden'; + NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; - SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")"; + SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)"; CURSOR_SELECT_SCROLL_PADDING = 5 @@ -565,10 +565,6 @@ } else { field.remove(); } - if (this.options.toggleLabel) { - this.updateLabel(selectedObject, el, this); - } - return selectedObject; } else if (el.hasClass(INDETERMINATE_CLASS)) { el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); @@ -578,7 +574,6 @@ if (!field.length && fieldName) { this.addInput(fieldName, value, selectedObject); } - return selectedObject; } else { if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) { this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS); @@ -590,9 +585,6 @@ field.remove(); } el.addClass(ACTIVE_CLASS); - if (this.options.toggleLabel) { - this.updateLabel(selectedObject, el, this); - } if (value != null) { if (!field.length && fieldName) { this.addInput(fieldName, value, selectedObject); @@ -600,8 +592,14 @@ field.val(value).trigger('change'); } } - return selectedObject; } + + // Update label right after input has been added + if (this.options.toggleLabel) { + this.updateLabel(selectedObject, el, this); + } + + return selectedObject; }; GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js index 218f24fe908..e5d4fd44c96 100644 --- a/app/assets/javascripts/logo.js +++ b/app/assets/javascripts/logo.js @@ -1,50 +1,12 @@ (function() { - var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work; - Turbolinks.enableProgressBar(); - defaultClass = 'tanuki-shape'; - - pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek']; - - pieceIndex = 0; - - firstPiece = pieces[0]; - - currentTimer = null; - - delay = 150; - - clearHighlights = function() { - return $("." + defaultClass + ".highlight").attr('class', defaultClass); - }; - start = function() { - clearHighlights(); - pieceIndex = 0; - if (pieces[0] !== firstPiece) { - pieces.reverse(); - } - if (currentTimer) { - clearInterval(currentTimer); - } - return currentTimer = setInterval(work, delay); + $('.tanuki-logo').addClass('animate'); }; stop = function() { - clearInterval(currentTimer); - return clearHighlights(); - }; - - work = function() { - clearHighlights(); - $(pieces[pieceIndex]).attr('class', defaultClass + " highlight"); - if (pieceIndex === pieces.length - 1) { - pieceIndex = 0; - return pieces.reverse(); - } else { - return pieceIndex++; - } + $('.tanuki-logo').removeClass('animate'); }; $(document).on('page:fetch', start); diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6 index 77bffbcb403..b56fd5aa658 100644 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ b/app/assets/javascripts/merge_conflict_resolver.js.es6 @@ -75,10 +75,8 @@ class MergeConflictResolver { window.location.href = data.redirect_to; }) .error(() => { - new Flash('Something went wrong!'); - }) - .always(() => { this.vue.isSubmitting = false; + new Flash('Something went wrong!'); }); } diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index a306b8f3f29..d5cca1b10fb 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -24,6 +24,7 @@ @import "framework/issue_box.scss"; @import "framework/jquery.scss"; @import "framework/lists.scss"; +@import "framework/logo.scss"; @import "framework/markdown_area.scss"; @import "framework/mobile.scss"; @import "framework/modal.scss"; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index be5c64c56d3..edb2ff01f88 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -195,6 +195,12 @@ .separator + .dropdown-header { padding-top: 2px; } + + .unclickable { + cursor: not-allowed; + padding: 5px 8px; + color: $dropdown-header-color; + } } .dropdown-menu-large { diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 43d55661541..37ff7e22ed1 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -19,7 +19,6 @@ input[type='text'].danger { } .form-actions { - margin: -$gl-padding; margin-top: 0; margin-bottom: -$gl-padding; padding: $gl-padding; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 0c607071840..afe4a276ae5 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -2,16 +2,6 @@ * Application Header * */ -@mixin tanuki-logo-colors($path-color) { - fill: $path-color; - transition: all 0.8s; - - &:hover, - &.highlight { - fill: lighten($path-color, 25%); - transition: all 0.1s; - } -} header { transition: padding $sidebar-transition-duration; @@ -25,7 +15,7 @@ header { margin: 8px 0; text-align: center; - #tanuki-logo, img { + .tanuki-logo, img { height: 36px; } } @@ -205,26 +195,6 @@ header { } } -#tanuki-logo { - - #tanuki-left-ear, - #tanuki-right-ear, - #tanuki-nose { - @include tanuki-logo-colors($tanuki-red); - } - - #tanuki-left-eye, - #tanuki-right-eye { - @include tanuki-logo-colors($tanuki-orange); - } - - #tanuki-left-cheek, - #tanuki-right-cheek { - @include tanuki-logo-colors($tanuki-yellow); - } - -} - @media (max-width: $screen-xs-max) { header .container-fluid { font-size: 18px; diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss new file mode 100644 index 00000000000..3ee3fb4cee5 --- /dev/null +++ b/app/assets/stylesheets/framework/logo.scss @@ -0,0 +1,118 @@ +@mixin unique-keyframes { + $animation-name: unique-id(); + @include webkit-prefix(animation-name, $animation-name); + + @-webkit-keyframes #{$animation-name} { + @content; + } + @keyframes #{$animation-name} { + @content; + } +} + +@mixin tanuki-logo-colors($path-color) { + fill: $path-color; + transition: all 0.8s; + + &:hover { + fill: lighten($path-color, 25%); + transition: all 0.1s; + } +} + +@mixin tanuki-second-highlight-animations($tanuki-color) { + @include unique-keyframes { + 10%, 80% { + fill: #{$tanuki-color} + } + 20%, 90% { + fill: lighten($tanuki-color, 25%); + } + } +} + +@mixin tanuki-forth-highlight-animations($tanuki-color) { + @include unique-keyframes { + 30%, 60% { + fill: #{$tanuki-color}; + } + 40%, 70% { + fill: lighten($tanuki-color, 25%); + } + } +} + +.tanuki-logo { + + .tanuki-left-ear, + .tanuki-right-ear, + .tanuki-nose { + @include tanuki-logo-colors($tanuki-red); + } + + .tanuki-left-eye, + .tanuki-right-eye { + @include tanuki-logo-colors($tanuki-orange); + } + + .tanuki-left-cheek, + .tanuki-right-cheek { + @include tanuki-logo-colors($tanuki-yellow); + } + + &.animate { + .tanuki-shape { + @include webkit-prefix(animation-duration, 1.5s); + @include webkit-prefix(animation-iteration-count, infinite); + } + + .tanuki-left-cheek { + @include unique-keyframes { + 0%, 10%, 100% { + fill: lighten($tanuki-yellow, 25%); + } + 90% { + fill: $tanuki-yellow; + } + } + } + + .tanuki-left-eye { + @include tanuki-second-highlight-animations($tanuki-orange); + } + + .tanuki-left-ear { + @include tanuki-second-highlight-animations($tanuki-red); + } + + .tanuki-nose { + @include unique-keyframes { + 20%, 70% { + fill: $tanuki-red; + } + 30%, 80% { + fill: lighten($tanuki-red, 25%); + } + } + } + + .tanuki-right-eye { + @include tanuki-forth-highlight-animations($tanuki-orange); + } + + .tanuki-right-ear { + @include tanuki-forth-highlight-animations($tanuki-red); + } + + .tanuki-right-cheek { + @include unique-keyframes { + 40% { + fill: $tanuki-yellow; + } + 60% { + fill: lighten($tanuki-yellow, 25%); + } + } + } + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index d2d60ed7196..396a37bab6e 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -9,22 +9,6 @@ border-radius: $radius; } -@mixin border-radius-left($radius) { - @include border-radius($radius 0 0 $radius) -} - -@mixin border-radius-right($radius) { - @include border-radius(0 0 $radius $radius) -} - -@mixin linear-gradient($from, $to) { - background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); - background-image: -webkit-linear-gradient($from, $to); - background-image: -moz-linear-gradient($from, $to); - background-image: -ms-linear-gradient($from, $to); - background-image: -o-linear-gradient($from, $to); -} - @mixin transition($transition) { -webkit-transition: $transition; -moz-transition: $transition; @@ -38,14 +22,6 @@ * Mixins with fixed values */ -@mixin shade { - @include box-shadow(0 0 3px #ddd); -} - -@mixin solid-shade { - @include box-shadow(0 0 0 3px #f1f1f1); -} - @mixin str-truncated($max_width: 82%) { display: inline-block; overflow: hidden; @@ -94,23 +70,6 @@ } } -@mixin input-big { - height: 36px; - padding: 5px 10px; - font-size: 16px; - line-height: 24px; - color: #7f8fa4; - background-color: #fff; - border-color: #e7e9ed; -} - -@mixin btn-big { - height: 36px; - padding: 5px 10px; - font-size: 16px; - line-height: 24px; -} - @mixin bulleted-list { > ul { list-style-type: disc; @@ -129,3 +88,8 @@ color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.1); } + +@mixin webkit-prefix($property, $value) { + #{'-webkit-' + $property}: $value; + #{$property}: $value; +} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index cf7cf125504..9efbaf54e90 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -8,10 +8,7 @@ height: 30px; transition-duration: .3s; -webkit-transform: translateZ(0); - background: -webkit-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); - background: -o-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); - background: -moz-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); - background: linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); + background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4)); &.scrolling { visibility: visible; @@ -161,6 +158,7 @@ > .dropdown { margin-right: $gl-padding-top; display: inline-block; + vertical-align: top; &:last-child { margin-right: 0; @@ -210,12 +208,6 @@ } } - .project-filter-form { - input { - background-color: $background-color; - } - } - @media (max-width: $screen-xs-max) { padding-bottom: 0; width: 100%; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 5c336bb1c7e..0cd45fb90bf 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -115,11 +115,8 @@ } &.commits-stat { - margin-top: 3px; display: block; - padding: 3px; - padding-left: 0; - + padding: 0 3px 0 0; &:hover { background: none; } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 743fe89a9f4..0dcf61dd2dd 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -319,6 +319,14 @@ a { color: $layout-link-gray; + text-decoration: none; + + &:hover { + .ci-status-text { + text-decoration: underline; + } + } + } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index eaf2d3270b3..83500a687bb 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -600,18 +600,25 @@ pre.light-well { } } -.project-show-readme .readme-holder { - padding: $gl-padding 0; - border-top: 0; - - .edit-project-readme { - z-index: 2; - position: relative; +.project-show-readme { + .row-content-block { + background-color: inherit; + border: none; } - .wiki h1 { - border-bottom: none; - padding: 0; + .readme-holder { + padding: $gl-padding 0; + border-top: 0; + + .edit-project-readme { + z-index: 2; + position: relative; + } + + .wiki h1 { + border-bottom: none; + padding: 0; + } } } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 587f2d9f3c1..0ee7ceecae5 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -43,6 +43,15 @@ border-color: $blue-normal; } + &.ci-created { + color: $table-text-gray; + border-color: $table-text-gray; + + svg { + fill: $table-text-gray; + } + } + svg { height: 13px; width: 13px; diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 7241949393b..59222637961 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -1,22 +1,25 @@ class Projects::ArtifactsController < Projects::ApplicationController + include ExtractsPath + layout 'project' before_action :authorize_read_build! before_action :authorize_update_build!, only: [:keep] + before_action :extract_ref_name_and_path before_action :validate_artifacts! def download - unless artifacts_file.file_storage? - return redirect_to artifacts_file.url + if artifacts_file.file_storage? + send_file artifacts_file.path, disposition: 'attachment' + else + redirect_to artifacts_file.url end - - send_file artifacts_file.path, disposition: 'attachment' end def browse directory = params[:path] ? "#{params[:path]}/" : '' @entry = build.artifacts_metadata_entry(directory) - return render_404 unless @entry.exists? + render_404 unless @entry.exists? end def file @@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController redirect_to namespace_project_build_path(project.namespace, project, build) end + def latest_succeeded + target_path = artifacts_action_path(@path, project, build) + + if target_path + redirect_to(target_path) + else + render_404 + end + end + private + def extract_ref_name_and_path + return unless params[:ref_name_and_path] + + @ref_name, @path = extract_ref(params[:ref_name_and_path]) + end + def validate_artifacts! - render_404 unless build.artifacts? + render_404 unless build && build.artifacts? end def build - @build ||= project.builds.find_by!(id: params[:build_id]) + @build ||= build_from_id || build_from_ref + end + + def build_from_id + project.builds.find_by(id: params[:build_id]) if params[:build_id] + end + + def build_from_ref + return unless @ref_name + + builds = project.latest_successful_builds_for(@ref_name) + builds.find_by(name: params[:job]) end def artifacts_file diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index bb285a17baf..639deb7c521 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -25,6 +25,11 @@ module CiStatusHelper end end + def ci_status_for_statuseable(subject) + status = subject.try(:status) || 'not found' + status.humanize + end + def ci_icon_for_status(status) icon_name = case status @@ -41,7 +46,7 @@ module CiStatusHelper when 'play' 'icon_play' when 'created' - 'icon_status_pending' + 'icon_status_created' else 'icon_status_cancel' end @@ -66,10 +71,10 @@ module CiStatusHelper Ci::Runner.shared.blank? end - def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '') + def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body') klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" title = "#{type.titleize}: #{ci_label_for_status(status)}" - data = { toggle: 'tooltip', placement: tooltip_placement } + data = { toggle: 'tooltip', placement: tooltip_placement, container: container } if path link_to ci_icon_for_status(status), path, diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 5386ddadc62..a322a90cc4e 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -149,4 +149,20 @@ module GitlabRoutingHelper def resend_invite_group_member_path(group_member, *args) resend_invite_group_group_member_path(group_member.source, group_member) end + + # Artifacts + + def artifacts_action_path(path, project, build) + action, path_params = path.split('/', 2) + args = [project.namespace, project, build, path_params] + + case action + when 'download' + download_namespace_project_build_artifacts_path(*args) + when 'browse' + browse_namespace_project_build_artifacts_path(*args) + when 'file' + file_namespace_project_build_artifacts_path(*args) + end + end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index db6e731c744..a9e175c3f5c 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -98,6 +98,6 @@ module MergeRequestsHelper end def merge_request_button_visibility(merge_request, closed) - return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) + return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? end end diff --git a/app/models/ability.rb b/app/models/ability.rb index a49dd703926..c1df4a865f6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -355,7 +355,7 @@ class Ability rules += named_abilities('project_snippet') end - unless project.wiki_enabled + unless project.has_wiki? rules += named_abilities('wiki') end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 03812cd195f..bd1737c7587 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -65,8 +65,8 @@ module Ci end # ref can't be HEAD or SHA, can only be branch/tag name - scope :latest_successful_for, ->(ref = default_branch) do - where(ref: ref).success.order(id: :desc).limit(1) + def self.latest_successful_for(ref) + where(ref: ref).order(id: :desc).success.first end def self.truncate_sha(sha) diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb index a881fb83b7f..b8dd27a7afe 100644 --- a/app/models/concerns/note_on_diff.rb +++ b/app/models/concerns/note_on_diff.rb @@ -28,4 +28,8 @@ module NoteOnDiff def can_be_award_emoji? false end + + def to_discussion + Discussion.new([self]) + end end diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index df2a9e3e84b..a3ac577cf3e 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -52,11 +52,11 @@ module Taskable end # Return a string that describes the current state of this Taskable's task - # list items, e.g. "20 tasks (12 completed, 8 remaining)" + # list items, e.g. "12 of 20 tasks completed" def task_status return '' if description.blank? sum = tasks.summary - "#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)" + "#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed" end end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index c8320ff87fa..4442cefc7e9 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -107,10 +107,6 @@ class DiffNote < Note self.noteable.find_diff_discussion(self.discussion_id) end - def to_discussion - Discussion.new([self]) - end - private def supported? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1d05e4a85d1..62163e74000 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base end end - validates :source_project, presence: true, unless: [:allow_broken, :importing?] + validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?] validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true validates :merge_user, presence: true, if: :merge_when_build_succeeds? - validate :validate_branches, unless: [:allow_broken, :importing?] - validate :validate_fork + validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] + validate :validate_fork, unless: :closed_without_fork? scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } @@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base def validate_fork return true unless target_project && source_project + return true if target_project == source_project + return true unless forked_source_project_missing? - if target_project == source_project - true - else - # If source and target projects are different - # we should check if source project is actually a fork of target project - if source_project.forked_from?(target_project) - true - else - errors.add :validate_fork, - 'Source project is not a fork of target project' - end - end + errors.add :validate_fork, + 'Source project is not a fork of the target project' + end + + def closed_without_fork? + closed? && forked_source_project_missing? + end + + def forked_source_project_missing? + return false unless for_fork? + return true unless source_project + + !source_project.forked_from?(target_project) end def ensure_merge_request_diff @@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base end def pipeline - @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project + return unless diff_head_sha && source_project + + @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end def all_pipelines diff --git a/app/models/project.rb b/app/models/project.rb index 0e4fb94f8eb..c34064f96ce 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -436,7 +436,7 @@ class Project < ActiveRecord::Base # ref can't be HEAD, can only be branch/tag name or SHA def latest_successful_builds_for(ref = default_branch) - latest_pipeline = pipelines.latest_successful_for(ref).first + latest_pipeline = pipelines.latest_successful_for(ref) if latest_pipeline latest_pipeline.builds.latest.with_artifacts @@ -680,6 +680,10 @@ class Project < ActiveRecord::Base update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) end + def has_wiki? + wiki_enabled? || has_external_wiki? + end + def external_wiki if has_external_wiki.nil? cache_has_external_wiki # Populate @@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base !namespace.share_with_group_lock end - def pipeline(sha, ref) + def pipeline_for(ref, sha = nil) + sha ||= commit(ref).try(:sha) + + return unless sha + pipelines.order(id: :desc).find_by(sha: sha, ref: ref) end - def ensure_pipeline(sha, ref, current_user = nil) - pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user) + def ensure_pipeline(ref, sha, current_user = nil) + pipeline_for(ref, sha) || + pipelines.create(sha: sha, ref: ref, user: current_user) end def enable_ci diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb index adc71b0c2bc..19caa038c44 100644 --- a/app/services/merge_requests/resolve_service.rb +++ b/app/services/merge_requests/resolve_service.rb @@ -1,11 +1,14 @@ module MergeRequests class ResolveService < MergeRequests::BaseService - attr_accessor :conflicts, :rugged, :merge_index + attr_accessor :conflicts, :rugged, :merge_index, :merge_request def execute(merge_request) @conflicts = merge_request.conflicts @rugged = project.repository.rugged @merge_index = conflicts.merge_index + @merge_request = merge_request + + fetch_their_commit! conflicts.files.each do |file| write_resolved_file_to_index(file, params[:sections]) @@ -27,5 +30,21 @@ module MergeRequests merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) merge_index.conflict_remove(our_path) end + + # If their commit (in the target project) doesn't exist in the source project, it + # can't be a parent for the merge commit we're about to create. If that's the case, + # fetch the target branch ref into the source project so the commit exists in both. + # + def fetch_their_commit! + return if rugged.include?(conflicts.their_commit.oid) + + random_string = SecureRandom.hex + + project.repository.fetch_ref( + merge_request.target_project.repository.path_to_repo, + "refs/heads/#{merge_request.target_branch}", + "refs/tmp/#{random_string}/head" + ) + end end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 30c5f24988c..398ec47f0ea 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -11,6 +11,10 @@ module MergeRequests params.except!(:target_project_id) params.except!(:source_branch) + if merge_request.closed_without_fork? + params.except!(:target_branch, :force_remove_source_branch) + end + merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) update(merge_request) diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index 0044d779c31..889086c62b1 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -1,3 +1,6 @@ +- page_title "CI Lint" +- page_description "Validate your GitLab CI configuration file" + %h2 Check your .gitlab-ci.yml %hr diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 6192ccb710b..808e6b95746 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,6 +27,8 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare + = render 'projects/buttons/download', project: @project, ref: branch.name + - if can_remove_branch?(@project, branch.name) = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do = icon("trash-o") diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 58f43ecb5d5..5f5e071eb40 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,4 +1,42 @@ -- unless @project.empty_repo? - - if can? current_user, :download_code, @project - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do - = icon('download') +- if !project.empty_repo? && can?(current_user, :download_code, project) + %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'} + .dropdown.inline + %button.btn{ 'data-toggle' => 'dropdown' } + = icon('download') + %span.caret + %span.sr-only + Select Archive Format + %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li.dropdown-header Source code + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do + %i.fa.fa-download + %span Download zip + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.gz + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.bz2 + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar + + - pipeline = project.pipelines.latest_successful_for(ref) + - if pipeline + - artifacts = pipeline.builds.latest.with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + - latest_pipeline = project.pipeline_for(ref) + %li + .unclickable= ci_status_for_statuseable(latest_pipeline) + %li.dropdown-header Previous Artifacts + - artifacts.each do |job| + %li + = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do + %i.fa.fa-download + %span Download '#{job.name}' diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index ebdf60ae370..36fb0300aeb 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -5,11 +5,11 @@ - if is_playable = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do = render_status_with_link('build', 'play') - = subject.name + %span.ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do = render_status_with_link('build', subject.status) - = subject.name + %span.ci-status-text= subject.name - else = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 584c0fa18ae..31d40f6ad03 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -3,7 +3,7 @@ - if subject.target_url - link_to subject.target_url do = render_status_with_link('commit status', subject.status) - = subject.name + %span.ci-status-text= subject.name - else = render_status_with_link('commit status', subject.status) - = subject.name + %span.ci-status-text= subject.name diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index 6ea9f612d13..a8eeab3e55e 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -5,7 +5,7 @@ - @related_branches.each do |branch| %li - target = @project.repository.find_branch(branch).target - - pipeline = @project.pipeline(target.sha, branch) if target + - pipeline = @project.pipeline_for(branch, target.sha) if target - if pipeline %span.related-branch-ci-status = render_pipeline_status(pipeline) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 098ce19da21..e35291dff7d 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,3 +1,7 @@ +- if @merge_request.closed_without_fork? + .alert.alert-danger + %p The source project of this merge request has been removed. + .clearfix.detail-page-header .issuable-header .issuable-status-box.status-box{ class: status_box_class(@merge_request) } diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml deleted file mode 100644 index 24658319060..00000000000 --- a/app/views/projects/repositories/_download_archive.html.haml +++ /dev/null @@ -1,37 +0,0 @@ -- ref = ref || nil -- btn_class = btn_class || '' -- split_button = split_button || false -- if split_button == true - %span.btn-group{class: btn_class} - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do - %i.fa.fa-download - %span Download zip - %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } - %span.caret - %span.sr-only - Select Archive Format - %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do - %i.fa.fa-download - %span Download zip - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.gz - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.bz2 - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar -- else - %span.btn-group{class: btn_class} - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do - %i.fa.fa-download - %span zip - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do - %i.fa.fa-download - %span tar.gz diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 340e159c874..9adce776c1c 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -72,7 +72,7 @@ = render "projects/buttons/koding" .btn-group.project-repo-btn-group - = render "projects/buttons/download" + = render 'projects/buttons/download', project: @project, ref: @ref = render 'projects/buttons/dropdown' = render 'shared/notifications/button', notification_setting: @notification_setting diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml deleted file mode 100644 index 8a11dbfa9f4..00000000000 --- a/app/views/projects/tags/_download.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -%span.btn-group - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do - %span Source code - %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' } - %span.caret - %span.sr-only - Select Archive Format - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do - %span Download zip - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do - %span Download tar.gz diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 2c11c0e5b21..a156d98bab8 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -11,8 +11,7 @@ = strip_gpg_signature(tag.message) .controls - - if can?(current_user, :download_code, @project) - = render 'projects/tags/download', ref: tag.name, project: @project + = render 'projects/buttons/download', project: @project, ref: tag.name - if can?(current_user, :push_code, @project) = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 395d7af6cbb..4dd7439b2d0 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -12,8 +12,7 @@ = icon('files-o') = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do = icon('history') - - if can? current_user, :download_code, @project - = render 'projects/tags/download', ref: @tag.name, project: @project + = render 'projects/buttons/download', project: @project, ref: @tag.name - if can?(current_user, :admin_project, @project) .pull-right = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index bf5360b4dee..37d341212af 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -10,8 +10,7 @@ %div{ class: container_class } .tree-controls = render 'projects/find_file_link' - - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true + = render 'projects/buttons/download', project: @project, ref: @ref #tree-holder.tree-holder.clearfix .nav-block diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg index b07f1c5603e..9b67422da2c 100644 --- a/app/views/shared/_logo.svg +++ b/app/views/shared/_logo.svg @@ -1,9 +1,9 @@ -<svg width="36" height="36" id="tanuki-logo"> - <path id="tanuki-right-ear" class="tanuki-shape" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/> - <path id="tanuki-left-ear" class="tanuki-shape" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/> - <path id="tanuki-nose" class="tanuki-shape" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/> - <path id="tanuki-right-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/> - <path id="tanuki-left-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/> - <path id="tanuki-right-cheek" class="tanuki-shape" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/> - <path id="tanuki-left-cheek" class="tanuki-shape" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/> +<svg width="36" height="36" class="tanuki-logo"> + <path class="tanuki-shape tanuki-left-ear" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/> + <path class="tanuki-shape tanuki-right-ear" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/> + <path class="tanuki-shape tanuki-nose" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/> + <path class="tanuki-shape tanuki-left-eye" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/> + <path class="tanuki-shape tanuki-right-eye" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/> + <path class="tanuki-shape tanuki-left-cheek" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/> + <path class="tanuki-shape tanuki-right-cheek" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/> </svg> diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg new file mode 100644 index 00000000000..4a08fd65860 --- /dev/null +++ b/app/views/shared/icons/_icon_status_created.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg> diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 22594b46443..3856a4917b4 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -134,7 +134,7 @@ title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } = icon('question-circle') -- if issuable.is_a?(MergeRequest) +- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork? %hr - if @merge_request.new_record? .form-group @@ -175,7 +175,7 @@ = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel' - else .pull-right - - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) + - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project) = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' |