From 5d928ca13c7328e998dd81fb86103de762b6565b Mon Sep 17 00:00:00 2001 From: Robin Roth Date: Tue, 5 Jul 2016 20:57:38 +0200 Subject: [PATCH] Fix git shallow update (#3912) * remove unused variables * fetch branch name instead of HEAD fix #3782, which was introduced by f1bacc1d3f9578f26d4ae2f66112cbb2509a7fe8 * disable git depth option for old git versions fixes #3782 git support for `--depth` did not fully work in old git versions (before 1.8.2) fall back to full clones/fetches on those versions * raise required git version to 1.9.1 for depth option * use correct depth argument in switch_version --- lib/ansible/modules/source_control/git.py | 53 ++++++++++++++++------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/lib/ansible/modules/source_control/git.py b/lib/ansible/modules/source_control/git.py index 22555009820..6bcba63a990 100644 --- a/lib/ansible/modules/source_control/git.py +++ b/lib/ansible/modules/source_control/git.py @@ -110,7 +110,7 @@ options: description: - Create a shallow clone with a history truncated to the specified number or revisions. The minimum possible value is C(1), otherwise - ignored. Needs I(git>=1.8.3) to work correctly. + ignored. Needs I(git>=1.9.1) to work correctly. clone: required: false default: "yes" @@ -206,6 +206,7 @@ EXAMPLES = ''' import re import tempfile +from distutils.version import LooseVersion def get_submodule_update_params(module, git_path, cwd): @@ -523,12 +524,12 @@ def fetch(git_path, module, repo, dest, version, remote, depth, bare, refspec): if depth: # try to find the minimal set of refs we need to fetch to get a # successful checkout + currenthead = get_head_branch(git_path, module, dest, remote) if refspec: refspecs.append(refspec) elif version == 'HEAD': - refspecs.append('HEAD') + refspecs.append(currenthead) elif is_remote_branch(git_path, module, dest, repo, version): - currenthead = get_head_branch(git_path, module, dest, remote) if currenthead != version: # this workaroung is only needed for older git versions # 1.8.3 is broken, 1.9.x works @@ -541,6 +542,7 @@ def fetch(git_path, module, repo, dest, version, remote, depth, bare, refspec): refspecs.append('+refs/tags/'+version+':refs/tags/'+version) if refspecs: # if refspecs is empty, i.e. version is neither heads nor tags + # assume it is a version hash # fall back to a full clone, otherwise we might not be able to checkout # version fetch_cmd.extend(['--depth', str(depth)]) @@ -655,12 +657,11 @@ def set_remote_branch(git_path, module, dest, remote, version, depth): if rc != 0: module.fail_json(msg="Failed to fetch branch from remote: %s" % version, stdout=out, stderr=err, rc=rc) -def switch_version(git_path, module, dest, remote, version, verify_commit): +def switch_version(git_path, module, dest, remote, version, verify_commit, depth): cmd = '' if version != 'HEAD': if is_remote_branch(git_path, module, dest, remote, version): if not is_local_branch(git_path, module, dest, version): - depth = module.params['depth'] if depth: # git clone --depth implies --single-branch, which makes # the checkout fail if the version changes @@ -703,6 +704,20 @@ def verify_commit_sign(git_path, module, dest, version): module.fail_json(msg='Failed to verify GPG signature of commit/tag "%s"' % version, stdout=out, stderr=err, rc=rc) return (rc, out, err) + +def git_version(git_path, module): + """return the installed version of git""" + cmd = "%s --version" % git_path + (rc, out, err) = module.run_command(cmd) + if rc != 0: + # one could fail_json here, but the version info is not that important, so let's try to fail only on actual git commands + return None + rematch = re.search('git version (.*)$', out) + if not rematch: + return None + return LooseVersion(rematch.groups()[0]) + + # =========================================== def main(): @@ -746,6 +761,9 @@ def main(): key_file = module.params['key_file'] ssh_opts = module.params['ssh_opts'] + return_values = {} + return_values['warnings'] = [] + # We screenscrape a huge amount of git commands so use C locale anytime we # call run_command() module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C') @@ -775,12 +793,15 @@ def main(): add_git_host_key(module, repo, accept_hostkey=module.params['accept_hostkey']) else: add_git_host_key(module, repo, accept_hostkey=module.params['accept_hostkey']) + git_version_used = git_version(git_path, module) + + if depth is not None and git_version_used < LooseVersion('1.9.1'): + return_values['warnings'].append("Your git version is too old to fully support the depth argument. Falling back to full checkouts.") + depth = None recursive = module.params['recursive'] track_submodules = module.params['track_submodules'] - rc, out, err, status = (0, None, None, None) - before = None local_mods = False repo_updated = None @@ -791,7 +812,7 @@ def main(): # In those cases we do an ls-remote if module.check_mode or not allow_clone: remote_head = get_remote_head(git_path, module, dest, version, repo, bare) - module.exit_json(changed=True, before=before, after=remote_head) + module.exit_json(changed=True, before=before, after=remote_head, **return_values) # there's no git config, so clone clone(git_path, module, repo, dest, remote, depth, version, bare, reference, refspec, verify_commit) repo_updated = True @@ -800,7 +821,7 @@ def main(): # this does no checking that the repo is the actual repo # requested. before = get_version(module, git_path, dest) - module.exit_json(changed=False, before=before, after=before) + module.exit_json(changed=False, before=before, after=before, **return_values) else: # else do a pull local_mods = has_local_mods(module, git_path, dest, bare) @@ -808,7 +829,7 @@ def main(): if local_mods: # failure should happen regardless of check mode if not force: - module.fail_json(msg="Local modifications exist in repository (force=no).") + module.fail_json(msg="Local modifications exist in repository (force=no).", **return_values) # if force and in non-check mode, do a reset if not module.check_mode: reset(git_path, module, dest) @@ -819,7 +840,7 @@ def main(): if before == remote_head: if local_mods: module.exit_json(changed=True, before=before, after=remote_head, - msg="Local modifications exist") + msg="Local modifications exist", **return_values) elif version == 'HEAD': # If the remote and local match and we're using the default of # HEAD (It's not a real tag) then exit early @@ -835,14 +856,14 @@ def main(): if repo_updated is None: if module.check_mode: - module.exit_json(changed=True, before=before, after=remote_head) + module.exit_json(changed=True, before=before, after=remote_head, **return_values) fetch(git_path, module, repo, dest, version, remote, depth, bare, refspec) repo_updated = True # switch to version specified regardless of whether # we got new revisions from the repository if not bare: - switch_version(git_path, module, dest, remote, version, verify_commit) + switch_version(git_path, module, dest, remote, version, verify_commit, depth) # Deal with submodules submodules_updated = False @@ -851,9 +872,9 @@ def main(): if module.check_mode: if submodules_updated: - module.exit_json(changed=True, before=before, after=remote_head, submodules_changed=True) + module.exit_json(changed=True, before=before, after=remote_head, submodules_changed=True, **return_values) else: - module.exit_json(changed=False, before=before, after=remote_head) + module.exit_json(changed=False, before=before, after=remote_head, **return_values) if submodules_updated: # Switch to version specified @@ -874,7 +895,7 @@ def main(): # No need to fail if the file already doesn't exist pass - module.exit_json(changed=changed, before=before, after=after) + module.exit_json(changed=changed, before=before, after=after, **return_values) # import module snippets from ansible.module_utils.basic import *