1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
# frozen_string_literal: true
class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
include SortingHelper
# Authorize
before_action :require_non_empty_project, except: :create
before_action :authorize_read_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
# Support legacy URLs
before_action :redirect_for_legacy_index_sort_or_search, only: [:index]
before_action :limit_diverging_commit_counts!, only: [:diverging_commit_counts]
feature_category :source_code_management
urgency :low, [:index, :diverging_commit_counts, :create, :destroy]
def index
respond_to do |format|
format.html do
@mode = params[:state].presence || 'overview'
@sort = sort_value_for_mode
@overview_max_branches = 5
# Fetch branches for the specified mode
fetch_branches_by_mode
@refs_pipelines = @project.ci_pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
@branch_pipeline_statuses = Ci::CommitStatusesFinder.new(@project, repository, current_user, @branches).execute
# https://gitlab.com/gitlab-org/gitlab/-/issues/22851
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
rescue Gitlab::Git::CommandError
@gitaly_unavailable = true
render status: :service_unavailable
end
format.json do
branches = BranchesFinder.new(@repository, params).execute
branches = Kaminari.paginate_array(branches).page(params[:page])
render json: branches.map(&:name)
end
end
end
def diverging_commit_counts
respond_to do |format|
format.json do
service = ::Branches::DivergingCommitCountsService.new(repository)
branches = BranchesFinder.new(repository, params.permit(names: [])).execute
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: branches.to_h { |branch| [branch.name, service.call(branch)] }
end
end
end
end
# rubocop: disable CodeReuse/ActiveRecord
def create
branch_name = strip_tags(sanitize(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
redirect_to_autodeploy = project.empty_repo? && project.deployment_platform.present?
result = ::Branches::CreateService.new(project, current_user)
.execute(branch_name, ref)
success = (result[:status] == :success)
if params[:issue_iid] && success
target_project = confidential_issue_project || @project
issue = IssuesFinder.new(current_user, project_id: target_project.id).find_by(iid: params[:issue_iid])
SystemNoteService.new_issue_branch(issue, target_project, current_user, branch_name, branch_project: @project) if issue
end
respond_to do |format|
format.html do
if success
if redirect_to_autodeploy
redirect_to url_to_autodeploy_setup(project, branch_name),
notice: view_context.autodeploy_flash_notice(branch_name)
else
redirect_to project_tree_path(@project, branch_name)
end
else
@error = result[:message]
render action: 'new'
end
end
format.json do
if success
render json: { name: branch_name, url: project_tree_url(@project, branch_name) }
else
render json: result[:messsage], status: :unprocessable_entity
end
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
def destroy
result = ::Branches::DeleteService.new(project, current_user).execute(params[:id])
respond_to do |format|
format.html do
flash_type = result.error? ? :alert : :notice
flash[flash_type] = result.message
redirect_back_or_default(default: project_branches_path(@project), options: { status: :see_other })
end
format.js { head result.http_status }
format.json { render json: { message: result.message }, status: result.http_status }
end
end
def destroy_all_merged
::Branches::DeleteMergedService.new(@project, current_user).async_execute
redirect_to project_branches_path(@project),
notice: _('Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.')
end
private
def sort_value_for_mode
custom_sort || default_sort
end
def custom_sort
sort = params[:sort].presence
unless sort.in?(supported_sort_options)
flash.now[:alert] = _("Unsupported sort value.")
sort = nil
end
sort
end
def default_sort
'stale' == @mode ? sort_value_oldest_updated : sort_value_recently_updated
end
def supported_sort_options
[nil, sort_value_name, sort_value_oldest_updated, sort_value_recently_updated]
end
# It can be expensive to calculate the diverging counts for each
# branch. Normally the frontend should be specifying a set of branch
# names, but prior to
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/32496, the
# frontend could omit this set. To prevent excessive I/O, we require
# that a list of names be specified.
def limit_diverging_commit_counts!
limit = Kaminari.config.default_per_page
# If we don't have many branches in the repository, then go ahead.
return if project.repository.branch_count <= limit
return if params[:names].present? && Array(params[:names]).length <= limit
render json: { error: "Specify at least one and at most #{limit} branch names" }, status: :unprocessable_entity
end
def ref
if params[:ref]
ref_escaped = strip_tags(sanitize(params[:ref]))
Addressable::URI.unescape(ref_escaped)
else
@project.default_branch_or_main
end
end
def url_to_autodeploy_setup(project, branch_name)
project_new_blob_path(
project,
branch_name,
file_name: '.gitlab-ci.yml',
commit_message: 'Set up auto deploy',
target_branch: branch_name,
context: 'autodeploy'
)
end
def redirect_for_legacy_index_sort_or_search
# Normalize a legacy URL with redirect
if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence }
redirect_to project_branches_filtered_path(@project, state: 'all'), notice: _('Update your bookmarked URLs as filtered/sorted branches URL has been changed.')
end
end
def fetch_branches_by_mode
return fetch_branches_for_overview if @mode == 'overview'
@branches, @prev_path, @next_path =
Projects::BranchesByModeService.new(@project, params.merge(sort: @sort, mode: @mode)).execute
end
def fetch_branches_for_overview
# Here we get one more branch to indicate if there are more data we're not showing
limit = @overview_max_branches + 1
@active_branches =
BranchesFinder.new(@repository, { per_page: limit, sort: sort_value_recently_updated })
.execute(gitaly_pagination: true).select(&:active?)
@stale_branches =
BranchesFinder.new(@repository, { per_page: limit, sort: sort_value_oldest_updated })
.execute(gitaly_pagination: true).select(&:stale?)
@branches = @active_branches + @stale_branches
end
def confidential_issue_project
return if params[:confidential_issue_project_id].blank?
confidential_issue_project = Project.find(params[:confidential_issue_project_id])
return unless can?(current_user, :update_issue, confidential_issue_project)
confidential_issue_project
end
end
|