Add update_head option to git.latest

This option will allow the state to simply fetch and not update the
local HEAD if it differs from the desired remote revision.

Additionally, this commit modifies test=True output so that only changes
to the SHA1 are reported when test=True. This prevents executing
"prereq" requisites for minor changes like the remote URL if they would
not result in changes to the actual content in the local clone.
This commit is contained in:
Erik Johnson 2015-11-16 15:29:00 -06:00
parent 3787f7ed00
commit 6a35a39ca5

View file

@ -65,37 +65,6 @@ def _format_comments(comments):
return ret
def _parse_fetch(output):
'''
Go through the output from a git fetch and return a dict
'''
update_re = re.compile(
r'.*(?:([0-9a-f]+)\.\.([0-9a-f]+)|'
r'\[(?:new (tag|branch)|tag update)\])\s+(.+)->'
)
ret = {}
for line in output.splitlines():
match = update_re.match(line)
if match:
old_sha, new_sha, new_ref_type, ref_name = \
match.groups()
ref_name = ref_name.rstrip()
if new_ref_type is not None:
# ref is a new tag/branch
ref_key = 'new tags' \
if new_ref_type == 'tag' \
else 'new branches'
ret.setdefault(ref_key, []).append(ref_name)
elif old_sha is not None:
# ref is a branch update
ret.setdefault('updated_branches', {})[ref_name] = \
{'old': old_sha, 'new': new_sha}
else:
# ref is an updated tag
ret.setdefault('updated tags', []).append(ref_name)
return ret
def _get_local_rev_and_branch(target, user):
'''
Return the local revision for before/after comparisons
@ -135,8 +104,7 @@ def _uptodate(ret, target, comments=None):
# Shouldn't be making any changes if the repo was up to date, but
# report on them so we are alerted to potential problems with our
# logic.
ret['comment'] += '\n\nChanges made: '
ret['comment'] += _format_comments(comments)
ret['comment'] += '\n\nChanges made: ' + comments
return ret
@ -176,6 +144,7 @@ def latest(name,
target=None,
branch=None,
user=None,
update_head=True,
force_checkout=False,
force_clone=False,
force_fetch=False,
@ -208,6 +177,10 @@ def latest(name,
If ``rev`` is not specified, it will be assumed to be ``HEAD``, and
Salt will not manage the tracking branch at all.
.. versionchanged:: 2015.8.0
If not specified, ``rev`` now defaults to the remote repository's
HEAD.
target
Name of the target directory where repository is about to be cloned
@ -228,6 +201,13 @@ def latest(name,
.. versionadded:: 0.17.0
update_head : True
If set to ``False``, then the remote repository will be fetched (if
necessary) to ensure that the commit to which ``rev`` points exists in
the local checkout, but no changes will be made to the local HEAD.
.. versionadded:: 2015.8.2
force : False
.. deprecated:: 2015.8.0
Use ``force_clone`` instead. For earlier Salt versions, ``force``
@ -498,9 +478,6 @@ def latest(name,
else:
if rev == 'HEAD':
if 'HEAD' in all_remote_refs:
# head_ref will only be defined if rev == 'HEAD', be careful
# how this is used below
head_ref = remote + '/HEAD'
remote_rev = all_remote_refs['HEAD']
# Just go with whatever the upstream currently is
desired_upstream = None
@ -536,6 +513,20 @@ def latest(name,
else:
remote_rev = None
# For the comment field of the state return dict, the remote location
# (and short-sha1, if rev is not a sha1) is referenced several times,
# determine it once here and reuse the value below.
if remote_rev_type == 'sha1':
if rev == 'HEAD':
remote_loc = 'remote HEAD (' + remote_rev[:7] + ')'
else:
remote_loc = remote_rev[:7]
else:
remote_loc = '{0} ({1})'.format(
desired_upstream if remote_rev_type == 'branch' else rev,
remote_rev[:7]
)
if remote_rev is None and not bare:
if rev != 'HEAD':
# A specific rev is desired, but that rev doesn't exist on the
@ -606,6 +597,8 @@ def latest(name,
user=user,
redact_auth=False)
revs_match = _revs_equal(local_rev, remote_rev, remote_rev_type)
if remote_rev_type == 'sha1' \
and base_rev is not None \
and base_rev.startswith(remote_rev):
@ -701,6 +694,19 @@ def latest(name,
# and don't have to worry about fast-forwarding.
fast_forward = None
else:
# Remote rev already present
if (not revs_match and not update_head) \
and (branch is None or branch == local_branch):
ret['comment'] = remote_loc.capitalize() \
if rev == 'HEAD' \
else remote_loc
ret['comment'] += (
' is already present and local HEAD ({0}) does not '
'match, but update_head=False. HEAD has not been '
'updated locally.'.format(local_rev[:7])
)
return ret
if base_rev is None:
# If we're here, the remote_rev exists in the local
# checkout but there is still no HEAD locally. A possible
@ -765,35 +771,39 @@ def latest(name,
if remote_rev is not None and desired_fetch_url != fetch_url:
if __opts__['test']:
ret['changes']['remotes/{0}'.format(remote)] = {
'old': salt.utils.url.redact_http_basic_auth(fetch_url),
'new': redacted_fetch_url
}
actions = [
'Remote \'{0}\' would be set to {1}'.format(
'Remote \'{0}\' would be changed from {1} to {2}'
.format(
remote,
salt.utils.url.redact_http_basic_auth(fetch_url),
redacted_fetch_url
)
]
if not has_remote_rev:
actions.append('Remote would be fetched')
if not _revs_equal(local_rev,
remote_rev,
remote_rev_type):
ret['changes']['revision'] = {
'old': local_rev, 'new': remote_rev
}
if fast_forward is False:
ret['changes']['forced update'] = True
actions.append(
'Repository would be {0} to {1}'.format(
merge_action,
_short_sha(remote_rev)
if not revs_match:
if update_head:
ret['changes']['revision'] = {
'old': local_rev, 'new': remote_rev
}
if fast_forward is False:
ret['changes']['forced update'] = True
actions.append(
'Repository would be {0} to {1}'.format(
merge_action,
_short_sha(remote_rev)
)
)
)
if ret['changes']:
return _neutral_test(ret, _format_comments(actions))
else:
if not revs_match and not update_head:
# Repo content would not be modified but the remote
# URL would be modified, so we can't just say that
# the repo is up-to-date, we need to inform the
# user of the actions taken.
ret['comment'] = _format_comments(actions)
return ret
return _uptodate(ret,
target,
_format_comments(actions))
@ -807,114 +817,105 @@ def latest(name,
user=user,
https_user=https_user,
https_pass=https_pass)
ret['changes']['remotes/{0}'.format(remote)] = {
'old': salt.utils.url.redact_http_basic_auth(fetch_url),
'new': redacted_fetch_url
}
comments.append(
'Remote \'{0}\' set to {1}'.format(
'Remote \'{0}\' changed from {1} to {2}'.format(
remote,
salt.utils.url.redact_http_basic_auth(fetch_url),
redacted_fetch_url
)
)
if remote_rev is not None:
if __opts__['test']:
if not _revs_equal(local_rev, remote_rev, remote_rev_type):
ret['changes']['revision'] = {
'old': local_rev, 'new': remote_rev
}
actions = []
if not has_remote_rev:
actions.append(
'Remote \'{0}\' would be fetched'
.format(remote)
'Remote \'{0}\' would be fetched'.format(remote)
)
if branch is not None:
if branch != local_branch:
ret['changes']['local branch'] = {
'old': local_branch, 'new': branch
}
if branch not in all_local_branches:
if (not revs_match) \
and (update_head or (branch is not None
and branch != local_branch)):
ret['changes']['revision'] = {
'old': local_rev, 'new': remote_rev
}
if branch is not None and branch != local_branch:
if branch not in all_local_branches:
actions.append(
'New branch \'{0}\' would be checked '
'out, with {1} as a starting '
'point'.format(branch, remote_loc)
)
if desired_upstream:
actions.append(
'New branch \'{0}\' would be checked '
'out, with {1} ({2}) as a starting '
'point'.format(
branch,
desired_upstream
if desired_upstream
else rev,
_short_sha(remote_rev)
)
'Tracking branch would be set to {0}'
.format(desired_upstream)
)
if desired_upstream:
actions.append(
'Tracking branch would be set to {0}'
.format(desired_upstream)
)
else:
if fast_forward is False:
ret['changes']['hard reset'] = True
actions.append(
'Branch \'{0}\' would be checked out '
'and {1} to {2}'.format(
branch,
merge_action,
_short_sha(remote_rev)
)
else:
actions.append(
'Branch \'{0}\' would be checked out '
'and {1} to {2}'.format(
branch,
merge_action,
_short_sha(remote_rev)
)
)
else:
if not _revs_equal(local_rev,
remote_rev,
remote_rev_type):
if fast_forward is True:
actions.append(
'Repository would be fast-forwarded from '
'{0} to {1}'.format(
_short_sha(local_rev),
_short_sha(remote_rev)
if not revs_match:
if update_head:
if fast_forward is True:
actions.append(
'Repository would be fast-forwarded from '
'{0} to {1}'.format(
_short_sha(local_rev),
_short_sha(remote_rev)
)
)
else:
actions.append(
'Repository would be {0} from {1} to {2}'
.format(
'hard-reset'
if force_reset and has_remote_rev
else 'updated',
_short_sha(local_rev),
_short_sha(remote_rev)
)
)
)
else:
actions.append(
'Repository would be {0} from {1} to {2}'
.format(
'hard-reset'
if force_reset and has_remote_rev
else 'updated',
_short_sha(local_rev),
_short_sha(remote_rev)
'Local HEAD ({0}) does not match {1} but '
'update_head=False, HEAD would not be '
'updated locally'.format(
local_rev[:7],
remote_loc
)
)
# Check if upstream needs changing
upstream_changed = False
if not upstream and desired_upstream:
upstream_changed = True
actions.append(
'Tracking branch would be set to {0}'.format(
desired_upstream
)
)
elif upstream and desired_upstream is False:
upstream_changed = True
actions.append(
'Tracking branch would be unset'
)
elif desired_upstream and upstream != desired_upstream:
upstream_changed = True
actions.append(
'Tracking branch would be '
'updated to {0}'.format(desired_upstream)
)
if upstream_changed:
ret['changes']['upstream'] = {
'old': upstream,
'new': desired_upstream
}
if ret['changes']:
return _neutral_test(ret, _format_comments(actions))
else:
formatted_actions = _format_comments(actions)
if not revs_match \
and not update_head \
and formatted_actions:
ret['comment'] = formatted_actions
return ret
return _uptodate(ret,
target,
_format_comments(actions))
@ -941,7 +942,7 @@ def latest(name,
if not has_remote_rev:
try:
output = __salt__['git.fetch'](
fetch_changes = __salt__['git.fetch'](
target,
remote=remote,
force=force_fetch,
@ -962,9 +963,11 @@ def latest(name,
msg += ':\n\n' + str(exc)
return _fail(ret, msg, comments)
else:
fetch_changes = _parse_fetch(output)
if fetch_changes:
ret['changes']['fetch'] = fetch_changes
comments.append(
'{0} was fetched, resulting in updated '
'refs'.format(name)
)
try:
__salt__['git.rev_parse'](
@ -974,10 +977,24 @@ def latest(name,
except CommandExecutionError as exc:
return _fail(
ret,
'Fetch did not successfully retrieve rev '
'{0}: {1}'.format(rev, exc)
'Fetch did not successfully retrieve rev \'{0}\' '
'from {1}: {2}'.format(rev, name, exc)
)
if (not revs_match and not update_head) \
and (branch is None or branch == local_branch):
# Rev now exists locally (was fetched), and since we're
# not updating HEAD we'll just exit here.
ret['comment'] = remote_loc.capitalize() \
if rev == 'HEAD' \
else remote_loc
ret['comment'] += (
' is already present and local HEAD ({0}) does not '
'match, but update_head=False. HEAD has not been '
'updated locally.'.format(local_rev[:7])
)
return ret
# Now that we've fetched, check again whether or not
# the update is a fast-forward.
if base_rev is None:
@ -1005,8 +1022,8 @@ def latest(name,
return _fail(
ret,
'Local branch \'{0}\' has uncommitted '
'changes. Set \'force_checkout\' to discard '
'them and proceed.'
'changes. Set \'force_checkout\' to True to '
'discard them and proceed.'
)
# TODO: Maybe re-retrieve all_local_branches to handle
@ -1015,7 +1032,7 @@ def latest(name,
# a long time to complete.
if branch not in all_local_branches:
if rev == 'HEAD':
checkout_rev = head_ref
checkout_rev = remote_rev
else:
checkout_rev = desired_upstream \
if desired_upstream \
@ -1029,17 +1046,8 @@ def latest(name,
force=force_checkout,
opts=checkout_opts,
user=user)
ret['changes']['local branch'] = {
'old': local_branch, 'new': branch
}
if fast_forward is False:
if rev == 'HEAD':
reset_ref = head_ref
else:
reset_ref = desired_upstream \
if desired_upstream \
else rev
__salt__['git.reset'](
target,
opts=['--hard', remote_rev],
@ -1047,10 +1055,7 @@ def latest(name,
)
ret['changes']['forced update'] = True
comments.append(
'Repository was hard-reset to {0} ({1})'.format(
reset_ref,
_short_sha(remote_rev)
)
'Repository was hard-reset to {0}'.format(remote_loc)
)
if branch_opts is not None:
@ -1059,11 +1064,6 @@ def latest(name,
base_branch,
opts=branch_opts,
user=user)
ret['changes']['upstream'] = {
'old': upstream,
'new': desired_upstream if desired_upstream
else None
}
comments.append(upstream_action)
# Fast-forward to the desired revision
@ -1071,7 +1071,7 @@ def latest(name,
and not _revs_equal(base_rev,
remote_rev,
remote_rev_type):
if desired_upstream:
if desired_upstream or rev == 'HEAD':
# Check first to see if we are on a branch before
# trying to merge changes. (The call to
# git.symbolic_ref will only return output if HEAD
@ -1080,8 +1080,7 @@ def latest(name,
'HEAD',
opts=['--quiet'],
ignore_retcode=True):
merge_rev = head_ref \
if rev == 'HEAD' \
merge_rev = remote_rev if rev == 'HEAD' \
else desired_upstream
__salt__['git.merge'](
target,
@ -1090,15 +1089,14 @@ def latest(name,
user=user
)
comments.append(
'Repository was fast-forwarded to {0} ({1})'
.format(merge_rev, _short_sha(remote_rev))
'Repository was fast-forwarded to {0}'
.format(remote_loc)
)
else:
# Shouldn't ever happen but fail with a meaningful
# error message if it does.
msg = (
'Unable to merge {0}, HEAD is detached'
.format(desired_upstream)
return _fail(
ret,
'Unable to fast-forward, HEAD is detached',
comments
)
else:
# Update is a fast forward, but we cannot merge to that
@ -1132,20 +1130,36 @@ def latest(name,
return _neutral_test(ret, msg)
else:
return _uptodate(ret, target, msg)
output = __salt__['git.fetch'](
target,
remote=remote,
force=force_fetch,
refspecs=refspecs,
user=user,
identity=identity)
fetch_changes = _parse_fetch(output)
if fetch_changes:
ret['changes']['fetch'] = fetch_changes
comments.append(
'Bare repository at {0} was fetched'.format(target)
)
try:
fetch_changes = __salt__['git.fetch'](
target,
remote=remote,
force=force_fetch,
refspecs=refspecs,
user=user,
identity=identity)
except CommandExecutionError as exc:
msg = 'Fetch failed'
if isinstance(exc, CommandExecutionError):
msg += (
'. Set \'force_fetch\' to True to force '
'the fetch if the failure was due to it '
'bein non-fast-forward. Output of the '
'fetch command follows:\n\n'
)
msg += _strip_exc(exc)
else:
msg += ':\n\n' + str(exc)
return _fail(ret, msg, comments)
else:
comments.append(
'Bare repository at {0} was fetched{1}'.format(
target,
', resulting in updated refs'
if fetch_changes
else ''
)
)
try:
new_rev = __salt__['git.revision'](
cwd=target,
@ -1291,7 +1305,7 @@ def latest(name,
__salt__['git.list_branches'](target,
user=user):
if rev == 'HEAD':
checkout_rev = head_ref
checkout_rev = remote_rev
else:
checkout_rev = desired_upstream \
if desired_upstream \
@ -1301,13 +1315,10 @@ def latest(name,
opts=['-b', branch],
user=user)
comments.append(
'Branch \'{0}\' checked out, with {1} ({2}) '
'Branch \'{0}\' checked out, with {1} '
'as a starting point'.format(
branch,
desired_upstream
if desired_upstream
else rev,
_short_sha(remote_rev)
remote_loc
)
)
@ -1315,25 +1326,13 @@ def latest(name,
_get_local_rev_and_branch(target, user)
if not _revs_equal(local_rev, remote_rev, remote_rev_type):
if rev == 'HEAD':
# Shouldn't happen, if we just cloned the repo and
# than the remote HEAD and remote_rev should be the
# same SHA1.
reset_ref = head_ref
else:
reset_ref = desired_upstream \
if desired_upstream \
else rev
__salt__['git.reset'](
target,
opts=['--hard', remote_rev],
user=user
)
comments.append(
'Repository was reset to {0} ({1})'.format(
reset_ref,
_short_sha(remote_rev)
)
'Repository was reset to {0}'.format(remote_loc)
)
try:
@ -1756,7 +1755,7 @@ def config_unset(name,
**{'global': global_}
)
for key_name, values in six.iteritems(pre):
for key_name in pre:
if key_name not in post:
ret['changes'][key_name] = pre[key_name]
unset = [x for x in pre[key_name] if x not in post[key_name]]
@ -1788,7 +1787,6 @@ def config_unset(name,
def config_set(name,
cwd=None,
value=None,
multivar=None,
repo=None,