diff --git a/Gemfile b/Gemfile index e61d9b2ff7d..d7aa463d830 100644 --- a/Gemfile +++ b/Gemfile @@ -178,6 +178,7 @@ gem "gitlab_emoji", "~> 0.0.1.1" gem "gon", '~> 5.0.0' gem 'nprogress-rails' gem 'request_store' +gem "virtus" group :development do gem "annotate", "~> 2.6.0.beta2" diff --git a/Gemfile.lock b/Gemfile.lock index 1b94f063845..500e80ce4ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -692,5 +692,6 @@ DEPENDENCIES unicorn (~> 4.6.3) unicorn-worker-killer version_sorter + virtus webmock wikicloth (= 0.8.1) diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee new file mode 100644 index 00000000000..dbe00c487dc --- /dev/null +++ b/app/assets/javascripts/diff.js.coffee @@ -0,0 +1,46 @@ +class Diff + UNFOLD_COUNT = 20 + constructor: -> + $(document).on('click', '.js-unfold', (event) => + target = $(event.target) + unfoldBottom = target.hasClass('js-unfold-bottom') + unfold = true + + [old_line, line_number] = @lineNumbers(target.parent()) + offset = line_number - old_line + + if unfoldBottom + line_number += 1 + since = line_number + to = line_number + UNFOLD_COUNT + else + [prev_old_line, prev_new_line] = @lineNumbers(target.parent().prev()) + line_number -= 1 + to = line_number + if line_number - UNFOLD_COUNT > prev_new_line + 1 + since = line_number - UNFOLD_COUNT + else + since = prev_new_line + 1 + unfold = false + + link = target.parents('.diff-file').attr('data-blob-diff-path') + params = + since: since + to: to + bottom: unfoldBottom + offset: offset + unfold: unfold + + $.get(link, params, (response) => + target.parent().replaceWith(response) + ) + ) + + lineNumbers: (line) -> + return ([0, 0]) unless line.children().length + lines = line.children().slice(0, 2) + line_numbers = ($(l).attr('data-linenumber') for l in lines) + (parseInt(line_number) for line_number in line_numbers) + + +@Diff = Diff diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index ff68b520ad6..89bb475a8a6 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -23,13 +23,21 @@ class Dispatcher new Issue() when 'projects:milestones:show' new Milestone() - when 'projects:issues:new', 'projects:merge_requests:new' + when 'projects:issues:new' GitLab.GfmAutoComplete.setup() + when 'projects:merge_requests:new' + GitLab.GfmAutoComplete.setup() + new Diff() + when 'projects:merge_requests:show' + new Diff() + when "projects:merge_requests:diffs" + new Diff() when 'dashboard:show' new Dashboard() new Activities() when 'projects:commit:show' new Commit() + new Diff() when 'groups:show', 'projects:show' new Activities() when 'projects:new', 'projects:edit' diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss index 88b188dbe8d..488d06919b0 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/sections/diff.scss @@ -48,6 +48,9 @@ background-color: #8F8; } } + .unfold { + cursor: pointer; + } .file-mode-changed { padding: 10px; diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index db3d173b98d..7009e3b1bc8 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -25,6 +25,21 @@ class Projects::BlobController < Projects::ApplicationController end end + def diff + @form = UnfoldForm.new(params) + @lines = @blob.data.lines[@form.since - 1..@form.to - 1] + + if @form.bottom? + @match_line = '' + else + lines_length = @lines.length - 1 + line = [@form.since, lines_length].join(',') + @match_line = "@@ -#{line}+#{line} @@" + end + + render layout: false + end + private def blob diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 9a8b3928bf4..f61aa259154 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -232,4 +232,16 @@ module CommitsHelper def diff_file_mode_changed?(diff) diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode end + + def unfold_bottom_class(bottom) + (bottom) ? 'js-unfold-bottom' : '' + end + + def view_file_btn(commit_sha, diff, project) + link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)), + class: 'btn btn-small view-file js-view-file' do + raw('View file @') + content_tag(:span, commit_sha[0..6], + class: 'commit-short-id') + end + end end diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml new file mode 100644 index 00000000000..cfb91d6568a --- /dev/null +++ b/app/views/projects/blob/diff.html.haml @@ -0,0 +1,19 @@ +- if @lines.present? + - if @form.unfold? && @form.since != 1 && !@form.bottom? + %tr.line_holder{ id: @form.since } + = render "projects/commits/diffs/match_line", {line: @match_line, + line_old: @form.since, line_new: @form.since, bottom: false} + + - @lines.each_with_index do |line, index| + - line_new = index + @form.since + - line_old = line_new - @form.offset + %tr.line_holder + %td.old_line.diff-line-num{data: {linenumber: line_old}} + = link_to raw(line_old), "#" + %td.new_line= link_to raw(line_new) , "#" + %td.line_content.noteable_line= line + + - if @form.unfold? && @form.bottom? && @form.to < @blob.loc + %tr.line_holder{ id: @form.to } + = render "projects/commits/diffs/match_line", {line: @match_line, + line_old: @form.to, line_new: @form.to, bottom: true} diff --git a/app/views/projects/commits/_diff_file.html.haml b/app/views/projects/commits/_diff_file.html.haml index 9cbcb84aead..6e6107c8849 100644 --- a/app/views/projects/commits/_diff_file.html.haml +++ b/app/views/projects/commits/_diff_file.html.haml @@ -1,15 +1,15 @@ - file = project.repository.blob_for_diff(@commit, diff) - return unless file -.diff-file{id: "diff-#{i}"} +- blob_diff_path = diff_project_blob_path(project, + tree_join(@commit.id, diff.new_path)) +.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }} .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"} - if diff.deleted_file %span= diff.old_path .diff-btn-group - if @commit.parent_ids.present? - = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), { class: 'btn btn-small view-file' } do - View file @ - %span.commit-short-id= @commit.short_id(6) + = view_file_btn(@commit.parent_id, diff, project) - else %span= diff.new_path - if diff_file_mode_changed?(diff) @@ -26,10 +26,7 @@ Edit   - = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), { class: 'btn btn-small view-file' } do - View file @ - %span.commit-short-id= @commit.short_id(6) - + = view_file_btn(@commit.id, diff, project) .diff-content -# Skipp all non non-supported blobs diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml index f5b0d711416..756481c1b21 100644 --- a/app/views/projects/commits/_text_file.html.haml +++ b/app/views/projects/commits/_text_file.html.haml @@ -3,18 +3,20 @@ %a.supp_diff_link Changes suppressed. Click to show %table.text-file{class: "#{'hide' if too_big}"} + - last_line = 0 - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line| + - last_line = line_new %tr.line_holder{ id: line_code, class: "#{type}" } - if type == "match" - %td.old_line= "..." - %td.new_line= "..." - %td.line_content.matched= line + = render "projects/commits/diffs/match_line", {line: line, + line_old: line_old, line_new: line_new, bottom: false} - else %td.old_line = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - if @comments_allowed = link_to_new_diff_note(line_code) - %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code + %td.new_line{data: {linenumber: line_new}} + = link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line) - if @reply_allowed @@ -22,6 +24,10 @@ - unless comments.empty? = render "projects/notes/diff_notes_with_reply", notes: comments, line: line + - if last_line > 0 + = render "projects/commits/diffs/match_line", {line: "", + line_old: last_line, line_new: last_line, bottom: true} + - if diff.diff.blank? && diff_file_mode_changed?(diff) .file-mode-changed File mode changed diff --git a/app/views/projects/commits/diffs/_match_line.html.haml b/app/views/projects/commits/diffs/_match_line.html.haml new file mode 100644 index 00000000000..4ebe3379733 --- /dev/null +++ b/app/views/projects/commits/diffs/_match_line.html.haml @@ -0,0 +1,7 @@ +%td.old_line.diff-line-num.unfold.js-unfold{data: {linenumber: line_old}, + class: unfold_bottom_class(bottom)} + \... +%td.new_line.diff-line-num.unfold.js-unfold{data: {linenumber: line_new}, + class: unfold_bottom_class(bottom)} + \... +%td.line_content.matched= line diff --git a/config/routes.rb b/config/routes.rb index 261fbb50e38..ce66ea99951 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -193,7 +193,9 @@ Gitlab::Application.routes.draw do end scope module: :projects do - resources :blob, only: [:show, :destroy], constraints: {id: /.+/} + resources :blob, only: [:show, :destroy], constraints: { id: /.+/ } do + get :diff, on: :member + end resources :raw, only: [:show], constraints: {id: /.+/} resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index d4c71ba336e..8b6c296dfe6 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -139,3 +139,11 @@ Feature: Project Merge Requests And I click link "Show inline discussion" of the second file Then I should see a comment like "Line is wrong" in the second file And I should still see a comment like "Line is correct" in the first file + + @javascript + Scenario: I unfold diff + Given project "Shop" have "Bug NS-05" open merge request with diffs inside + And I visit merge request page "Bug NS-05" + And I switch to the diff tab + And I unfold diff + Then I should see additional file lines diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index f0007a039e4..05d3e5067c5 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -242,6 +242,14 @@ class ProjectMergeRequests < Spinach::FeatureSteps end end + step 'I unfold diff' do + first('.js-unfold').click + end + + step 'I should see additional file lines' do + expect(first('.text-file')).to have_content('.bundle') + end + def project @project ||= Project.find_by!(name: "Shop") end diff --git a/lib/gitlab/diff_parser.rb b/lib/gitlab/diff_parser.rb index 14bbb328637..b244295027e 100644 --- a/lib/gitlab/diff_parser.rb +++ b/lib/gitlab/diff_parser.rb @@ -30,7 +30,7 @@ module Gitlab line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0 next if line_old == 1 && line_new == 1 #top of file - yield(full_line, type, nil, nil, nil) + yield(full_line, type, nil, line_new, line_old) next else type = identification_type(line) diff --git a/lib/gt_one_coercion.rb b/lib/gt_one_coercion.rb new file mode 100644 index 00000000000..ef2dc09767c --- /dev/null +++ b/lib/gt_one_coercion.rb @@ -0,0 +1,5 @@ +class GtOneCoercion < Virtus::Attribute + def coerce(value) + [1, value.to_i].max + end +end diff --git a/lib/unfold_form.rb b/lib/unfold_form.rb new file mode 100644 index 00000000000..46b12beeaaf --- /dev/null +++ b/lib/unfold_form.rb @@ -0,0 +1,11 @@ +require_relative 'gt_one_coercion' + +class UnfoldForm + include Virtus.model + + attribute :since, GtOneCoercion + attribute :to, GtOneCoercion + attribute :bottom, Boolean + attribute :unfold, Boolean, default: true + attribute :offset, Integer +end