You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repositories_helper.rb 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2021 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. module RepositoriesHelper
  19. def format_revision(revision)
  20. if revision.respond_to? :format_identifier
  21. revision.format_identifier
  22. else
  23. revision.to_s
  24. end
  25. end
  26. def truncate_at_line_break(text, length = 255)
  27. if text
  28. text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
  29. end
  30. end
  31. def render_pagination
  32. pagination_links_each @paginator do |text, parameters, options|
  33. if entry = @entries[parameters[:page] - 1]
  34. ent_path = Redmine::CodesetUtil.replace_invalid_utf8(entry.path)
  35. link_to text, {action: 'entry', id: @project, repository_id: @repository.identifier_param, path: to_path_param(ent_path), rev: @rev}
  36. end
  37. end if @paginator
  38. end
  39. def render_properties(properties)
  40. unless properties.nil? || properties.empty?
  41. content = +''
  42. properties.keys.sort.each do |property|
  43. content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe)
  44. end
  45. content_tag('ul', content.html_safe, :class => 'properties')
  46. end
  47. end
  48. def render_changeset_changes
  49. changes = @changeset.filechanges.limit(1000).reorder('path').collect do |change|
  50. case change.action
  51. when 'A'
  52. # Detects moved/copied files
  53. if !change.from_path.blank?
  54. change.action =
  55. @changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
  56. end
  57. change
  58. when 'D'
  59. @changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change
  60. else
  61. change
  62. end
  63. end.compact
  64. tree = {}
  65. changes.each do |change|
  66. p = tree
  67. dirs = change.path.to_s.split('/').select {|d| !d.blank?}
  68. path = ''
  69. dirs.each do |dir|
  70. path += '/' + dir
  71. p[:s] ||= {}
  72. p = p[:s]
  73. p[path] ||= {}
  74. p = p[path]
  75. end
  76. p[:c] = change
  77. end
  78. render_changes_tree(tree[:s])
  79. end
  80. def render_changes_tree(tree)
  81. return '' if tree.nil?
  82. output = +''
  83. output << '<ul>'
  84. tree.keys.sort.each do |file|
  85. style = +'change'
  86. text = File.basename(h(file))
  87. if s = tree[file][:s]
  88. style << ' folder'
  89. path_param = to_path_param(@repository.relative_path(file))
  90. text = link_to(h(text), :controller => 'repositories',
  91. :action => 'show',
  92. :id => @project,
  93. :repository_id => @repository.identifier_param,
  94. :path => path_param,
  95. :rev => @changeset.identifier)
  96. output << "<li class='#{style}'>#{text}"
  97. output << render_changes_tree(s)
  98. output << "</li>"
  99. elsif c = tree[file][:c]
  100. style << " change-#{c.action}"
  101. path_param = to_path_param(@repository.relative_path(c.path))
  102. text = link_to(h(text), :controller => 'repositories',
  103. :action => 'entry',
  104. :id => @project,
  105. :repository_id => @repository.identifier_param,
  106. :path => path_param,
  107. :rev => @changeset.identifier) unless c.action == 'D'
  108. text << " - #{h(c.revision)}" unless c.revision.blank?
  109. text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
  110. :action => 'diff',
  111. :id => @project,
  112. :repository_id => @repository.identifier_param,
  113. :path => path_param,
  114. :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
  115. text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
  116. output << "<li class='#{style}'>#{text}</li>"
  117. end
  118. end
  119. output << '</ul>'
  120. output.html_safe
  121. end
  122. def repository_field_tags(form, repository)
  123. method = repository.class.name.demodulize.underscore + "_field_tags"
  124. if repository.is_a?(Repository) &&
  125. respond_to?(method) && method != 'repository_field_tags'
  126. send(method, form, repository)
  127. end
  128. end
  129. def scm_select_tag(repository)
  130. scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
  131. Redmine::Scm::Base.all.each do |scm|
  132. if Setting.enabled_scm.include?(scm) ||
  133. (repository && repository.class.name.demodulize == scm)
  134. scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
  135. end
  136. end
  137. select_tag('repository_scm',
  138. options_for_select(scm_options, repository.class.name.demodulize),
  139. :disabled => (repository && !repository.new_record?),
  140. :data => {:remote => true, :method => 'get', :url => new_project_repository_path(repository.project)})
  141. end
  142. def with_leading_slash(path)
  143. path.to_s.starts_with?('/') ? path : "/#{path}"
  144. end
  145. def subversion_field_tags(form, repository)
  146. content_tag('p',
  147. form.text_field(:url, :size => 60, :required => true,
  148. :disabled => !repository.safe_attribute?('url')) +
  149. scm_path_info_tag(repository)) +
  150. content_tag('p', form.text_field(:login, :size => 30)) +
  151. content_tag(
  152. 'p',
  153. form.password_field(
  154. :password, :size => 30, :name => 'ignore',
  155. :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x' * 15)),
  156. :onfocus => "this.value=''; this.name='repository[password]';",
  157. :onchange => "this.name='repository[password]';")
  158. )
  159. end
  160. def mercurial_field_tags(form, repository)
  161. content_tag(
  162. 'p',
  163. form.text_field(
  164. :url, :label => l(:field_path_to_repository),
  165. :size => 60, :required => true,
  166. :disabled => !repository.safe_attribute?('url')
  167. ) + scm_path_info_tag(repository)
  168. ) + scm_path_encoding_tag(form, repository)
  169. end
  170. def git_field_tags(form, repository)
  171. content_tag(
  172. 'p',
  173. form.text_field(
  174. :url, :label => l(:field_path_to_repository),
  175. :size => 60, :required => true,
  176. :disabled => !repository.safe_attribute?('url')
  177. ) + scm_path_info_tag(repository)) +
  178. scm_path_encoding_tag(form, repository) +
  179. content_tag('p',
  180. form.check_box(
  181. :report_last_commit,
  182. :label => l(:label_git_report_last_commit)
  183. ))
  184. end
  185. def cvs_field_tags(form, repository)
  186. content_tag(
  187. 'p',
  188. form.text_field(
  189. :root_url,
  190. :label => l(:field_cvsroot),
  191. :size => 60, :required => true,
  192. :disabled => !repository.safe_attribute?('root_url')
  193. ) + scm_path_info_tag(repository)
  194. ) +
  195. content_tag(
  196. 'p',
  197. form.text_field(
  198. :url,
  199. :label => l(:field_cvs_module),
  200. :size => 30, :required => true,
  201. :disabled => !repository.safe_attribute?('url')
  202. )
  203. ) + scm_log_encoding_tag(form, repository) +
  204. scm_path_encoding_tag(form, repository)
  205. end
  206. def bazaar_field_tags(form, repository)
  207. content_tag(
  208. 'p',
  209. form.text_field(
  210. :url, :label => l(:field_path_to_repository),
  211. :size => 60, :required => true,
  212. :disabled => !repository.safe_attribute?('url')
  213. ) + scm_path_info_tag(repository)
  214. ) + scm_log_encoding_tag(form, repository)
  215. end
  216. def filesystem_field_tags(form, repository)
  217. content_tag(
  218. 'p',
  219. form.text_field(
  220. :url, :label => l(:field_root_directory),
  221. :size => 60, :required => true,
  222. :disabled => !repository.safe_attribute?('url')
  223. ) + scm_path_info_tag(repository)
  224. ) + scm_path_encoding_tag(form, repository)
  225. end
  226. def scm_path_info_tag(repository)
  227. text = scm_path_info(repository)
  228. if text.present?
  229. content_tag('em', text, :class => 'info')
  230. else
  231. ''
  232. end
  233. end
  234. def scm_path_info(repository)
  235. scm_name = repository.scm_name.to_s.downcase
  236. info_from_config = Redmine::Configuration["scm_#{scm_name}_path_info"].presence
  237. return info_from_config.html_safe if info_from_config
  238. l("text_#{scm_name}_repository_note", :default => '')
  239. end
  240. def scm_log_encoding_tag(form, repository)
  241. select = form.select(
  242. :log_encoding,
  243. [nil] + Setting::ENCODINGS,
  244. :label => l(:field_commit_logs_encoding),
  245. :required => true
  246. )
  247. content_tag('p', select)
  248. end
  249. def scm_path_encoding_tag(form, repository)
  250. select = form.select(
  251. :path_encoding,
  252. [nil] + Setting::ENCODINGS,
  253. :label => l(:field_scm_path_encoding)
  254. )
  255. content_tag('p', select + content_tag('em', l(:text_scm_path_encoding_note), :class => 'info'))
  256. end
  257. def index_commits(commits, heads)
  258. return nil if commits.nil? or commits.first.parents.nil?
  259. refs_map = {}
  260. heads.each do |head|
  261. refs_map[head.scmid] ||= []
  262. refs_map[head.scmid] << head
  263. end
  264. commits_by_scmid = {}
  265. commits.reverse.each_with_index do |commit, commit_index|
  266. commits_by_scmid[commit.scmid] = {
  267. :parent_scmids => commit.parents.collect {|parent| parent.scmid},
  268. :rdmid => commit_index,
  269. :refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
  270. :scmid => commit.scmid,
  271. :href => block_given? ? yield(commit.scmid) : commit.scmid
  272. }
  273. end
  274. heads.sort_by!(&:to_s)
  275. space = nil
  276. heads.each do |head|
  277. if commits_by_scmid.include? head.scmid
  278. space = index_head((space || -1) + 1, head, commits_by_scmid)
  279. end
  280. end
  281. # when no head matched anything use first commit
  282. space ||= index_head(0, commits.first, commits_by_scmid)
  283. return commits_by_scmid, space
  284. end
  285. def index_head(space, commit, commits_by_scmid)
  286. stack = [[space, commits_by_scmid[commit.scmid]]]
  287. max_space = space
  288. until stack.empty?
  289. space, commit = stack.pop
  290. commit[:space] = space if commit[:space].nil?
  291. space -= 1
  292. commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
  293. parent_commit = commits_by_scmid[parent_scmid]
  294. if parent_commit and parent_commit[:space].nil?
  295. stack.unshift [space += 1, parent_commit]
  296. end
  297. end
  298. max_space = space if max_space < space
  299. end
  300. max_space
  301. end
  302. end