diff --git a/Gemfile b/Gemfile index 6a2e3a54f..5b3de9bfa 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,7 @@ gem 'state_machines-activerecord' gem 'redis-rails' gem 'grack', git: 'git://github.com/rosa-abf/grack.git', require: 'git_http' -gem 'grit', git: 'git://github.com/rosa-abf/grit.git', tag: '2.6.16' +gem 'grit', git: 'git://github.com/rosa-abf/grit.git', tag: '2.6.17' gem 'charlock_holmes' gem 'github-linguist', '3.1.5', require: 'linguist' gem 'diff-display' diff --git a/Gemfile.lock b/Gemfile.lock index f62782e8b..ea370ee06 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,8 +41,8 @@ GIT GIT remote: git://github.com/rosa-abf/grit.git - revision: a9548c92188cc307e7af1dd41a733e7000a783a9 - tag: 2.6.16 + revision: b733f0ceefb44b18a9dec8f509ba5493dab59e4e + tag: 2.6.17 specs: grit (2.5.0) diff-lcs (~> 1.1) diff --git a/app/helpers/commit_helper.rb b/app/helpers/commit_helper.rb index f3282580b..2935e2f0d 100644 --- a/app/helpers/commit_helper.rb +++ b/app/helpers/commit_helper.rb @@ -1,23 +1,35 @@ module CommitHelper MAX_FILES_WITHOUT_COLLAPSE = 25 - def render_commit_stats(stats) - res = [""] + def render_commit_stats(stats, diff) + res = ["" - res << "" - res << "" + file_name = if diff[ind].renamed_file + "#{diff[ind].a_path.rtruncate 60}=>#{diff[ind].b_path.rtruncate 60}" + else + filename.rtruncate(120) + end + + res << "
  • " + res << "
    " + res << "" + + res << "
    " + res << "
    " + res << "+#{adds} -#{deletes}" + res << "
    " + res << "
    " + + res << "
    " + res << render_progress_bar(adds, deletes) + res << "
    " + + res << "" ind +=1 end - res << "
  • #{h(filename.rtruncate 120)}" - res << I18n.t("layout.projects.inline_changes_count", count: total).strip + - " (" + - I18n.t("layout.projects.inline_additions_count", count: adds).strip + - ", " + - I18n.t("layout.projects.inline_deletions_count", count: deletes).strip + - ")" - res << "
    " + res << "" wrap_commit_header_list(stats, res) end @@ -73,9 +85,41 @@ module CommitHelper Russian.p(commits_count, *commits_pluralization_arr) end + def is_file_open_in_diff(blob, diff) + return true if blob.binary? && blob.render_as == :image + return true if diff.diff.blank? && diff.a_mode != diff.b_mode + diff.diff.present? && diff.diff.split("\n").count <= DiffHelper::MAX_LINES_WITHOUT_COLLAPSE + end + protected def commits_pluralization_arr pluralize ||= t('layout.commits.pluralize').map {|base, title| title.to_s} end + + def render_progress_bar(adds, deletes) + return if adds+deletes == 0 + res = '' + pluses = ((adds/(adds+deletes).to_f)*100).round + minuses = 100 - pluses + + res << "
    " + res << "
    " + res << "
    " + res << "
    " + res + end + + def diff_file_icon(diff) + icon = if diff.renamed_file + 'fa-caret-square-o-right text-info' + elsif diff.new_file + 'fa-plus-square text-success' + elsif diff.deleted_file + 'fa-minus-square text-danger' + else + 'fa-pencil-square text-warning' + end + "" + end end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index afa4b193a..f9877b22f 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -2,7 +2,7 @@ module DiffHelper MAX_FILES_WITHOUT_COLLAPSE = 25 MAX_LINES_WITHOUT_COLLAPSE = 50 - def render_diff_stats(stats) + def render_diff_stats(stats, diff) res = [""] stats.each_with_index do |stat, ind| res << "" diff --git a/app/views/projects/git/commits/_diff.html.slim b/app/views/projects/git/commits/_diff.html.slim index 2d3189d13..395fb34f7 100644 --- a/app/views/projects/git/commits/_diff.html.slim +++ b/app/views/projects/git/commits/_diff.html.slim @@ -1,9 +1,11 @@ - commit_id = diff.deleted_file ? parent_commit.try(:id) : @commit.id - diff_counter_content = "diff-#{diff_counter}_content" - -- if diff.diff.present? +- if diff.renamed_file + - blob = @project.repo.tree(commit_id) / diff.b_path +- else - blob = @project.repo.tree(commit_id) / diff.a_path - - is_file_open = 'in' if !blob.binary? && diff.diff.split("\n").count <= DiffHelper::MAX_LINES_WITHOUT_COLLAPSE + +- is_file_open = 'in' if is_file_open_in_diff(blob, diff) .file.offset10 a name = "diff-#{diff_counter}" @@ -34,3 +36,5 @@ - if (@project.repo.tree(commit_id) / diff.b_path).nil? = "a_path=#{diff.a_path}; b_path=#{diff.b_path}" == render_diff(diff, diff_counter: diff_counter, comments: @comments) + - if diff.a_mode != diff.b_mode && diff.diff.blank? + == render 'file_change_mode', blob: blob, diff: diff, diff_counter_content: diff_counter_content diff --git a/app/views/projects/git/commits/_show.html.slim b/app/views/projects/git/commits/_show.html.slim index bc9187fba..ecec19721 100644 --- a/app/views/projects/git/commits/_show.html.slim +++ b/app/views/projects/git/commits/_show.html.slim @@ -1,6 +1,7 @@ - begin - - stats = @commit.stats - = render_commit_stats(stats) + - diffs = @commit.show + - stats = Grit::CommitStats.find_all(@project.repo, @commit.sha, max_count: 1, skip: 0, M: true)[0][-1] + = render_commit_stats(stats, diffs) .pull-right => link_to 'raw diff', commit_path(@project, @commit.id, :diff) @@ -8,6 +9,6 @@ =< link_to 'patch', commit_path(@project, @commit.id, :patch) .clearfix - == render partial: 'diff', collection: @commit.diffs, locals: { parent_commit: @commit.parents.try(:first) } + == render partial: 'diff', collection: diffs, locals: { parent_commit: @commit.parents.try(:first) } - rescue Grit::Git::GitTimeout h3.text-danger= t('layout.git.repositories.commit_diff_too_big') diff --git a/app/views/projects/git/commits/diff.html.slim b/app/views/projects/git/commits/diff.html.slim index 7a04db0b4..d11e63f4e 100644 --- a/app/views/projects/git/commits/diff.html.slim +++ b/app/views/projects/git/commits/diff.html.slim @@ -21,8 +21,9 @@ deletions: t("layout.projects.commit_deletions_count", count: total_deletions)) -begin - == render_diff_stats @stats - - diffs = Grit::Commit.diff(@project.repo, @common_ancestor.id, @commit.id) + - diffs = @project.repo.diff @common_ancestor.id, @commit.id + == render_diff_stats @stats, diffs + -# diffs = Grit::Commit.diff(@project.repo, @common_ancestor.id, @commit.id) == render partial: 'projects/git/commits/diff', collection: diffs, locals: { parent_commit: @common_ancestor } - rescue Grit::Git::GitTimeout diff --git a/app/views/projects/pull_requests/_diff_tab.html.slim b/app/views/projects/pull_requests/_diff_tab.html.slim index 095c93fe9..fc9c3df14 100644 --- a/app/views/projects/pull_requests/_diff_tab.html.slim +++ b/app/views/projects/pull_requests/_diff_tab.html.slim @@ -1,6 +1,7 @@ -begin - == render_diff_stats(@stats) - == render partial: 'pull_diff', collection: @pull.diff + - diff = @pull.diff + == render_diff_stats(@stats, diff) + == render partial: 'pull_diff', collection: diff - rescue => ex -if ex.try(:message) == 'Grit::Git::GitTimeout' p= t 'layout.git.repositories.commit_diff_too_big' diff --git a/lib/ext/core/string.rb b/lib/ext/core/string.rb index b22e3dee6..b8837a085 100644 --- a/lib/ext/core/string.rb +++ b/lib/ext/core/string.rb @@ -7,7 +7,9 @@ class String force_encoding(default_encoding) else # should encode options = {invalid: :replace, undef: :replace, replace: ''} - if (detected = detect_encoding) && detected[:encoding] + if encoding.name == 'UTF-8' + encode!(default_encoding, 'UTF-8', options) + elsif (detected = detect_encoding) && detected[:encoding] force_encoding(detected[:encoding]).encode!(default_encoding, detected[:encoding], options) end scrub('') diff --git a/lib/ext/git/grit.rb b/lib/ext/git/grit.rb index 62ca5dc6a..0b1a70980 100644 --- a/lib/ext/git/grit.rb +++ b/lib/ext/git/grit.rb @@ -64,7 +64,6 @@ module Grit def diff(a, b, *paths) diff = self.git.native('diff', {M: true}, "#{a}...#{b}", '--', *paths) - if diff =~ /diff --git a/ diff = diff.sub(/.*?(diff --git a)/m, '\1') else diff --git a/lib/ext/posix_spawn.rb b/lib/ext/posix_spawn.rb new file mode 100644 index 000000000..bd708da07 --- /dev/null +++ b/lib/ext/posix_spawn.rb @@ -0,0 +1,101 @@ +require 'posix/spawn' + +module POSIX + module Spawn + class Child + include POSIX::Spawn + private + # Start a select loop writing any input on the child's stdin and reading + # any output from the child's stdout or stderr. + # + # input - String input to write on stdin. May be nil. + # stdin - The write side IO object for the child's stdin stream. + # stdout - The read side IO object for the child's stdout stream. + # stderr - The read side IO object for the child's stderr stream. + # timeout - An optional Numeric specifying the total number of seconds + # the read/write operations should occur for. + # + # Returns an [out, err] tuple where both elements are strings with all + # data written to the stdout and stderr streams, respectively. + # Raises TimeoutExceeded when all data has not been read / written within + # the duration specified in the timeout argument. + # Raises MaximumOutputExceeded when the total number of bytes output + # exceeds the amount specified by the max argument. + def read_and_write(input, stdin, stdout, stderr, timeout=nil, max=nil) + max = nil if max && max <= 0 + @out, @err = '', '' + offset = 0 + + # force all string and IO encodings to BINARY under 1.9 for now + #if @out.respond_to?(:force_encoding) and stdin.respond_to?(:set_encoding) + # [stdin, stdout, stderr].each do |fd| + # fd.set_encoding('BINARY', 'BINARY') + # end + # @out.force_encoding('BINARY') + # @err.force_encoding('BINARY') + # input = input.dup.force_encoding('BINARY') if input + #end + + timeout = nil if timeout && timeout <= 0.0 + @runtime = 0.0 + start = Time.now + + readers = [stdout, stderr] + writers = + if input + [stdin] + else + stdin.close + [] + end + slice_method = input.respond_to?(:byteslice) ? :byteslice : :slice + t = timeout + + while readers.any? || writers.any? + ready = IO.select(readers, writers, readers + writers, t) + raise TimeoutExceeded if ready.nil? + + # write to stdin stream + ready[1].each do |fd| + begin + boom = nil + size = fd.write_nonblock(input) + input = input.send(slice_method, size..-1) + rescue Errno::EPIPE => boom + rescue Errno::EAGAIN, Errno::EINTR + end + if boom || input.bytesize == 0 + stdin.close + writers.delete(stdin) + end + end + + # read from stdout and stderr streams + ready[0].each do |fd| + buf = (fd == stdout) ? @out : @err + begin + buf << fd.readpartial(BUFSIZE) + rescue Errno::EAGAIN, Errno::EINTR + rescue EOFError + readers.delete(fd) + fd.close + end + end + + # keep tabs on the total amount of time we've spent here + @runtime = Time.now - start + if timeout + t = timeout - @runtime + raise TimeoutExceeded if t < 0.0 + end + + # maybe we've hit our max output + if max && ready[0].any? && (@out.size + @err.size) > max + raise MaximumOutputExceeded + end + end + [@out.mb_chars.default_encoding!, @err.mb_chars.default_encoding!] + end + end + end +end