1
0
mirror of https://github.com/meineerde/redmine.git synced 2026-02-16 16:42:01 +00:00
redmine/app/helpers/repositories_helper.rb
Go MAEDA 29bad5d405 Change the way commits are indexed for revision graph (#42762).
The original curve design made it difficult to identify which commits were connected, when several commits existed between the connected ones on the branches.

Now, connections between commits on different branches are more easily recognizable in typical scenarios.
The new curves connect from the left or right side to commit that already have vertical connections, and from the top or bottom to the last or first commit on a branch, respectively.

<pre>
|   |    | |    |
* /-*    * *    *-\
| | |    | |    | |
*-/ *    *-/    * *
|   |    |      | |
</pre>

Patch by Leonid Murin (user:murin).


git-svn-id: https://svn.redmine.org/redmine/trunk@24258 e93f8b46-1217-0410-a6f0-8f06a7374b81
2026-01-05 03:52:14 +00:00

343 lines
12 KiB
Ruby

# frozen_string_literal: true
# Redmine - project management software
# Copyright (C) 2006- Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module RepositoriesHelper
def format_revision(revision)
if revision.respond_to? :format_identifier
revision.format_identifier
else
revision.to_s
end
end
def truncate_at_line_break(text, length = 255)
if text
text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
end
end
def render_pagination
pagination_links_each @paginator do |text, parameters, options|
if entry = @entries[parameters[:page] - 1]
ent_path = Redmine::CodesetUtil.replace_invalid_utf8(entry.path)
link_to text, {action: 'entry', id: @project, repository_id: @repository.identifier_param, path: to_path_param(ent_path), rev: @rev}
end
end if @paginator
end
def render_properties(properties)
unless properties.nil? || properties.empty?
content = +''
properties.keys.sort.each do |property|
content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe)
end
content_tag('ul', content.html_safe, :class => 'properties')
end
end
def render_changeset_changes
changes = @changeset.filechanges.limit(1000).reorder('path').filter_map do |change|
case change.action
when 'A'
# Detects moved/copied files
if change.from_path.present?
change.action =
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end
change
when 'D'
@changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change
else
change
end
end
tree = {}
changes.each do |change|
p = tree
dirs = change.path.to_s.split('/').select {|d| !d.blank?}
path = ''
dirs.each do |dir|
path += '/' + dir
p[:s] ||= {}
p = p[:s]
p[path] ||= {}
p = p[path]
end
p[:c] = change
end
render_changes_tree(tree[:s])
end
def render_changes_tree(tree)
return '' if tree.nil?
output = +''
output << '<ul>'
tree.keys.sort.each do |file|
style = +'change'
text = File.basename(h(file))
if s = tree[file][:s]
style << ' folder'
path_param = to_path_param(@repository.relative_path(file))
text = link_to(sprite_icon("folder-open", h(text)), :controller => 'repositories',
:action => 'show',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier)
output << "<li class='#{style}'>#{text}"
output << render_changes_tree(s)
output << "</li>"
elsif c = tree[file][:c]
style << " change-#{c.action}"
path_param = to_path_param(@repository.relative_path(c.path))
text = link_to(scm_change_icon(c.action, h(text)), :controller => 'repositories',
:action => 'entry',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier) unless c.action == 'D'
text << " - #{h(c.revision)}" unless c.revision.blank?
text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
:action => 'diff',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
output << "<li class='#{style}'>#{text}</li>"
end
end
output << '</ul>'
output.html_safe
end
def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags"
if repository.is_a?(Repository) &&
respond_to?(method) && method != 'repository_field_tags'
send(method, form, repository)
end
end
def scm_select_tag(repository)
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
Redmine::Scm::Base.all.each do |scm|
if Setting.enabled_scm.include?(scm) ||
(repository && repository.class.name.demodulize == scm)
scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
end
end
select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
:data => {:remote => true, :method => 'get', :url => new_project_repository_path(repository.project)})
end
def with_leading_slash(path)
path.to_s.starts_with?('/') ? path : "/#{path}"
end
def subversion_field_tags(form, repository)
content_tag('p',
form.text_field(:url, :size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')) +
scm_path_info_tag(repository)) +
content_tag('p', form.text_field(:login, :size => 30)) +
content_tag(
'p',
form.password_field(
:password, :size => 30, :name => 'ignore',
:value => ((repository.new_record? || repository.password.blank?) ? '' : ('x' * 15)),
:onfocus => "this.value=''; this.name='repository[password]';",
:onchange => "this.name='repository[password]';")
)
end
def mercurial_field_tags(form, repository)
content_tag(
'p',
form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
) + scm_path_info_tag(repository)
) + scm_path_encoding_tag(form, repository)
end
def git_field_tags(form, repository)
content_tag(
'p',
form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
) + scm_path_info_tag(repository)) +
scm_path_encoding_tag(form, repository) +
content_tag('p',
form.check_box(
:report_last_commit,
:label => l(:label_git_report_last_commit)
))
end
def cvs_field_tags(form, repository)
content_tag(
'p',
form.text_field(
:root_url,
:label => l(:field_cvsroot),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('root_url')
) + scm_path_info_tag(repository)
) +
content_tag(
'p',
form.text_field(
:url,
:label => l(:field_cvs_module),
:size => 30, :required => true,
:disabled => !repository.safe_attribute?('url')
)
) + scm_log_encoding_tag(form, repository) +
scm_path_encoding_tag(form, repository)
end
def bazaar_field_tags(form, repository)
content_tag(
'p',
form.text_field(
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
) + scm_path_info_tag(repository)
) + scm_log_encoding_tag(form, repository)
end
def filesystem_field_tags(form, repository)
content_tag(
'p',
form.text_field(
:url, :label => l(:field_root_directory),
:size => 60, :required => true,
:disabled => !repository.safe_attribute?('url')
) + scm_path_info_tag(repository)
) + scm_path_encoding_tag(form, repository)
end
def scm_path_info_tag(repository)
text = scm_path_info(repository)
if text.present?
content_tag('em', text, :class => 'info')
else
''
end
end
def scm_path_info(repository)
scm_name = repository.scm_name.to_s.downcase
info_from_config = Redmine::Configuration["scm_#{scm_name}_path_info"].presence
return info_from_config.html_safe if info_from_config
l("text_#{scm_name}_repository_note", :default => '')
end
def scm_log_encoding_tag(form, repository)
select = form.select(
:log_encoding,
[nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding),
:required => true
)
content_tag('p', select)
end
def scm_path_encoding_tag(form, repository)
select = form.select(
:path_encoding,
[nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
)
content_tag('p', select + content_tag('em', l(:text_scm_path_encoding_note), :class => 'info'))
end
def index_commits(commits, heads)
return nil if commits.nil? or commits.first.parents.nil?
refs_map = {}
heads.each do |head|
refs_map[head.scmid] ||= []
refs_map[head.scmid] << head
end
commits_by_scmid = {}
commits.reverse.each_with_index do |commit, commit_index|
commits_by_scmid[commit.scmid] = {
:parent_scmids => commit.parents.collect {|parent| parent.scmid},
:rdmid => commit_index,
:refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
:scmid => commit.scmid,
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
end
heads.sort_by!(&:to_s)
# Process commits starting from the latest
space = index_head(0, commits.first, commits_by_scmid)
# Process commits from heads
heads.each do |head|
if commits_by_scmid.include?(head.scmid) && commits_by_scmid[head.scmid][:space].nil?
space = index_head(space + 1, head, commits_by_scmid)
end
end
# Process orphan commits
while (commit = commits.find { |commit| commits_by_scmid[commit.scmid][:space].nil? })
space = index_head(space + 1, commit, commits_by_scmid)
end
# Set vertical_children flag for commits that have children in the same column
# for S-style connections between commits
commits_by_scmid.each_value do |commit|
commit[:parent_scmids].each do |scmid|
if (parent = commits_by_scmid[scmid]) && parent[:space] == commit[:space]
parent[:vertical_children] = true
end
end
end
return commits_by_scmid, space
end
def index_head(space, commit, commits_by_scmid)
stack = [[space, commits_by_scmid[commit.scmid]]]
max_space = space
until stack.empty?
space, commit = stack.pop
commit[:space] = space if commit[:space].nil?
space -= 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
parent_commit = commits_by_scmid[parent_scmid]
if parent_commit and parent_commit[:space].nil?
stack.unshift [space += 1, parent_commit]
end
end
max_space = space if max_space < space
end
max_space
end
end