From 55f08a7a681bec3d6cf3fceac3984f5e1f7acb30 Mon Sep 17 00:00:00 2001
From: Marco Descher
Date: Wed, 4 Nov 2026 08:44:02 +0100
Subject: [PATCH] Branch-Tag for 7.0
---
app/assets/javascripts/application-legacy.js | 7 +++
app/assets/javascripts/repository_navigation.js | 5 ++
app/assets/stylesheets/application.css | 3 +
app/assets/stylesheets/scm.css | 3 +
app/helpers/repositories_helper.rb | 76 +++++++++++++++++++++++++
app/views/issues/tabs/_changesets.html.erb | 7 +++
app/views/repositories/_changeset.html.erb | 14 ++++-
app/views/settings/_issues.html.erb | 9 +++
config/locales/de.yml | 4 ++
config/locales/en.yml | 4 ++
config/settings.yml | 4 ++
lib/redmine/scm/adapters/git_adapter.rb | 23 ++++++++
12 files changed, 158 insertions(+), 1 deletion(-)
diff --git a/app/assets/javascripts/application-legacy.js b/app/assets/javascripts/application-legacy.js
index e8c84d0b8..8c2293542 100644
--- a/app/assets/javascripts/application-legacy.js
+++ b/app/assets/javascripts/application-legacy.js
@@ -1568,3 +1568,10 @@ $(document).ready(setupFilePreviewNavigation);
$(document).on('focus', '[data-auto-complete=true]', function(event) {
inlineAutoComplete(event.target);
});
+
+// Branch groups are rendered on the revision page and in the issue
+// history tab, which is loaded later. Delegation keeps the expander working.
+$(document).on('click', 'a.scm-branch-group', function(e) {
+ e.preventDefault();
+ $(this).parent().removeClass('scm-branch-hide');
+});
diff --git a/app/assets/javascripts/repository_navigation.js b/app/assets/javascripts/repository_navigation.js
index 27da5eac1..029f5bcf5 100644
--- a/app/assets/javascripts/repository_navigation.js
+++ b/app/assets/javascripts/repository_navigation.js
@@ -36,4 +36,9 @@ $(document).ready(function() {
$('#branch,#tag').removeAttr('disabled');
}
});
+
+ $('a.scm-branch-group').on('click', function(e) {
+ e.preventDefault();
+ $(this).parent().removeClass('scm-branch-hide');
+ });
})
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 0170d41b1..12cad1adf 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -2725,3 +2725,6 @@ th[role=columnheader]:not(.no-sort):hover:after {
#sidebar .api-key-actions .copy-api-key-link svg {
opacity: 1;
}
+
+.scm-branch-group, .scm-branch-hide > .scm-branches { display: none; }
+.scm-branch-hide > .scm-branch-group { display: inline; }
diff --git a/app/assets/stylesheets/scm.css b/app/assets/stylesheets/scm.css
index 90d8f2ce8..e1d9942df 100644
--- a/app/assets/stylesheets/scm.css
+++ b/app/assets/stylesheets/scm.css
@@ -181,3 +181,6 @@ table.annotate td.line-code {
.breadcrumbs>.separator::before, .breadcrumbs>.separator::after {
content: " ";
}
+
+.scm-branch-group, .scm-branch-hide > .scm-branches { display: none; }
+.scm-branch-hide > .scm-branch-group { display: inline; }
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
index 7cfbcdda0..b3188ab00 100644
--- a/app/helpers/repositories_helper.rb
+++ b/app/helpers/repositories_helper.rb
@@ -339,4 +339,80 @@ module RepositoriesHelper
end
max_space
end
+
+ def has_branch_detail?(repository = @repository)
+ repository.present? && repository.scm.respond_to?(:branch_contains)
+ rescue Redmine::Scm::Adapters::CommandFailed
+ false
+ end
+
+ def insert_branches_detail(html)
+ return html unless has_branch_detail?
+
+ html = html.to_s.dup
+ substring = ''
+ index = html.index(substring)
+ return html.html_safe unless index
+
+ html.insert(index + substring.length, branches_html)
+ html.html_safe
+ end
+
+ def branches_html
+ content_tag(:li) do
+ safe_join(
+ [
+ content_tag(:strong, l(:label_branch)),
+ ' ',
+ "#{@repository.identifier}@ ",
+ safe_join(links_to_branches, ', ')
+ ]
+ )
+ end
+ end
+
+ def links_to_branches(repository = @repository, rev = @rev)
+ return [] unless has_branch_detail?(repository)
+
+ branch_groups(repository, rev).map do |name, branches|
+ branches_link(repository, name, branches)
+ end
+ rescue Redmine::Scm::Adapters::CommandFailed
+ []
+ end
+
+ def branches_link(repository, name, branches)
+ return branch_link(repository, branches.first) if branches.length == 1
+
+ link = link_to("[#{name}...]", 'javascript:;', :class => 'scm-branch-group')
+ content_tag(:span, :class => 'scm-branch-hide') do
+ link + content_tag(
+ :span,
+ safe_join(branches.map {|branch| branch_link(repository, branch)}, ', '),
+ :class => 'scm-branches'
+ )
+ end
+ end
+
+ def branch_link(repository, branch)
+ link_to(
+ branch,
+ {
+ :controller => 'repositories',
+ :action => 'show',
+ :id => repository.project,
+ :repository_id => repository.identifier_param,
+ :path => to_path_param(@path),
+ :rev => branch
+ }
+ )
+ end
+
+ def branch_groups(repository = @repository, rev = @rev)
+ return [] if rev.blank?
+
+ repository.scm.branch_contains(rev).group_by do |branch|
+ branch.downcase.gsub(/^\d+/, '#####').split(/[\-\._]/).first
+ end.sort_by {|name, branches| [branches.length, name.to_s]}
+ end
end
diff --git a/app/views/issues/tabs/_changesets.html.erb b/app/views/issues/tabs/_changesets.html.erb
index 2df4d40c2..a154ba22d 100644
--- a/app/views/issues/tabs/_changesets.html.erb
+++ b/app/views/issues/tabs/_changesets.html.erb
@@ -20,6 +20,13 @@
:path => "",
:rev => changeset.identifier) %>)
<% end %>
+ <% if Setting.display_under_associated_revisions? && changeset.scmid.present? &&
+ has_branch_detail?(changeset.repository) %>
+ <% branch_links = links_to_branches(changeset.repository, changeset.identifier) %>
+ <% if branch_links.any? %>
+ (<%= l(:label_branches) %>: <%= safe_join(branch_links, ', ') %>)
+ <% end %>
+ <% end %>
+
+
<% if @changeset.scmid.present? %>-
@@ -20,6 +26,12 @@
}.join(", ").html_safe %>
<% end %>
+ <% if branch_links.any? %>
+ -
+ <%= l(:label_branches) %>
+ <%= safe_join(branch_links, ', ') %>
+
+ <% end %>
<% if @changeset.children.present? %>
-
<%= l(:label_child_revision) %>
diff --git a/app/views/settings/_issues.html.erb b/app/views/settings/_issues.html.erb
index b9acf4fda..3c70b09f8 100644
--- a/app/views/settings/_issues.html.erb
+++ b/app/views/settings/_issues.html.erb
@@ -59,6 +59,15 @@
<%= setting_text_field :gantt_months_limit, :size => 6 %>