2019-10-12 21:52:04 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-08-24 12:49:21 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
2023-03-04 22:38:38 +05:30
|
|
|
RSpec.describe 'Branches', feature_category: :projects do
|
2022-11-25 23:54:43 +05:30
|
|
|
let_it_be(:user) { create(:user) }
|
|
|
|
let_it_be(:project) { create(:project, :public, :repository) }
|
2016-08-24 12:49:21 +05:30
|
|
|
let(:repository) { project.repository }
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
context 'when logged in as reporter' do
|
|
|
|
before do
|
|
|
|
sign_in(user)
|
|
|
|
project.add_reporter(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not show delete button' do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
|
|
|
|
expect(page).not_to have_css '.js-delete-branch-button'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when logged in as developer' do
|
2016-09-29 09:46:39 +05:30
|
|
|
before do
|
2017-09-10 17:25:29 +05:30
|
|
|
sign_in(user)
|
2018-03-17 18:26:18 +05:30
|
|
|
project.add_developer(user)
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
context 'on the projects with 6 active branches and 4 stale branches' do
|
|
|
|
let(:project) { create(:project, :public, :empty_repo) }
|
|
|
|
let(:repository) { project.repository }
|
|
|
|
let(:threshold) { Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD }
|
|
|
|
|
|
|
|
before do
|
|
|
|
# Add 4 stale branches
|
|
|
|
(1..4).reverse_each do |i|
|
2023-01-13 00:05:48 +05:30
|
|
|
travel_to((threshold + i.hours).ago) do
|
|
|
|
create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}")
|
|
|
|
end
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
# Add 6 active branches
|
|
|
|
(1..6).each do |i|
|
2023-01-13 00:05:48 +05:30
|
|
|
travel_to((threshold - i.hours).ago) do
|
|
|
|
create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}")
|
|
|
|
end
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'Overview page of the branches' do
|
|
|
|
it 'shows the first 5 active branches and the first 4 stale branches sorted by last updated' do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
|
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active'))
|
2021-03-11 19:13:27 +05:30
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_asc, state: 'stale'))
|
2018-03-27 19:54:05 +05:30
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
expect(page).to have_button('Copy branch name')
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(page).to have_link(
|
|
|
|
'Show more active branches',
|
|
|
|
href: project_branches_filtered_path(project, state: 'active')
|
|
|
|
)
|
2018-03-27 19:54:05 +05:30
|
|
|
expect(page).not_to have_content('Show more stale branches')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'Active branches page' do
|
|
|
|
it 'shows 6 active branches sorted by last updated' do
|
|
|
|
visit project_branches_filtered_path(project, state: 'active')
|
|
|
|
|
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_desc, state: 'active'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'Stale branches page' do
|
2021-03-11 19:13:27 +05:30
|
|
|
it 'shows 4 stale branches sorted by last updated' do
|
2018-03-27 19:54:05 +05:30
|
|
|
visit project_branches_filtered_path(project, state: 'stale')
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_asc, state: 'stale'))
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'All branches page' do
|
|
|
|
it 'shows 10 branches sorted by last updated' do
|
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
|
|
|
|
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 10, sort_by: :updated_desc))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with branches over more than one page' do
|
|
|
|
before do
|
|
|
|
allow(Kaminari.config).to receive(:default_per_page).and_return(5)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'shows only default_per_page active branches sorted by last updated' do
|
|
|
|
visit project_branches_filtered_path(project, state: 'active')
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page,
|
|
|
|
sort_by: :updated_desc, state: 'active'))
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'shows only default_per_page branches sorted by last updated on All branches' do
|
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page,
|
|
|
|
sort_by: :updated_desc))
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'Find branches' do
|
|
|
|
it 'shows filtered branches', :js do
|
2017-09-10 17:25:29 +05:30
|
|
|
visit project_branches_path(project)
|
2016-08-24 12:49:21 +05:30
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
search_for_branch('fix')
|
2018-03-27 19:54:05 +05:30
|
|
|
|
|
|
|
expect(page).to have_content('fix')
|
|
|
|
expect(find('.all-branches')).to have_selector('li', count: 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'Delete unprotected branch on Overview' do
|
2021-09-04 01:27:46 +05:30
|
|
|
it 'removes branch after confirmation', :js do
|
2018-03-27 19:54:05 +05:30
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
|
|
|
|
|
|
|
expect(all('.all-branches').last).to have_selector('li', count: 20)
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
delete_branch_and_confirm
|
|
|
|
|
|
|
|
expect(page).to have_content('Branch was deleted')
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'All branches page' do
|
|
|
|
it 'shows all the branches sorted by last updated by default' do
|
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc))
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
it 'sorts the branches by name', :js do
|
2018-03-27 19:54:05 +05:30
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
click_button "Updated date" # Open sorting dropdown
|
2021-04-29 21:17:54 +05:30
|
|
|
within '[data-testid="branches-dropdown"]' do
|
2023-03-04 22:38:38 +05:30
|
|
|
first('span', text: 'Name').click
|
2021-04-29 21:17:54 +05:30
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :name))
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
2021-04-29 21:17:54 +05:30
|
|
|
it 'sorts the branches by oldest updated', :js do
|
2018-03-27 19:54:05 +05:30
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
click_button "Updated date" # Open sorting dropdown
|
2021-04-29 21:17:54 +05:30
|
|
|
within '[data-testid="branches-dropdown"]' do
|
2023-03-04 22:38:38 +05:30
|
|
|
first('span', text: 'Oldest updated').click
|
2021-04-29 21:17:54 +05:30
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_asc))
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
it 'avoids a N+1 query in branches index' do
|
2017-09-10 17:25:29 +05:30
|
|
|
control_count = ActiveRecord::QueryRecorder.new { visit project_branches_path(project) }.count
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
%w[one two three four five].each { |ref| repository.add_branch(user, ref, 'master') }
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
expect { visit project_branches_filtered_path(project, state: 'all') }.not_to exceed_query_limit(control_count)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
describe 'Find branches on All branches' do
|
2018-03-17 18:26:18 +05:30
|
|
|
it 'shows filtered branches', :js do
|
2018-03-27 19:54:05 +05:30
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
search_for_branch('fix')
|
2017-09-10 17:25:29 +05:30
|
|
|
|
|
|
|
expect(page).to have_content('fix')
|
|
|
|
expect(find('.all-branches')).to have_selector('li', count: 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
describe 'Delete unprotected branch on All branches' do
|
2018-03-17 18:26:18 +05:30
|
|
|
it 'removes branch after confirmation', :js do
|
2018-03-27 19:54:05 +05:30
|
|
|
visit project_branches_filtered_path(project, state: 'all')
|
2016-09-29 09:46:39 +05:30
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
search_for_branch('fix')
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
expect(all('.all-branches').last).to have_selector('li', count: 1)
|
2016-08-24 12:49:21 +05:30
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
delete_branch_and_confirm
|
|
|
|
|
|
|
|
expect(page).to have_content('Branch was deleted')
|
|
|
|
|
|
|
|
page.refresh
|
|
|
|
|
|
|
|
search_for_branch('fix')
|
2017-09-10 17:25:29 +05:30
|
|
|
|
|
|
|
expect(page).not_to have_content('fix')
|
2022-11-25 23:54:43 +05:30
|
|
|
expect(all('.all-branches', wait: false).last).to have_selector('li', count: 0)
|
2021-09-04 01:27:46 +05:30
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
2018-03-27 19:54:05 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
describe 'Link to branch rules' do
|
|
|
|
it 'does not have possibility to navigate to branch rules', :js do
|
|
|
|
expect(page).not_to have_content(s_("Branches|View branch rules"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
context 'on project with 0 branch' do
|
|
|
|
let(:project) { create(:project, :public, :empty_repo) }
|
|
|
|
let(:repository) { project.repository }
|
|
|
|
|
|
|
|
describe '0 branches on Overview' do
|
|
|
|
it 'shows warning' do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
|
|
|
|
expect(page).not_to have_selector('.all-branches')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
context 'when logged in as maintainer' do
|
2017-09-10 17:25:29 +05:30
|
|
|
before do
|
|
|
|
sign_in(user)
|
2018-11-18 11:00:15 +05:30
|
|
|
project.add_maintainer(user)
|
2017-09-10 17:25:29 +05:30
|
|
|
end
|
|
|
|
|
2018-05-09 12:01:36 +05:30
|
|
|
it 'shows the merge request button' do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
|
|
|
|
page.within first('.all-branches li') do
|
|
|
|
expect(page).to have_content 'Merge request'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the project is archived' do
|
|
|
|
let(:project) { create(:project, :public, :repository, :archived) }
|
|
|
|
|
|
|
|
it 'does not show the merge request button when the project is archived' do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
|
|
|
|
page.within first('.all-branches li') do
|
|
|
|
expect(page).not_to have_content 'Merge request'
|
|
|
|
end
|
|
|
|
end
|
2023-05-27 22:25:52 +05:30
|
|
|
|
|
|
|
describe 'Navigate to branch rules from branches page' do
|
|
|
|
it 'shows repository settings page with Branch rules section expanded' do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
|
|
|
|
view_branch_rules
|
|
|
|
|
|
|
|
expect(page).to have_content(
|
|
|
|
_('Define rules for who can push, merge, and the required approvals for each branch.'))
|
|
|
|
end
|
|
|
|
end
|
2018-05-09 12:01:36 +05:30
|
|
|
end
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
context 'when logged out' do
|
2016-09-29 09:46:39 +05:30
|
|
|
before do
|
2017-09-10 17:25:29 +05:30
|
|
|
visit project_branches_path(project)
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
|
2016-09-29 09:46:39 +05:30
|
|
|
it 'does not show merge request button' do
|
|
|
|
page.within first('.all-branches li') do
|
2018-05-09 12:01:36 +05:30
|
|
|
expect(page).not_to have_content 'Merge request'
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
|
|
|
end
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
context 'with one or more pipeline', :js do
|
2022-11-25 23:54:43 +05:30
|
|
|
let_it_be(:project) { create(:project, :public, :empty_repo) }
|
2022-08-13 15:12:31 +05:30
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
before do
|
|
|
|
sha = create_file(branch_name: "branch")
|
|
|
|
create(:ci_pipeline,
|
|
|
|
project: project,
|
|
|
|
user: user,
|
|
|
|
ref: "branch",
|
|
|
|
sha: sha,
|
|
|
|
status: :success,
|
|
|
|
created_at: 5.months.ago)
|
|
|
|
visit project_branches_path(project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'shows pipeline status when available' do
|
|
|
|
page.within first('.all-branches li') do
|
|
|
|
expect(page).to have_css 'a.ci-status-icon-success'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'displays a placeholder when not available' do
|
|
|
|
page.all('.all-branches li') do |li|
|
|
|
|
expect(li).to have_css 'svg.s24'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with no pipelines', :js do
|
|
|
|
before do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not show placeholder or pipeline status' do
|
|
|
|
page.all('.all-branches') do |branches|
|
|
|
|
expect(branches).not_to have_css 'svg.s24'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
describe 'comparing branches' do
|
|
|
|
before do
|
|
|
|
sign_in(user)
|
|
|
|
project.add_developer(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'compares branches' do
|
|
|
|
it 'compares branches' do
|
|
|
|
visit project_branches_path(project)
|
|
|
|
|
|
|
|
page.within first('.all-branches li') do
|
|
|
|
click_link 'Compare'
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(page).to have_content 'Commits'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'on a read-only instance' do
|
|
|
|
before do
|
|
|
|
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'compares branches'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'on a read-write instance' do
|
|
|
|
it_behaves_like 'compares branches'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-27 19:54:05 +05:30
|
|
|
def sorted_branches(repository, count:, sort_by:, state: nil)
|
|
|
|
branches = repository.branches_sorted_by(sort_by)
|
|
|
|
branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state
|
2018-03-17 18:26:18 +05:30
|
|
|
sorted_branches =
|
2018-03-27 19:54:05 +05:30
|
|
|
branches.first(count).map do |branch|
|
2018-03-17 18:26:18 +05:30
|
|
|
Regexp.escape(branch.name)
|
|
|
|
end
|
|
|
|
|
|
|
|
Regexp.new(sorted_branches.join('.*'))
|
|
|
|
end
|
2018-03-27 19:54:05 +05:30
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
def create_file(branch_name:, message: 'message')
|
2018-03-27 19:54:05 +05:30
|
|
|
repository.create_file(user, generate(:branch), 'content', message: message, branch_name: branch_name)
|
|
|
|
end
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
def search_for_branch(name)
|
|
|
|
branch_search = find('input[data-testid="branch-search"]')
|
|
|
|
branch_search.set(name)
|
|
|
|
branch_search.native.send_keys(:enter)
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_branch_and_confirm
|
|
|
|
find('.js-delete-branch-button', match: :first).click
|
|
|
|
|
|
|
|
within '.modal-footer' do
|
|
|
|
click_button 'Yes, delete branch'
|
|
|
|
end
|
|
|
|
end
|
2023-05-27 22:25:52 +05:30
|
|
|
|
|
|
|
def view_branch_rules
|
|
|
|
page.within('.nav-controls') do
|
|
|
|
click_link s_("Branches|View branch rules")
|
|
|
|
end
|
|
|
|
wait_for_requests
|
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|