def latest(name,
           rev='HEAD',
           target=None,
           branch=None,
           user=None,
           update_head=True,
           force_checkout=False,
           force_clone=False,
           force_fetch=False,
           force_reset=False,
           submodules=False,
           bare=False,
           mirror=False,
           remote='origin',
           fetch_tags=True,
           depth=None,
           identity=None,
           https_user=None,
           https_pass=None,
           onlyif=False,
           unless=False,
           **kwargs):
    ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}

    kwargs = salt.utils.clean_kwargs(**kwargs)
    always_fetch = kwargs.pop('always_fetch', False)
    force = kwargs.pop('force', False)
    remote_name = kwargs.pop('remote_name', False)
    if kwargs:
        return _fail(
            ret,
            salt.utils.invalid_kwargs(kwargs, raise_exc=False)
        )

    if always_fetch:
        salt.utils.warn_until(
            'Nitrogen',
            'The \'always_fetch\' argument to the git.latest state no longer '
            'has any effect, see the 2015.8.0 release notes for details.'
        )
    if force:
        salt.utils.warn_until(
            'Nitrogen',
            'The \'force\' argument to the git.latest state has been '
            'deprecated, please use \'force_clone\' instead.'
        )
        force_clone = force
    if remote_name:
        salt.utils.warn_until(
            'Nitrogen',
            'The \'remote_name\' argument to the git.latest state has been '
            'deprecated, please use \'remote\' instead.'
        )
        remote = remote_name

    if not remote:
        return _fail(ret, '\'remote\' argument is required')

    if not target:
        return _fail(ret, '\'target\' argument is required')

    if not rev:
        return _fail(
            ret,
            '\'{0}\' is not a valid value for the \'rev\' argument'.format(rev)
        )

    # Ensure that certain arguments are strings to ensure that comparisons work
    if not isinstance(rev, six.string_types):
        rev = str(rev)
    if target is not None:
        if not isinstance(target, six.string_types):
            target = str(target)
        if not os.path.isabs(target):
            return _fail(
                ret,
                'target \'{0}\' is not an absolute path'.format(target)
            )
    if branch is not None and not isinstance(branch, six.string_types):
        branch = str(branch)
    if user is not None and not isinstance(user, six.string_types):
        user = str(user)
    if remote is not None and not isinstance(remote, six.string_types):
        remote = str(remote)
    if identity is not None:
        if isinstance(identity, six.string_types):
            identity = [identity]
        elif not isinstance(identity, list):
            return _fail(ret, 'identity must be either a list or a string')
        for ident_path in identity:
            if 'salt://' in ident_path:
                try:
                    ident_path = __salt__['cp.cache_file'](ident_path)
                except IOError as exc:
                    log.error(
                        'Failed to cache {0}: {1}'.format(ident_path, exc)
                    )
                    return _fail(
                        ret,
                        'identity \'{0}\' does not exist.'.format(
                            ident_path
                        )
                    )
            if not os.path.isabs(ident_path):
                return _fail(
                    ret,
                    'identity \'{0}\' is not an absolute path'.format(
                        ident_path
                    )
                )
    if https_user is not None and not isinstance(https_user, six.string_types):
        https_user = str(https_user)
    if https_pass is not None and not isinstance(https_pass, six.string_types):
        https_pass = str(https_pass)

    if os.path.isfile(target):
        return _fail(
            ret,
            'Target \'{0}\' exists and is a regular file, cannot proceed'
            .format(target)
        )

    try:
        desired_fetch_url = salt.utils.url.add_http_basic_auth(
            name,
            https_user,
            https_pass,
            https_only=True
        )
    except ValueError as exc:
        return _fail(ret, exc.__str__())

    redacted_fetch_url = \
        salt.utils.url.redact_http_basic_auth(desired_fetch_url)

    if mirror:
        bare = True

    # Check to make sure rev and mirror/bare are not both in use
    if rev != 'HEAD' and bare:
        return _fail(ret, ('\'rev\' is not compatible with the \'mirror\' and '
                           '\'bare\' arguments'))

    run_check_cmd_kwargs = {'runas': user}
    if 'shell' in __grains__:
        run_check_cmd_kwargs['shell'] = __grains__['shell']

    # check if git.latest should be applied
    cret = mod_run_check(
        run_check_cmd_kwargs, onlyif, unless
    )
    if isinstance(cret, dict):
        ret.update(cret)
        return ret

    refspecs = [
        'refs/heads/*:refs/remotes/{0}/*'.format(remote),
        '+refs/tags/*:refs/tags/*'
    ] if fetch_tags else []

    log.info('Checking remote revision for {0}'.format(name))
    try:
        all_remote_refs = __salt__['git.remote_refs'](
            name,
            heads=False,
            tags=False,
            user=user,
            identity=identity,
            https_user=https_user,
            https_pass=https_pass,
            ignore_retcode=False)
    except CommandExecutionError as exc:
        return _fail(
            ret,
            'Failed to check remote refs: {0}'.format(_strip_exc(exc))
        )

    if bare:
        remote_rev = None
        remote_rev_type = None
    else:
        if rev == 'HEAD':
            if 'HEAD' in all_remote_refs:
                remote_rev = all_remote_refs['HEAD']
                # Just go with whatever the upstream currently is
                desired_upstream = None
                remote_rev_type = 'sha1'
            else:
                # Empty remote repo
                remote_rev = None
                remote_rev_type = None
        elif 'refs/heads/' + rev in all_remote_refs:
            remote_rev = all_remote_refs['refs/heads/' + rev]
            desired_upstream = '/'.join((remote, rev))
            remote_rev_type = 'branch'
        elif 'refs/tags/' + rev + '^{}' in all_remote_refs:
            # Annotated tag
            remote_rev = all_remote_refs['refs/tags/' + rev + '^{}']
            desired_upstream = False
            remote_rev_type = 'tag'
        elif 'refs/tags/' + rev in all_remote_refs:
            # Non-annotated tag
            remote_rev = all_remote_refs['refs/tags/' + rev]
            desired_upstream = False
            remote_rev_type = 'tag'
        else:
            if len(rev) <= 40 \
                    and all(x in string.hexdigits for x in rev):
                # git ls-remote did not find the rev, and because it's a
                # hex string <= 40 chars we're going to assume that the
                # desired rev is a SHA1
                rev = rev.lower()
                remote_rev = rev
                desired_upstream = False
                remote_rev_type = 'sha1'
            else:
                remote_rev = None
                remote_rev_type = 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]
        elif remote_rev is not None:
            remote_loc = '{0} ({1})'.format(
                desired_upstream if remote_rev_type == 'branch' else rev,
                remote_rev[:7]
            )
        else:
            # Shouldn't happen but log a warning here for future
            # troubleshooting purposes in the event we find a corner case.
            log.warning(
                'Unable to determine remote_loc. rev is %s, remote_rev is '
                '%s, remove_rev_type is %s, desired_upstream is %s, and bare '
                'is%s set',
                rev,
                remote_rev,
                remote_rev_type,
                desired_upstream,
                ' not' if not bare else ''
            )
            remote_loc = None

    if remote_rev is None and not bare:
        if rev != 'HEAD':
            # A specific rev is desired, but that rev doesn't exist on the
            # remote repo.
            return _fail(
                ret,
                'No revision matching \'{0}\' exists in the remote '
                'repository'.format(rev)
            )

    git_ver = _LooseVersion(__salt__['git.version'](versioninfo=False))

    check = 'refs' if bare else '.git'
    gitdir = os.path.join(target, check)
    comments = []
    if os.path.isdir(gitdir) or __salt__['git.is_worktree'](target):
        # Target directory is a git repository or git worktree
        try:
            all_local_branches = __salt__['git.list_branches'](
                target, user=user)
            all_local_tags = __salt__['git.list_tags'](target, user=user)
            local_rev, local_branch = _get_local_rev_and_branch(target, user)

            if not bare and remote_rev is None and local_rev is not None:
                return _fail(
                    ret,
                    'Remote repository is empty, cannot update from a '
                    'non-empty to an empty repository'
                )

            # Base rev and branch are the ones from which any reset or merge
            # will take place. If the branch is not being specified, the base
            # will be the "local" rev and branch, i.e. those we began with
            # before this state was run. If a branch is being specified and it
            # both exists and is not the one with which we started, then we'll
            # be checking that branch out first, and it instead becomes our
            # base. The base branch and rev will be used below in comparisons
            # to determine what changes to make.
            base_rev = local_rev
            base_branch = local_branch
            if _need_branch_change(branch, local_branch):
                if branch not in all_local_branches:
                    # We're checking out a new branch, so the base_rev and
                    # remote_rev will be identical.
                    base_rev = remote_rev
                else:
                    base_branch = branch
                    # Desired branch exists locally and is not the current
                    # branch. We'll be performing a checkout to that branch
                    # eventually, but before we do that we need to find the
                    # current SHA1.
                    try:
                        base_rev = __salt__['git.rev_parse'](
                            target,
                            branch + '^{commit}',
                            user=user,
                            ignore_retcode=True)
                    except CommandExecutionError as exc:
                        return _fail(
                            ret,
                            'Unable to get position of local branch \'{0}\': '
                            '{1}'.format(branch, _strip_exc(exc)),
                            comments
                        )

            remotes = __salt__['git.remotes'](target,
                                              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):
                # Either we're already checked out to the branch we need and it
                # is up-to-date, or the branch to which we need to switch is
                # on the same SHA1 as the desired remote revision. Either way,
                # we know we have the remote rev present already and no fetch
                # will be needed.
                has_remote_rev = True
            else:
                has_remote_rev = False
                if remote_rev is not None:
                    try:
                        __salt__['git.rev_parse'](
                            target,
                            remote_rev + '^{commit}',
                            ignore_retcode=True)
                    except CommandExecutionError:
                        # Local checkout doesn't have the remote_rev
                        pass
                    else:
                        # The object might exist enough to get a rev-parse to
                        # work, while the local ref could have been
                        # deleted/changed/force updated. Do some further sanity
                        # checks to determine if we really do have the
                        # remote_rev.
                        if remote_rev_type == 'branch':
                            if remote in remotes:
                                try:
                                    # Do a rev-parse on <remote>/<rev> to get
                                    # the local SHA1 for it, so we can compare
                                    # it to the remote_rev SHA1.
                                    local_copy = __salt__['git.rev_parse'](
                                        target,
                                        desired_upstream,
                                        user=user,
                                        ignore_retcode=True)
                                except CommandExecutionError:
                                    pass
                                else:
                                    # If the SHA1s don't match, then the remote
                                    # branch was force-updated, and we need to
                                    # fetch to update our local copy the ref
                                    # for the remote branch. If they do match,
                                    # then we have the remote_rev and don't
                                    # need to fetch.
                                    if local_copy == remote_rev:
                                        has_remote_rev = True
                        elif remote_rev_type == 'tag':
                            if rev in all_local_tags:
                                try:
                                    local_tag_sha1 = __salt__['git.rev_parse'](
                                        target,
                                        rev + '^{commit}',
                                        user=user,
                                        ignore_retcode=True)
                                except CommandExecutionError:
                                    # Shouldn't happen if the tag exists
                                    # locally but account for this just in
                                    # case.
                                    local_tag_sha1 = None
                                if local_tag_sha1 == remote_rev:
                                    has_remote_rev = True
                                else:
                                    if not force_reset:
                                        # SHA1 of tag on remote repo is
                                        # different than local tag. Unless
                                        # we're doing a hard reset then we
                                        # don't need to proceed as we know that
                                        # the fetch will update the tag and the
                                        # only way to make the state succeed is
                                        # to reset the branch to point at the
                                        # tag's new location.
                                        return _fail(
                                            ret,
                                            '\'{0}\' is a tag, but the remote '
                                            'SHA1 for this tag ({1}) doesn\'t '
                                            'match the local SHA1 ({2}). Set '
                                            '\'force_reset\' to True to force '
                                            'this update.'.format(
                                                rev,
                                                _short_sha(remote_rev),
                                                _short_sha(local_tag_sha1)
                                            )
                                        )
                        elif remote_rev_type == 'sha1':
                            has_remote_rev = True

            if not has_remote_rev:
                # Either the remote rev could not be found with git
                # ls-remote (in which case we won't know more until
                # fetching) or we're going to be checking out a new branch
                # 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
                    # reason for this is that an empty repository existed there
                    # and a remote was added and fetched, but the repository
                    # was not fast-forwarded. Regardless, going from no HEAD to
                    # a locally-present rev is considered a fast-forward update.
                    fast_forward = True
                else:
                    fast_forward = __salt__['git.merge_base'](
                        target,
                        refs=[base_rev, remote_rev],
                        is_ancestor=True,
                        user=user,
                        ignore_retcode=True)

            if fast_forward is False:
                if not force_reset:
                    return _not_fast_forward(
                        ret,
                        base_rev,
                        remote_rev,
                        branch,
                        local_branch,
                        comments)
                merge_action = 'hard-reset'
            elif fast_forward is True:
                merge_action = 'fast-forwarded'
            else:
                merge_action = 'updated'

            if base_branch is None:
                # No local branch, no upstream tracking branch
                upstream = None
            else:
                try:
                    upstream = __salt__['git.rev_parse'](
                        target,
                        base_branch + '@{upstream}',
                        opts=['--abbrev-ref'],
                        user=user,
                        ignore_retcode=True)
                except CommandExecutionError:
                    # There is a local branch but the rev-parse command
                    # failed, so that means there is no upstream tracking
                    # branch. This could be because it is just not set, or
                    # because the branch was checked out to a SHA1 or tag
                    # instead of a branch. Set upstream to False to make a
                    # distinction between the case above where there is no
                    # local_branch (when the local checkout is an empty
                    # repository).
                    upstream = False

            if remote in remotes:
                fetch_url = remotes[remote]['fetch']
            else:
                log.debug(
                    'Remote \'{0}\' not found in git checkout at {1}'
                    .format(remote, target)
                )
                fetch_url = None

            if remote_rev is not None and desired_fetch_url != fetch_url:
                if __opts__['test']:
                    actions = [
                        '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_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))

                # The fetch_url for the desired remote does not match the
                # specified URL (or the remote does not exist), so set the
                # remote URL.
                __salt__['git.remote_set'](target,
                                           url=name,
                                           remote=remote,
                                           user=user,
                                           https_user=https_user,
                                           https_pass=https_pass)
                comments.append(
                    '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']:
                    actions = []
                    if not has_remote_rev:
                        actions.append(
                            'Remote \'{0}\' would be fetched'.format(remote)
                        )
                    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 _need_branch_change(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(
                                    'Tracking branch would be set to {0}'
                                    .format(desired_upstream)
                                )
                        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_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(
                                    '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
                    if not upstream and desired_upstream:
                        actions.append(
                            'Tracking branch would be set to {0}'.format(
                                desired_upstream
                            )
                        )
                    elif upstream and desired_upstream is False:
                        actions.append(
                            'Tracking branch would be unset'
                        )
                    elif desired_upstream and upstream != desired_upstream:
                        actions.append(
                            'Tracking branch would be '
                            'updated to {0}'.format(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))

                if not upstream and desired_upstream:
                    upstream_action = (
                        'Tracking branch was set to {0}'.format(
                            desired_upstream
                        )
                    )
                    branch_opts = _get_branch_opts(
                        branch,
                        local_branch,
                        all_local_branches,
                        desired_upstream,
                        git_ver)
                elif upstream and desired_upstream is False:
                    # If the remote_rev is a tag or SHA1, and there is an
                    # upstream tracking branch, we will unset it. However, we
                    # can only do this if the git version is 1.8.0 or newer, as
                    # the --unset-upstream option was not added until that
                    # version.
                    if git_ver >= _LooseVersion('1.8.0'):
                        upstream_action = 'Tracking branch was unset'
                        branch_opts = ['--unset-upstream']
                    else:
                        branch_opts = None
                elif desired_upstream and upstream != desired_upstream:
                    upstream_action = (
                        'Tracking branch was updated to {0}'.format(
                            desired_upstream
                        )
                    )
                    branch_opts = _get_branch_opts(
                        branch,
                        local_branch,
                        all_local_branches,
                        desired_upstream,
                        git_ver)
                else:
                    branch_opts = None

                if not has_remote_rev:
                    try:
                        fetch_changes = __salt__['git.fetch'](
                            target,
                            remote=remote,
                            force=force_fetch,
                            refspecs=refspecs,
                            user=user,
                            identity=identity)
                    except CommandExecutionError as exc:
                        return _failed_fetch(ret, exc, comments)
                    else:
                        if fetch_changes:
                            comments.append(
                                '{0} was fetched, resulting in updated '
                                'refs'.format(name)
                            )

                    try:
                        __salt__['git.rev_parse'](
                            target,
                            remote_rev + '^{commit}',
                            ignore_retcode=True)
                    except CommandExecutionError as exc:
                        return _fail(
                            ret,
                            '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:
                        fast_forward = True
                    else:
                        fast_forward = __salt__['git.merge_base'](
                            target,
                            refs=[base_rev, remote_rev],
                            is_ancestor=True,
                            user=user)

                    if fast_forward is False and not force_reset:
                        return _not_fast_forward(
                            ret,
                            base_rev,
                            remote_rev,
                            branch,
                            local_branch,
                            comments)

                if _need_branch_change(branch, local_branch):
                    local_changes = __salt__['git.status'](target,
                                                           user=user)
                    if local_changes and not force_checkout:
                        return _fail(
                            ret,
                            'Local branch \'{0}\' has uncommitted '
                            'changes. Set \'force_checkout\' to True to '
                            'discard them and proceed.'.format(local_branch)
                        )

                    # TODO: Maybe re-retrieve all_local_branches to handle
                    # the corner case where the destination branch was
                    # added to the local checkout during a fetch that takes
                    # a long time to complete.
                    if branch not in all_local_branches:
                        if rev == 'HEAD':
                            checkout_rev = remote_rev
                        else:
                            checkout_rev = desired_upstream \
                                if desired_upstream \
                                else rev
                        checkout_opts = ['-b', branch]
                    else:
                        checkout_rev = branch
                        checkout_opts = []
                    __salt__['git.checkout'](target,
                                             checkout_rev,
                                             force=force_checkout,
                                             opts=checkout_opts,
                                             user=user)
                    if '-b' in checkout_opts:
                        comments.append(
                            'New branch \'{0}\' was checked out, with {1} '
                            'as a starting point'.format(
                                branch,
                                remote_loc
                            )
                        )
                    else:
                        comments.append(
                            '\'{0}\' was checked out'.format(checkout_rev)
                        )

                if fast_forward is False:
                    __salt__['git.reset'](
                        target,
                        opts=['--hard', remote_rev],
                        user=user
                    )
                    ret['changes']['forced update'] = True
                    comments.append(
                        'Repository was hard-reset to {0}'.format(remote_loc)
                    )

                if branch_opts is not None:
                    __salt__['git.branch'](
                        target,
                        opts=branch_opts,
                        user=user)
                    comments.append(upstream_action)

                # Fast-forward to the desired revision
                if fast_forward is True \
                        and not _revs_equal(base_rev,
                                            remote_rev,
                                            remote_rev_type):
                    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
                        # points to a branch.)
                        if __salt__['git.symbolic_ref'](target,
                                                        'HEAD',
                                                        opts=['--quiet'],
                                                        ignore_retcode=True):
                            merge_rev = remote_rev if rev == 'HEAD' \
                                else desired_upstream

                            if git_ver >= _LooseVersion('1.8.1.6'):
                                # --ff-only added in version 1.8.1.6. It's not
                                # 100% necessary, but if we can use it, we'll
                                # ensure that the merge doesn't go through if
                                # not a fast-forward. Granted, the logic that
                                # gets us to this point shouldn't allow us to
                                # attempt this merge if it's not a
                                # fast-forward, but it's an extra layer of
                                # protection.
                                merge_opts = ['--ff-only']
                            else:
                                merge_opts = []

                            __salt__['git.merge'](
                                target,
                                rev=merge_rev,
                                opts=merge_opts,
                                user=user
                            )
                            comments.append(
                                'Repository was fast-forwarded to {0}'
                                .format(remote_loc)
                            )
                        else:
                            return _fail(
                                ret,
                                'Unable to fast-forward, HEAD is detached',
                                comments
                            )
                    else:
                        # Update is a fast forward, but we cannot merge to that
                        # commit so we'll reset to it.
                        __salt__['git.reset'](
                            target,
                            opts=['--hard',
                                  remote_rev if rev == 'HEAD' else rev],
                            user=user
                        )
                        comments.append(
                            'Repository was reset to {0} (fast-forward)'
                            .format(rev)
                        )

                # TODO: Figure out how to add submodule update info to
                # test=True return data, and changes dict.
                if submodules:
                    try:
                        __salt__['git.submodule'](
                            target,
                            'update',
                            opts=['--init', '--recursive'],
                            user=user,
                            identity=identity)
                    except CommandExecutionError as exc:
                        return _failed_submodule_update(ret, exc, comments)
            elif bare:
                if __opts__['test']:
                    msg = (
                        'Bare repository at {0} would be fetched'
                        .format(target)
                    )
                    if ret['changes']:
                        return _neutral_test(ret, msg)
                    else:
                        return _uptodate(ret, target, msg)
                try:
                    fetch_changes = __salt__['git.fetch'](
                        target,
                        remote=remote,
                        force=force_fetch,
                        refspecs=refspecs,
                        user=user,
                        identity=identity)
                except CommandExecutionError as exc:
                    return _failed_fetch(ret, exc, 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,
                    user=user,
                    ignore_retcode=True)
            except CommandExecutionError:
                new_rev = None

        except Exception as exc:
            log.error(
                'Unexpected exception in git.latest state',
                exc_info=True
            )
            if isinstance(exc, CommandExecutionError):
                msg = _strip_exc(exc)
            else:
                msg = str(exc)
            return _fail(ret, msg, comments)

        if not bare and not _revs_equal(new_rev,
                                        remote_rev,
                                        remote_rev_type):
            return _fail(ret, 'Failed to update repository', comments)

        if local_rev != new_rev:
            log.info(
                'Repository {0} updated: {1} => {2}'.format(
                    target, local_rev, new_rev)
            )
            ret['comment'] = _format_comments(comments)
            ret['changes']['revision'] = {'old': local_rev, 'new': new_rev}
        else:
            return _uptodate(ret, target, _format_comments(comments))
    else:
        if os.path.isdir(target):
            if force_clone:
                # Clone is required, and target directory exists, but the
                # ``force`` option is enabled, so we need to clear out its
                # contents to proceed.
                if __opts__['test']:
                    ret['changes']['forced clone'] = True
                    ret['changes']['new'] = name + ' => ' + target
                    return _neutral_test(
                        ret,
                        'Target directory {0} exists. Since force_clone=True, '
                        'the contents of {0} would be deleted, and {1} would '
                        'be cloned into this directory.'.format(target, name)
                    )
                log.debug(
                    'Removing contents of {0} to clone repository {1} in its '
                    'place (force_clone=True set in git.latest state)'
                    .format(target, name)
                )
                try:
                    if os.path.islink(target):
                        os.unlink(target)
                    else:
                        salt.utils.rm_rf(target)
                except OSError as exc:
                    return _fail(
                        ret,
                        'Unable to remove {0}: {1}'.format(target, exc),
                        comments
                    )
                else:
                    ret['changes']['forced clone'] = True
            # Clone is required, but target dir exists and is non-empty. We
            # can't proceed.
            elif os.listdir(target):
                return _fail(
                    ret,
                    'Target \'{0}\' exists, is non-empty and is not a git '
                    'repository. Set the \'force_clone\' option to True to '
                    'remove this directory\'s contents and proceed with '
                    'cloning the remote repository'.format(target)
                )

        log.debug(
            'Target {0} is not found, \'git clone\' is required'.format(target)
        )
        if __opts__['test']:
            ret['changes']['new'] = name + ' => ' + target
            return _neutral_test(
                ret,
                'Repository {0} would be cloned to {1}'.format(
                    name, target
                )
            )
        try:
            clone_opts = ['--mirror'] if mirror else ['--bare'] if bare else []
            if remote != 'origin':
                clone_opts.extend(['--origin', remote])
            if depth is not None:
                clone_opts.extend(['--depth', str(depth)])

            # We're cloning a fresh repo, there is no local branch or revision
            local_branch = local_rev = None

            try:
                __salt__['git.clone'](target,
                                      name,
                                      user=user,
                                      opts=clone_opts,
                                      identity=identity,
                                      https_user=https_user,
                                      https_pass=https_pass)
            except CommandExecutionError as exc:
                msg = 'Clone failed: {0}'.format(_strip_exc(exc))
                return _fail(ret, msg, comments)

            ret['changes']['new'] = name + ' => ' + target
            comments.append(
                '{0} cloned to {1}{2}'.format(
                    name,
                    target,
                    ' as mirror' if mirror
                        else ' as bare repository' if bare
                        else ''
                )
            )

            if not bare:
                if not remote_rev:
                    if rev != 'HEAD':
                        # No HEAD means the remote repo is empty, which means
                        # our new clone will also be empty. This state has
                        # failed, since a rev was specified but no matching rev
                        # exists on the remote host.
                        msg = (
                            '{{0}} was cloned but is empty, so {0}/{1} '
                            'cannot be checked out'.format(remote, rev)
                        )
                        log.error(msg.format(name))
                        return _fail(ret, msg.format('Repository'), comments)
                else:
                    if remote_rev_type == 'tag' \
                            and rev not in __salt__['git.list_tags'](
                                target, user=user):
                        return _fail(
                            ret,
                            'Revision \'{0}\' does not exist in clone'
                            .format(rev),
                            comments
                        )

                    if branch is not None:
                        if branch not in \
                                __salt__['git.list_branches'](target,
                                                              user=user):
                            if rev == 'HEAD':
                                checkout_rev = remote_rev
                            else:
                                checkout_rev = desired_upstream \
                                    if desired_upstream \
                                    else rev
                            __salt__['git.checkout'](target,
                                                     checkout_rev,
                                                     opts=['-b', branch],
                                                     user=user)
                            comments.append(
                                'Branch \'{0}\' checked out, with {1} '
                                'as a starting point'.format(
                                    branch,
                                    remote_loc
                                )
                            )

                    local_rev, local_branch = \
                        _get_local_rev_and_branch(target, user)

                    if not _revs_equal(local_rev, remote_rev, remote_rev_type):
                        __salt__['git.reset'](
                            target,
                            opts=['--hard', remote_rev],
                            user=user
                        )
                        comments.append(
                            'Repository was reset to {0}'.format(remote_loc)
                        )

                    try:
                        upstream = __salt__['git.rev_parse'](
                            target,
                            local_branch + '@{upstream}',
                            opts=['--abbrev-ref'],
                            user=user,
                            ignore_retcode=True)
                    except CommandExecutionError:
                        upstream = False

                    if not upstream and desired_upstream:
                        upstream_action = (
                            'Tracking branch was set to {0}'.format(
                                desired_upstream
                            )
                        )
                        branch_opts = _get_branch_opts(
                            branch,
                            local_branch,
                            __salt__['git.list_branches'](target, user=user),
                            desired_upstream,
                            git_ver)
                    elif upstream and desired_upstream is False:
                        # If the remote_rev is a tag or SHA1, and there is an
                        # upstream tracking branch, we will unset it. However,
                        # we can only do this if the git version is 1.8.0 or
                        # newer, as the --unset-upstream option was not added
                        # until that version.
                        if git_ver >= _LooseVersion('1.8.0'):
                            upstream_action = 'Tracking branch was unset'
                            branch_opts = ['--unset-upstream']
                        else:
                            branch_opts = None
                    elif desired_upstream and upstream != desired_upstream:
                        upstream_action = (
                            'Tracking branch was updated to {0}'.format(
                                desired_upstream
                            )
                        )
                        branch_opts = _get_branch_opts(
                            branch,
                            local_branch,
                            __salt__['git.list_branches'](target, user=user),
                            desired_upstream,
                            git_ver)
                    else:
                        branch_opts = None

                    if branch_opts is not None:
                        __salt__['git.branch'](
                            target,
                            opts=branch_opts,
                            user=user)
                        comments.append(upstream_action)

            if submodules and remote_rev:
                try:
                    __salt__['git.submodule'](target,
                                              'update',
                                              opts=['--init', '--recursive'],
                                              user=user,
                                              identity=identity)
                except CommandExecutionError as exc:
                    return _failed_submodule_update(ret, exc, comments)

            try:
                new_rev = __salt__['git.revision'](
                    cwd=target,
                    user=user,
                    ignore_retcode=True)
            except CommandExecutionError:
                new_rev = None

        except Exception as exc:
            log.error(
                'Unexpected exception in git.latest state',
                exc_info=True
            )
            if isinstance(exc, CommandExecutionError):
                msg = _strip_exc(exc)
            else:
                msg = str(exc)
            return _fail(ret, msg, comments)

        msg = _format_comments(comments)
        log.info(msg)
        ret['comment'] = msg
        if new_rev is not None:
            ret['changes']['revision'] = {'old': None, 'new': new_rev}
    return ret
Tests the GetParentFileEntry function.
dataset/ETHPy150Open saltstack/salt/salt/states/git.py/latest--crossover with--dataset/ETHPy150Open log2timeline/dfvfs/tests/vfs/fake_file_entry.py/FakeFileEntryTest.testGetParentFileEntry