class Repobrowse::GitCommitHTML

Constants

CMT_CMD

rugged doesn't seem to have a way to show diffstats, decorations or combined diffs (–cc/–combined) for merges, so use git-show here

Public Instance Methods

close() click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 316
def close
  @rd = @rd&.close
end
commit_header(env, repo, cmt) click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 12
def commit_header(env, repo, cmt)
  msg = Rugged.prettify_message(cmt.message)
  subject, body = msg.split(/\r?\n\r?\n/, 2)
  subject.strip!
  ht(subject)
  start(subject, repo)
  parents = cmt.parents
  oid = @commit = cmt.oid
  @buf << '   commit ' + oid
  @mhelp = nil
  case parents.size
  when 0
  when 1
    @buf << %Q[ (<a\nhref="#{oid}.patch">patch</a>)]
    pfx = '   parent'
  else
    @mhelp = "\n This is a merge, showing combined diff:\n\n"
    pfx = '  parents'
  end

  @buf << "\n     "
  @buf << %Q(tree <a\nrel=nofollow\nhref="src/#{oid}">#{cmt.tree_oid}</a>\n)
  pad = 0
  idents = [ '   author', 'committer' ].map do |field|
    x = cmt.__send__(field.strip)
    name_email = ht(+"#{x[:name]} <#{x[:email]}>")
    len = name_email.size
    pad = len if len > pad
    [ field, name_email, x[:time].strftime('%Y-%m-%d %k:%M:%S %z') ]
  end
  idents.each do |field, name_email, time|
    @buf << "#{field} #{[name_email].pack("A#{pad}")}\t#{time}\n"
  end
  @repo = repo
  cmd = CMT_CMD.dup
  cmd << @commit
  cmd << '--'
  @rd = repo.driver.popen(cmd, encoding: Encoding::UTF_8)
  abbr = @rd.gets(chomp: true).split(' ')
  parents.each_with_index do |pt, i|
    title = Rugged.prettify_message(pt.summary)
    title.strip!
    @buf << %Q(#{pfx} <a id=P#{i}\nhref="#{pt.oid}">#{abbr[i]}</a> #{ht(title)}\n)
    pfx = '         '
  end
  refnames = @rd.gets("\0", chomp: true)
  @buf << "\n<b>#{subject}</b>#{ht(refnames)}\n\n"
  @buf << ht(body) if body
  @buf << "<a\nid=D>---</a>\n"
  @anchors = {}
  @parents = parents
  @nchg = @nadd = @ndel = 0
  @state = :stat_begin
end
die(msg) click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 147
def die(msg)
  raise RuntimeError, "#{msg} (#@commit)", []
end
diff_done() click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 269
def diff_done
  buf, @mhelp = @mhelp, nil
  @state = :done
  "#{buf}#{show_unchanged}</pre></body></html>"
end
diff_line(line) click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 243
def diff_line(line)
  # dfa and dfb class names match public-inbox search term prefix
  case line
  when /\A\+/
    %Q{<span\nclass="dfa">#{ht(line.chomp!)}</span>\n}
  when /\A\-/
    %Q{<span\nclass="dfb">#{ht(line.chomp!)}</span>\n}
  when %r{\Adiff --git ("?a/.+) ("?b/.+)\n\z} # regular
    git_diff_ab_hdr($1, $2)
  when /\Adiff --(cc|combined) (.+)\n\z/ # merge
    git_diff_cc_hdr($1, $2)
  when /\Aindex ([a-f0-9]+)\.\.([a-f0-9]+)(.*)\n\z/ # regular
    git_diff_ab_index($1, $2, $3)
  when /\A@@ ([\d,\+\-]+) ([\d,\+\-]+) @@(.*)\n\z/ # regular
    git_diff_ab_hunk($1, $2, $3)
  when /\Aindex ([a-f0-9]+,[^\.]+)\.\.([a-f0-9]+)(.*)\n\z/ # --cc
    git_diff_cc_index($1, $2, $3)
  when /\A(@@@+) (\S+.*\S+) @@@+(.*)\n\z/  # --cc
    git_diff_cc_hunk($1, $2, $3)
  when nil
    diff_done
  else
    ht(line)
  end
end
diffstat_end() click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 138
def diffstat_end
  ret = +"\n #@nchg "
  ret << (@nchg == 1 ? 'file changed, ' : 'files changed, ')
  ret << @nadd.to_s
  ret << (@nadd == 1 ? ' insertion(+), ' : ' insertions(+), ')
  ret << @ndel.to_s
  ret << (@ndel == 1 ? " deletion(-)\n\n" : " deletions(-)\n\n")
end
diffstat_line(line) click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 109
def diffstat_line(line)
  line =~ /\A(\S+)\t+(\S+)\t+(.*)/ or die("bad stat line: #{line.inspect}")
  add = -$1
  del = -$2
  fn = -$3
  if fn != '' # normal modification
    anchor = -to_anchor(fn)
    @anchors[anchor] = -fn
    line = %Q(<a\nhref="##{anchor}">#{ht(fn.dup)}</a>)
  else # rename
    from = @rd.gets("\0", chomp: true) or die('EOF rename (from)')
    to = @rd.gets("\0", chomp: true) or die('EOF rename (to)')
    line = diffstat_rename_line(from, to);
  end

  # text changes show numerically, Binary does not
  if add =~ /\A\d+\z/ && del =~ /\A\d+\z/
    @nadd += add.to_i
    @ndel += del.to_i
    add = "+#{add}"
    del = "-#{del}"
  else # just in case...
    ht(add)
    ht(del)
  end
  @nchg += 1
  " #{sprintf('% 6s/%-6s', del, add)}\t#{line}\n"
end
diffstat_rename_line(from, to) click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 88
def diffstat_rename_line(from, to)
  anchor = -to_anchor(to)
  @anchors[anchor] = to
  from_parts = from.split('/')
  to_parts = to.split('/')
  base = []
  while to_parts[0] && to_parts[0] == from_parts[0]
    base << to_parts.shift
    from_parts.shift
  end
  from = from_parts.join('/')
  to = to_parts.join('/')
  to = %Q(<a\nhref="##{anchor}">#{ht(to)}</a>)
  if base[0]
    base = ht(base.join('/'))
    "#{base}/{#{from} =&gt; #{to}}"
  else
    "#{from} =&gt; #{to}"
  end
end
each() { |buf| ... } click to toggle source

called by the Rack server

# File lib/repobrowse/git_commit_html.rb, line 276
def each
  buf = @buf
  @buf = nil
  yield buf
  buf.clear
  while buf = each_i
    yield buf
    buf.clear unless buf.frozen?
  end
end
each_i() click to toggle source
# File lib/repobrowse/git_commit_html.rb, line 287
def each_i
  case @state
  when :stat_begin
    # merges start with an extra '\0' before the diffstat
    # non-merge commits start with an extra '\n', instead
    sep = @mhelp ? "\0" : "\n"
    @rd.gets(sep) == sep or die('diffstat line not empty')
    @state = :stat
  when :stat
    case line = @rd.gets("\0", chomp: true)
    when nil
      if @mhelp
        @mhelp = "\n This is a merge, and the combined diff is empty.\n"
        return diff_done
      end
      die('premature EOF')
    when ''
      @state = :diff
      return diffstat_end
    else
      return diffstat_line(line)
    end
  when :diff
    return diff_line(@rd.gets)
  when :done
    return
  end while true
end
git_diff_ab_hdr(fa, fb) click to toggle source

diff –git a/foo.c b/bar.c

# File lib/repobrowse/git_commit_html.rb, line 152
def git_diff_ab_hdr(fa, fb)
  html_a = ht(fa.dup)
  html_b = ht(fb.dup)
  fa = @repo.driver.git_unquote(fa)
  fb = @repo.driver.git_unquote(fb)
  fa.sub!(%r{\Aa/}, '')
  fb.sub!(%r{\Ab/}, '')
  anchor = -to_anchor(fb)
  @anchors.delete(anchor)
  @fa = fa
  @fb = fb
  # not wasting bandwidth on links here
  # links in hunk headers are far more useful with line offsets
  %Q(<a\nid="#{anchor}">diff</a> --git #{html_a} #{html_b}\n)
end
git_diff_ab_hunk(ca, cb, ctx) click to toggle source

@@ -1,2 +3,4 @@ (regular diff)

# File lib/repobrowse/git_commit_html.rb, line 192
def git_diff_ab_hunk(ca, cb, ctx)
  na = ca.match(/\A-(\d+)/)[1]
  nb = cb.match(/\A\+(\d+)/)[1]

  # we add "rel=nofollow" here to reduce load on search engines, here
  rv = +'@@ '
  rv << (na == '0' ? ca : git_diff_src_link(@parents[0], @fa, na, ca))
  rv << ' '
  rv << (nb == '0' ? cb : git_diff_src_link(@commit, @fb, nb, cb))
  rv << " @@#{ht(ctx)}\n"
end
git_diff_ab_index(da, db, tail) click to toggle source

index abcdef89..01234567

# File lib/repobrowse/git_commit_html.rb, line 179
def git_diff_ab_index(da, db, tail)
  # not wasting bandwidth on links here, yet
  # links in hunk headers are far more useful with line offsets
  "index #{da}..#{db}#{ht(tail)}\n"
end
git_diff_cc_hdr(combined, path) click to toggle source

diff (–cc|–combined)

# File lib/repobrowse/git_commit_html.rb, line 169
def git_diff_cc_hdr(combined, path)
  html_path = ht(path.dup)
  path = @repo.driver.git_unquote(path)
  anchor = to_anchor(path)
  @anchors.delete(anchor)
  @path_cc = path
  %Q(<a\nid="#{anchor}">diff</a> --#{combined} #{html_path}\n)
end
git_diff_cc_hunk(at, offs, ctx) click to toggle source

@@@ -1,2 -3,4 +5,6 @@@ (combined diff)

# File lib/repobrowse/git_commit_html.rb, line 215
def git_diff_cc_hunk(at, offs, ctx)
  offs = offs.split(' ')
  last = offs.pop
  rv = at.dup

  offs.each_with_index do |off, i|
    parent = @parents[i]
    blob = @parent_objs_cc[i]
    lineno = off.match(/\A-(\d+)/)[1]

    if lineno == '0' # new file (does this happen with --cc?)
      rv << " #{off}"
    else
      href = ha(+"src/#{parent}?id=#{blob}#n#{lineno}")
      rv << %Q( <a\nhref=#{href}>#{off}</a>)
    end
  end

  lineno = last.match(/\A\+(\d+)/)[1]
  rv << ' '
  if lineno == '0' # deleted file (does this happen with --cc?)
    rv << last
  else
    rv << git_diff_src_link(@commit, @path_cc, lineno, last)
  end
  rv << " #{at}#{ht(ctx)}\n"
end
git_diff_cc_index(before, last, tail) click to toggle source

index abcdef09,01234567..76543210

# File lib/repobrowse/git_commit_html.rb, line 205
def git_diff_cc_index(before, last, tail)
  ht(tail)
  @parent_objs_cc = before.split(',')

  # not wasting bandwidth on links here, yet
  # links in hunk headers are far more useful with line offsets
  "index #{before}..#{last}#{tail}\n"
end
show_unchanged() click to toggle source

do not break anchor links if the combined diff doesn't show changes:

# File lib/repobrowse/git_commit_html.rb, line 68
  def show_unchanged
    unchanged = @anchors.keys.sort!
    unchanged[0] or return ''
    buf = +<<EOS

 There are uninteresting changes from this merge.
 See the <a\nhref="#P0">parents</a>, or view final state(s) below:

EOS

    unchanged.each do |anchor|
      fn = @repo.driver.git_unquote(@anchors[anchor])
      href = ha(+"src/#@commit:#{fn}")
      buf << "\t<a\nrel=nofollow\nid=#{
        anchor}
        }\nhref=#{href}>#{ht(+fn)}</a>\n"
    end
    buf
  end