diff --git a/source_control/git.py b/source_control/git.py index ee8b9a6d29a..968ae77bcd1 100644 --- a/source_control/git.py +++ b/source_control/git.py @@ -80,6 +80,17 @@ options: default: "origin" description: - Name of the remote. + refspec: + required: false + default: null + version_added: "1.9" + description: + - Add an additional refspec to be fetched. + If version is set to a I(SHA-1) not reachable from any branch + or tag, this option may be necessary to specify the ref containing + the I(SHA-1). + Uses the same syntax as the 'git fetch' command. + An example value could be "refs/meta/config". force: required: false default: "yes" @@ -170,6 +181,9 @@ EXAMPLES = ''' # Example just get information about the repository whether or not it has # already been cloned locally. - git: repo=git://foosball.example.org/path/to/repo.git dest=/srv/checkout clone=no update=no + +# Example checkout a github repo and use refspec to fetch all pull requests +- git: repo=https://github.com/ansible/ansible-examples.git dest=/src/ansible-examples refspec=+refs/pull/*:refs/heads/* ''' import re @@ -283,7 +297,7 @@ def get_submodule_versions(git_path, module, dest, version='HEAD'): return submodules def clone(git_path, module, repo, dest, remote, depth, version, bare, - reference): + reference, refspec): ''' makes a new git repo if it does not already exist ''' dest_dirname = os.path.dirname(dest) try: @@ -308,6 +322,9 @@ def clone(git_path, module, repo, dest, remote, depth, version, bare, if remote != 'origin': module.run_command([git_path, 'remote', 'add', remote, repo], check_rc=True, cwd=dest) + if refspec: + module.run_command([git_path, 'fetch', remote, refspec], check_rc=True, cwd=dest) + def has_local_mods(module, git_path, dest, bare): if bare: return False @@ -455,35 +472,29 @@ def get_head_branch(git_path, module, dest, remote, bare=False): f.close() return branch -def fetch(git_path, module, repo, dest, version, remote, bare): +def fetch(git_path, module, repo, dest, version, remote, bare, refspec): ''' updates repo from remote sources ''' - out_acc = [] - err_acc = [] - (rc, out0, err0) = module.run_command([git_path, 'remote', 'set-url', remote, repo], cwd=dest) - if rc != 0: - module.fail_json(msg="Failed to set a new url %s for %s: %s" % (repo, remote, out0 + err0)) - if bare: - (rc, out1, err1) = module.run_command([git_path, 'fetch', remote, '+refs/heads/*:refs/heads/*'], cwd=dest) - else: - (rc, out1, err1) = module.run_command("%s fetch %s" % (git_path, remote), cwd=dest) - out_acc.append(out1) - err_acc.append(err1) - if rc != 0: - module.fail_json(msg="Failed to download remote objects and refs: %s %s" % - (''.join(out_acc), ''.join(err_acc))) + commands = [["set a new url %s for %s" % (repo, remote)], [git_path, 'remote', 'set-url', remote, repo]] + + fetch_str = 'download remote objects and refs' if bare: - (rc, out2, err2) = module.run_command([git_path, 'fetch', remote, '+refs/tags/*:refs/tags/*'], cwd=dest) + refspecs = ['+refs/heads/*:refs/heads/*', '+refs/tags/*:refs/tags/*'] + if refspec: + refspecs.append(refspec) + commands.append([fetch_str, [git_path, 'fetch', remote] + refspecs]) else: - (rc, out2, err2) = module.run_command("%s fetch --tags %s" % (git_path, remote), cwd=dest) - out_acc.append(out2) - err_acc.append(err2) - if rc != 0: - module.fail_json(msg="Failed to download remote objects and refs: %s %s" % - (''.join(out_acc), ''.join(err_acc))) - - return (rc, ''.join(out_acc), ''.join(err_acc)) - + commands.append([fetch_str, [git_path, 'fetch', '--tags']]) + if refspec: + # unlike in bare mode, there's no way to combine the + # additional refspec with the default git fetch behavior, + # so use two commands + commands.append([fetch_str, [git_path, 'fetch', remote, refspec]]) + + for (label,command) in commands: + (rc,out,err) = module.run_command(command, cwd=dest) + if rc != 0: + module.fail_json(msg="Failed to %s: %s %s" % (label, out, err)) def submodules_fetch(git_path, module, remote, track_submodules, dest): changed = False @@ -596,6 +607,7 @@ def main(): repo=dict(required=True, aliases=['name']), version=dict(default='HEAD'), remote=dict(default='origin'), + refspec=dict(default=None), reference=dict(default=None), force=dict(default='yes', type='bool'), depth=dict(default=None, type='int'), @@ -616,6 +628,7 @@ def main(): repo = module.params['repo'] version = module.params['version'] remote = module.params['remote'] + refspec = module.params['refspec'] force = module.params['force'] depth = module.params['depth'] update = module.params['update'] @@ -673,7 +686,7 @@ def main(): remote_head = get_remote_head(git_path, module, dest, version, repo, bare) module.exit_json(changed=True, before=before, after=remote_head) # there's no git config, so clone - clone(git_path, module, repo, dest, remote, depth, version, bare, reference) + clone(git_path, module, repo, dest, remote, depth, version, bare, reference, refspec) repo_updated = True elif not update: # Just return having found a repo already in the dest path @@ -707,7 +720,7 @@ def main(): if repo_updated is None: if module.check_mode: module.exit_json(changed=True, before=before, after=remote_head) - fetch(git_path, module, repo, dest, version, remote, bare) + fetch(git_path, module, repo, dest, version, remote, bare, refspec) repo_updated = True # switch to version specified regardless of whether