mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
git.latest: fail gracefully for misconfigured remote repo (#36391)
* git.latest: fail gracefully for misconfigured remote repo When the remote repo's HEAD refers to a nonexistent ref, this was causing a traceback when we tried to check if the upstream tracking branch needed to be changed after cloning the repo. This commit fixes this traceback by gracefully failing the state when the remote HEAD is not present in the ``git ls-remote`` output, but the desired remote revision doesn't exist. Additionally, a similar graceful failure now happens if the state is run again after we gracefully fail the first time, and we need to set the tracking branch. Trying to set the tracking branch when there is no local branch would fail with an ambiguous error like "fatal: branch 'master' does not exist", so before we even attempt to set the tracking branch, the state is failed with a more descriptive comment. * Add integration test for #36242
This commit is contained in:
parent
ad7045ad3b
commit
bb4d69f58a
2 changed files with 160 additions and 0 deletions
|
@ -1083,6 +1083,20 @@ def latest(name,
|
|||
else:
|
||||
branch_opts = None
|
||||
|
||||
if branch_opts is not None and local_branch is None:
|
||||
return _fail(
|
||||
ret,
|
||||
'Cannot set/unset upstream tracking branch, local '
|
||||
'HEAD refers to nonexistent branch. This may have '
|
||||
'been caused by cloning a remote repository for which '
|
||||
'the default branch was renamed or deleted. If you '
|
||||
'are unable to fix the remote repository, you can '
|
||||
'work around this by setting the \'branch\' argument '
|
||||
'(which will ensure that the named branch is created '
|
||||
'if it does not already exist).',
|
||||
comments
|
||||
)
|
||||
|
||||
if not has_remote_rev:
|
||||
try:
|
||||
fetch_changes = __salt__['git.fetch'](
|
||||
|
@ -1482,6 +1496,21 @@ def latest(name,
|
|||
local_rev, local_branch = \
|
||||
_get_local_rev_and_branch(target, user)
|
||||
|
||||
if local_branch is None \
|
||||
and remote_rev is not None \
|
||||
and 'HEAD' not in all_remote_refs:
|
||||
return _fail(
|
||||
ret,
|
||||
'Remote HEAD refers to a ref that does not exist. '
|
||||
'This can happen when the default branch on the '
|
||||
'remote repository is renamed or deleted. If you '
|
||||
'are unable to fix the remote repository, you can '
|
||||
'work around this by setting the \'branch\' argument '
|
||||
'(which will ensure that the named branch is created '
|
||||
'if it does not already exist).',
|
||||
comments
|
||||
)
|
||||
|
||||
if not _revs_equal(local_rev, remote_rev, remote_rev_type):
|
||||
__salt__['git.reset'](
|
||||
target,
|
||||
|
|
|
@ -8,6 +8,7 @@ from __future__ import absolute_import
|
|||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import string
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
@ -328,6 +329,136 @@ class GitTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn):
|
|||
self.assertSaltTrueReturn(ret)
|
||||
|
||||
|
||||
@skip_if_binaries_missing('git')
|
||||
class LocalRepoGitTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn):
|
||||
'''
|
||||
Tests which do no require connectivity to github.com
|
||||
'''
|
||||
def test_renamed_default_branch(self):
|
||||
'''
|
||||
Test the case where the remote branch has been removed
|
||||
https://github.com/saltstack/salt/issues/36242
|
||||
'''
|
||||
cwd = os.getcwd()
|
||||
repo = tempfile.mkdtemp(dir=integration.TMP)
|
||||
admin = tempfile.mkdtemp(dir=integration.TMP)
|
||||
name = tempfile.mkdtemp(dir=integration.TMP)
|
||||
for dirname in (repo, admin, name):
|
||||
self.addCleanup(shutil.rmtree, dirname, ignore_errors=True)
|
||||
self.addCleanup(os.chdir, cwd)
|
||||
|
||||
with salt.utils.fopen(os.devnull, 'w') as devnull:
|
||||
# Create bare repo
|
||||
subprocess.check_call(['git', 'init', '--bare', repo],
|
||||
stdout=devnull, stderr=devnull)
|
||||
# Clone bare repo
|
||||
subprocess.check_call(['git', 'clone', repo, admin],
|
||||
stdout=devnull, stderr=devnull)
|
||||
|
||||
# Create, add, commit, and push file
|
||||
os.chdir(admin)
|
||||
with salt.utils.fopen('foo', 'w'):
|
||||
pass
|
||||
subprocess.check_call(['git', 'add', '.'],
|
||||
stdout=devnull, stderr=devnull)
|
||||
subprocess.check_call(['git', 'commit', '-m', 'init'],
|
||||
stdout=devnull, stderr=devnull)
|
||||
subprocess.check_call(['git', 'push', 'origin', 'master'],
|
||||
stdout=devnull, stderr=devnull)
|
||||
|
||||
# Change back to the original cwd
|
||||
os.chdir(cwd)
|
||||
|
||||
# Rename remote 'master' branch to 'develop'
|
||||
os.rename(
|
||||
os.path.join(repo, 'refs', 'heads', 'master'),
|
||||
os.path.join(repo, 'refs', 'heads', 'develop')
|
||||
)
|
||||
|
||||
# Run git.latest state. This should successfuly clone and fail with a
|
||||
# specific error in the comment field.
|
||||
ret = self.run_state(
|
||||
'git.latest',
|
||||
name=repo,
|
||||
target=name,
|
||||
rev='develop',
|
||||
)
|
||||
self.assertSaltFalseReturn(ret)
|
||||
self.assertEqual(
|
||||
ret[next(iter(ret))]['comment'],
|
||||
'Remote HEAD refers to a ref that does not exist. '
|
||||
'This can happen when the default branch on the '
|
||||
'remote repository is renamed or deleted. If you '
|
||||
'are unable to fix the remote repository, you can '
|
||||
'work around this by setting the \'branch\' argument '
|
||||
'(which will ensure that the named branch is created '
|
||||
'if it does not already exist).\n\n'
|
||||
'Changes already made: {0} cloned to {1}'
|
||||
.format(repo, name)
|
||||
)
|
||||
self.assertEqual(
|
||||
ret[next(iter(ret))]['changes'],
|
||||
{'new': '{0} => {1}'.format(repo, name)}
|
||||
)
|
||||
|
||||
# Run git.latest state again. This should fail again, with a different
|
||||
# error in the comment field, and should not change anything.
|
||||
ret = self.run_state(
|
||||
'git.latest',
|
||||
name=repo,
|
||||
target=name,
|
||||
rev='develop',
|
||||
)
|
||||
self.assertSaltFalseReturn(ret)
|
||||
self.assertEqual(
|
||||
ret[next(iter(ret))]['comment'],
|
||||
'Cannot set/unset upstream tracking branch, local '
|
||||
'HEAD refers to nonexistent branch. This may have '
|
||||
'been caused by cloning a remote repository for which '
|
||||
'the default branch was renamed or deleted. If you '
|
||||
'are unable to fix the remote repository, you can '
|
||||
'work around this by setting the \'branch\' argument '
|
||||
'(which will ensure that the named branch is created '
|
||||
'if it does not already exist).'
|
||||
)
|
||||
self.assertEqual(ret[next(iter(ret))]['changes'], {})
|
||||
|
||||
# Run git.latest state again with a branch manually set. This should
|
||||
# checkout a new branch and the state should pass.
|
||||
ret = self.run_state(
|
||||
'git.latest',
|
||||
name=repo,
|
||||
target=name,
|
||||
rev='develop',
|
||||
branch='develop',
|
||||
)
|
||||
# State should succeed
|
||||
self.assertSaltTrueReturn(ret)
|
||||
self.assertSaltCommentRegexpMatches(
|
||||
ret,
|
||||
'New branch \'develop\' was checked out, with origin/develop '
|
||||
r'\([0-9a-f]{7}\) as a starting point'
|
||||
)
|
||||
# Only the revision should be in the changes dict.
|
||||
self.assertEqual(
|
||||
list(ret[next(iter(ret))]['changes'].keys()),
|
||||
['revision']
|
||||
)
|
||||
# Since the remote repo was incorrectly set up, the local head should
|
||||
# not exist (therefore the old revision should be None).
|
||||
self.assertEqual(
|
||||
ret[next(iter(ret))]['changes']['revision']['old'],
|
||||
None
|
||||
)
|
||||
# Make sure the new revision is a SHA (40 chars, all hex)
|
||||
self.assertTrue(
|
||||
len(ret[next(iter(ret))]['changes']['revision']['new']) == 40)
|
||||
self.assertTrue(
|
||||
all([x in string.hexdigits for x in
|
||||
ret[next(iter(ret))]['changes']['revision']['new']])
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(GitTest)
|
||||
|
|
Loading…
Add table
Reference in a new issue