Repository: ansible/ansible-modules-core

Issue:
ansible-pull: Shallow clone broken in 2.1.0.0-1
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

ansible-pull
##### ANSIBLE VERSION

```
ansible 2.1.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

None
##### OS / ENVIRONMENT

Ubuntu 12.04 (Precise)
##### SUMMARY

After upgrading to 2.1.0.0-1 ansible-pull no longer sees changes when doing a shallow clone.
Running ansible-pull with --full still works.
##### STEPS TO REPRODUCE

```
ansible-pull -d /etc/ansible/ring -U https://github.com/NLNOG/ring-ansible.git -i nodes -l <hostname> -c local playbook.yml
```
##### EXPECTED RESULTS

New changes are pulled.
##### ACTUAL RESULTS

Recent changes in the repository are not seen.

```
# ansible-pull -vvv -d /etc/ansible/ring -U https://github.com/NLNOG/ring-ansible.git -i nodes -l <hostname> -c local playbook.yml 
Using /etc/ansible/ansible.cfg as config file
Starting Ansible Pull at 2016-05-29 08:38:59
/usr/bin/ansible-pull -vvv -d /etc/ansible/ring -U https://github.com/NLNOG/ring-ansible.git -i nodes -l <hostname> -c local playbook.yml
Using /etc/ansible/ansible.cfg as config file
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186 `" && echo ansible-tmp-1464511140.14-77534406207186="` echo $HOME/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186 `" ) && sleep 0'
<localhost> PUT /tmp/tmpOAZtUz TO /root/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186/git
<localhost> EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186/git; rm -rf "/root/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186/" > /dev/null 2>&1 && sleep 0'
localhost | SUCCESS => {
    "after": "159e4f9cf7f8f41915713360d9c66e5daecdd7f9", 
    "before": "159e4f9cf7f8f41915713360d9c66e5daecdd7f9", 
    "changed": false, 
    "invocation": {
        "module_args": {
            "accept_hostkey": false, 
            "bare": false, 
            "clone": true, 
            "depth": 1, 
            "dest": "/etc/ansible/ring", 
            "executable": null, 
            "force": false, 
            "key_file": null, 
            "name": "https://github.com/NLNOG/ring-ansible.git", 
            "recursive": true, 
            "reference": null, 
            "refspec": null, 
            "remote": "origin", 
            "repo": "https://github.com/NLNOG/ring-ansible.git", 
            "ssh_opts": null, 
            "track_submodules": false, 
            "update": true, 
            "verify_commit": false, 
            "version": "HEAD"
        }, 
        "module_name": "git"
    }
}
```

ansible-pull: Shallow clone broken in 2.1.0.0-1
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

ansible-pull
##### ANSIBLE VERSION

```
ansible 2.1.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

None
##### OS / ENVIRONMENT

Ubuntu 12.04 (Precise)
##### SUMMARY

After upgrading to 2.1.0.0-1 ansible-pull no longer sees changes when doing a shallow clone.
Running ansible-pull with --full still works.
##### STEPS TO REPRODUCE

```
ansible-pull -d /etc/ansible/ring -U https://github.com/NLNOG/ring-ansible.git -i nodes -l <hostname> -c local playbook.yml
```
##### EXPECTED RESULTS

New changes are pulled.
##### ACTUAL RESULTS

Recent changes in the repository are not seen.

```
# ansible-pull -vvv -d /etc/ansible/ring -U https://github.com/NLNOG/ring-ansible.git -i nodes -l <hostname> -c local playbook.yml 
Using /etc/ansible/ansible.cfg as config file
Starting Ansible Pull at 2016-05-29 08:38:59
/usr/bin/ansible-pull -vvv -d /etc/ansible/ring -U https://github.com/NLNOG/ring-ansible.git -i nodes -l <hostname> -c local playbook.yml
Using /etc/ansible/ansible.cfg as config file
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186 `" && echo ansible-tmp-1464511140.14-77534406207186="` echo $HOME/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186 `" ) && sleep 0'
<localhost> PUT /tmp/tmpOAZtUz TO /root/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186/git
<localhost> EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186/git; rm -rf "/root/.ansible/tmp/ansible-tmp-1464511140.14-77534406207186/" > /dev/null 2>&1 && sleep 0'
localhost | SUCCESS => {
    "after": "159e4f9cf7f8f41915713360d9c66e5daecdd7f9", 
    "before": "159e4f9cf7f8f41915713360d9c66e5daecdd7f9", 
    "changed": false, 
    "invocation": {
        "module_args": {
            "accept_hostkey": false, 
            "bare": false, 
            "clone": true, 
            "depth": 1, 
            "dest": "/etc/ansible/ring", 
            "executable": null, 
            "force": false, 
            "key_file": null, 
            "name": "https://github.com/NLNOG/ring-ansible.git", 
            "recursive": true, 
            "reference": null, 
            "refspec": null, 
            "remote": "origin", 
            "repo": "https://github.com/NLNOG/ring-ansible.git", 
            "ssh_opts": null, 
            "track_submodules": false, 
            "update": true, 
            "verify_commit": false, 
            "version": "HEAD"
        }, 
        "module_name": "git"
    }
}
```



Discussion:
I believe f1bacc1d3f9578f26d4ae2f66112cbb2509a7fe8 broke this. I'll have a look.

I believe f1bacc1d3f9578f26d4ae2f66112cbb2509a7fe8 broke this. I'll have a look.


Resolving patch:
diff --git a/source_control/git.py b/source_control/git.py
--- a/source_control/git.py
+++ b/source_control/git.py
@@ -110,7 +110,7 @@
         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 @@
 
 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 *


Test patch: