Repository: ansible/ansible

Issue:
Git: depth=1 prevents checking out a tag
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
git

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.1.0
  config file =
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
Observed on Mac OS/X and Ubuntu 16.04

##### SUMMARY
<!--- Explain the problem briefly -->
If you use `depth: 1` on a git clone, then you will not be able to use `version: any_tag` successfully.

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: 127.0.0.1
  connection: local
  gather_facts: no
  tasks:
    - name: make a deep clone
      git:
        repo: git@github.com:edx/ecommerce.git
        dest: deep
        version: open-release/ficus.1rc3

    - name: make a shallow clone
      git:
        repo: git@github.com:edx/ecommerce.git
        dest: shallow
        version: open-release/ficus.1rc3
        depth: 1

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
I expected to get two clones of my git repo, `shallow` and `deep`, each checked out to the requested tag.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
My `deep` repo is as I wanted, checked out to the tag.  The `shallow` repo is incorrectly checked out to `master`, and ansible has failed.

<!--- Paste verbatim command output between quotes below -->
```
$ ansible-playbook -i 127.0.0.1, ./test.yml

PLAY [127.0.0.1] ***************************************************************

TASK [make a deep clone] *******************************************************
changed: [127.0.0.1]

TASK [make a shallow clone] ****************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": false, "cmd": "/usr/local/bin/git checkout --force open-release/ficus.1rc3", "failed": true, "msg": "Failed to checkout open-release/ficus.1rc3", "rc": 1, "stderr": "error: pathspec 'open-release/ficus.1rc3' did not match any file(s) known to git.\n", "stdout": "", "stdout_lines": []}
	to retry, use: --limit @/Users/ned/foo/ansible_git_bug/test.retry

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=1    changed=1    unreachable=0    failed=1
```

Using direct git commands, adding `--no-single-branch` would solve this problem, by getting a little bit of history from all the branches.

I think this is the same root cause as #19074, though this issue is a simpler use-case, since one clone is all it takes, rather than cloning and then later checking out a different revision.

Also, note that specifying a branch as version works fine. Only tags are affected.


Discussion:
We should not have `--no-single-branch` in general. This effectively download the majority of the repo for typical repos, that have tags for releases going back the full history. 
See e.g. https://github.com/ansible/ansible/issues/14954 for a potential regression then.

There is a (simple) workaround: Add a `reference`.

The solution is probably to add `--branch` to the clone. One should extend the test coverage to prevent regressions on this.
@robinro i'm sorry, i don't understand how `reference` helps? That refers to another repo?
I meant `refspec`, but when trying to build an example I just found in the current state this only works with hashes, not tags:
```
        git: 
          repo: git@github.com:edx/ecommerce.git
          dest: shallow
          version: '52b86ef4168707df94de7921b8bbbe25026c8ac8'
          refspec: 'open-release/ficus.master'
          depth: 1
```

In the integration tests we already have a test for switching to a tag on a shallow clone, but not for the initial clone...
This needs to be fixed asap.
@Nitaco Why the urgency? You can always fall back to a full clone. Afaik shallow+tag never worked correctly.

To speed things up you can open a PR. I'd be happy to review a fix. One should probably add a testcase to the integration tests for this.
In ansible 2.1.x this always worked correctly. We are using this version in productive, so we can't deploy new versions correctly. So I had to revert the ansible version to 2.1.x.

Resolving patch:
diff --git a/lib/ansible/modules/source_control/git.py b/lib/ansible/modules/source_control/git.py
--- a/lib/ansible/modules/source_control/git.py
+++ b/lib/ansible/modules/source_control/git.py
@@ -423,12 +423,17 @@ def clone(git_path, module, repo, dest, remote, depth, version, bare,
     else:
         cmd.extend([ '--origin', remote ])
     if depth:
-        if version == 'HEAD' \
-           or refspec  \
-           or is_remote_branch(git_path, module, dest, repo, version) \
-           or is_remote_tag(git_path, module, dest, repo, version):
-            # only use depth if the remote object is branch or tag (i.e. fetchable)
+        if version == 'HEAD' or refspec:
+            cmd.extend([ '--depth', str(depth) ])
+        elif is_remote_branch(git_path, module, dest, repo, version) \
+                or is_remote_tag(git_path, module, dest, repo, version):
             cmd.extend([ '--depth', str(depth) ])
+            cmd.extend(['--branch', version])
+        else:
+            # only use depth if the remote object is branch or tag (i.e. fetchable)
+            module.warn("Ignoring depth argument. "
+                        "Shallow clones are only available for "
+                        "HEAD, branches, tags or in combination with refspec.")
     if reference:
         cmd.extend([ '--reference', str(reference) ])
     cmd.extend([ repo, dest ])


Test patch:
diff --git a/test/integration/targets/git/tasks/depth.yml b/test/integration/targets/git/tasks/depth.yml
--- a/test/integration/targets/git/tasks/depth.yml
+++ b/test/integration/targets/git/tasks/depth.yml
@@ -64,6 +64,28 @@
   args:
     chdir: '{{ checkout_dir }}'
 
+- name: clear checkout_dir
+  file:
+    state: absent
+    path: "{{ checkout_dir }}"
+
+# Test for https://github.com/ansible/ansible/issues/21316
+- name: Shallow clone with tag
+  git:
+    repo: 'file://{{ repo_dir|expanduser }}/shallow'
+    dest: '{{ checkout_dir }}'
+    depth: 1
+    version: earlytag
+  register: cloneold
+
+- assert:
+    that: "cloneold|success"
+
+- name: clear checkout_dir
+  file:
+    state: absent
+    path: "{{ checkout_dir }}"
+
 
   # Test for https://github.com/ansible/ansible-modules-core/issues/3456
   # clone a repo with depth and version specified