diff --git a/awx/main/tasks/jobs.py b/awx/main/tasks/jobs.py index 5c65aea0ac..b5e6af244a 100644 --- a/awx/main/tasks/jobs.py +++ b/awx/main/tasks/jobs.py @@ -1346,7 +1346,7 @@ class RunProjectUpdate(BaseTask): extra_vars['scm_refspec'] = project_update.scm_refspec elif project_update.project.allow_override: # If branch is override-able, do extra fetch for all branches - extra_vars['scm_refspec'] = 'refs/heads/*:refs/remotes/origin/*' + extra_vars['scm_refspec'] = '+refs/heads/*:refs/remotes/origin/*' if project_update.scm_type == 'archive': # for raw archive, prevent error moving files between volumes diff --git a/awx/main/tests/live/tests/conftest.py b/awx/main/tests/live/tests/conftest.py index 6c932d7b86..6aaa57abbf 100644 --- a/awx/main/tests/live/tests/conftest.py +++ b/awx/main/tests/live/tests/conftest.py @@ -139,7 +139,7 @@ def podman_image_generator(): @pytest.fixture def project_factory(post, default_org, admin): - def _rf(scm_url=None, local_path=None): + def _rf(scm_url=None, local_path=None, **extra_kwargs): proj_kwargs = {} if local_path: # manual path @@ -153,6 +153,9 @@ def project_factory(post, default_org, admin): else: raise RuntimeError('Need to provide scm_url or local_path') + if extra_kwargs: + proj_kwargs.update(extra_kwargs) + proj_kwargs['name'] = project_name proj_kwargs['organization'] = default_org.id diff --git a/awx/main/tests/live/tests/projects/test_file_projects.py b/awx/main/tests/live/tests/projects/test_file_projects.py index a2872745b3..616578b136 100644 --- a/awx/main/tests/live/tests/projects/test_file_projects.py +++ b/awx/main/tests/live/tests/projects/test_file_projects.py @@ -1,2 +1,25 @@ +import os +import subprocess + +import pytest + +from awx.main.tests.live.tests.conftest import wait_for_job + + def test_git_file_project(live_tmp_folder, run_job_from_playbook): run_job_from_playbook('test_git_file_project', 'debug.yml', scm_url=f'file://{live_tmp_folder}/debug') + + +@pytest.mark.parametrize('allow_override', [True, False]) +def test_amend_commit(live_tmp_folder, project_factory, allow_override): + proj = project_factory(scm_url=f'file://{live_tmp_folder}/debug', allow_override=allow_override) + assert proj.current_job + wait_for_job(proj.current_job) + assert proj.allow_override is allow_override + + source_dir = os.path.join(live_tmp_folder, 'debug') + subprocess.run('git commit --amend --no-edit', cwd=source_dir, shell=True) + + update = proj.update() + update.signal_start() + wait_for_job(update) diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 4ddbd5e5ce..980eab2959 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -1086,6 +1086,70 @@ class TestProjectUpdateCredentials(TestJobExecution): assert env['FOO'] == 'BAR' +@pytest.mark.django_db +class TestProjectUpdateRefspec(TestJobExecution): + @pytest.fixture + def project_update(self, execution_environment): + org = Organization(pk=1) + proj = Project(pk=1, organization=org, allow_override=True) + project_update = ProjectUpdate(pk=1, project=proj, scm_type='git') + project_update.websocket_emit_status = mock.Mock() + project_update.execution_environment = execution_environment + return project_update + + def test_refspec_with_allow_override_includes_plus_prefix(self, project_update, private_data_dir, mock_me): + """Test that refspec includes + prefix to allow non-fast-forward updates when allow_override is True""" + task = jobs.RunProjectUpdate() + task.instance = project_update + + # Call build_extra_vars_file which sets the refspec + with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}): + task.build_extra_vars_file(project_update, private_data_dir) + + # Read the extra vars file to check the refspec + with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd: + extra_vars = yaml.load(fd, Loader=SafeLoader) + + # Verify the refspec includes the + prefix for force updates + assert 'scm_refspec' in extra_vars + assert extra_vars['scm_refspec'] == '+refs/heads/*:refs/remotes/origin/*' + + def test_custom_refspec_not_overridden(self, project_update, private_data_dir, mock_me): + """Test that custom user-provided refspec is not overridden""" + task = jobs.RunProjectUpdate() + task.instance = project_update + project_update.scm_refspec = 'refs/pull/*/head:refs/remotes/origin/pr/*' + + with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}): + task.build_extra_vars_file(project_update, private_data_dir) + + with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd: + extra_vars = yaml.load(fd, Loader=SafeLoader) + + # Custom refspec should be preserved + assert extra_vars['scm_refspec'] == 'refs/pull/*/head:refs/remotes/origin/pr/*' + + def test_no_refspec_without_allow_override(self, execution_environment, private_data_dir, mock_me): + """Test that no refspec is set when allow_override is False""" + org = Organization(pk=1) + proj = Project(pk=1, organization=org, allow_override=False) + project_update = ProjectUpdate(pk=1, project=proj, scm_type='git') + project_update.websocket_emit_status = mock.Mock() + project_update.execution_environment = execution_environment + + task = jobs.RunProjectUpdate() + task.instance = project_update + + with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}): + task.build_extra_vars_file(project_update, private_data_dir) + + with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd: + extra_vars = yaml.load(fd, Loader=SafeLoader) + + # No refspec should be set + assert 'scm_refspec' not in extra_vars + + class TestInventoryUpdateCredentials(TestJobExecution): @pytest.fixture def inventory_update(self, execution_environment):