diff --git a/source_control/git b/source_control/git index c241b8b5e61..85917d24cf9 100644 --- a/source_control/git +++ b/source_control/git @@ -43,6 +43,12 @@ options: - What version of the repository to check out. This can be the full 40-character I(SHA-1) hash, the literal string C(HEAD), a branch name, or a tag name. + reference: + required: false + default: null + version_added: "1.4" + description: + - Reference repository (see "git clone --reference ...") remote: required: false default: "origin" @@ -81,6 +87,14 @@ options: description: - Path to git executable to use. If not supplied, the normal mechanism for resolving binary paths will be used. + bare: + required: false + default: "no" + choices: [ "yes", "no" ] + version_added: "1.4" + description: + - if C(yes), repository will be created as a bare repo, otherwise + it will be a standard repo with a workspace. notes: - If the task seems to be hanging, first verify remote host is in C(known_hosts). SSH will prompt user to authorize the first contact with a remote host. One solution is to add @@ -105,15 +119,14 @@ EXAMPLES = ''' import re import tempfile -def get_version(git_path, dest): +def get_version(git_path, dest, ref="HEAD"): ''' samples the version of the git repo ''' os.chdir(dest) - cmd = "%s show" % (git_path,) - sha = os.popen(cmd).read().split("\n") - sha = sha[0].split()[1] + cmd = "%s rev-parse %s" % (git_path, ref) + sha = os.popen(cmd).read().rstrip("\n") return sha -def clone(git_path, module, repo, dest, remote, depth, version): +def clone(git_path, module, repo, dest, remote, depth, version, bare, reference): ''' makes a new git repo if it does not already exist ''' dest_dirname = os.path.dirname(dest) try: @@ -121,16 +134,28 @@ def clone(git_path, module, repo, dest, remote, depth, version): except: pass os.chdir(dest_dirname) - cmd = [ git_path, 'clone', '-o', remote, '--recursive' ] - if is_remote_branch(git_path, module, dest, repo, version) \ - or is_remote_tag(git_path, module, dest, repo, version): - cmd.extend([ '--branch', version ]) + cmd = [ git_path, 'clone' ] + if bare: + cmd.append('--bare') + else: + cmd.extend([ '--origin', remote, '--recursive' ]) + if is_remote_branch(git_path, module, dest, repo, version) \ + or is_remote_tag(git_path, module, dest, repo, version): + cmd.extend([ '--branch', version ]) if depth: cmd.extend([ '--depth', str(depth) ]) + if reference: + cmd.extend([ '--reference', str(reference) ]) cmd.extend([ repo, dest ]) - return module.run_command(cmd, check_rc=True) - -def has_local_mods(git_path, dest): + module.run_command(cmd, check_rc=True) + if bare: + os.chdir(dest) + if remote != 'origin': + module.run_command([git_path, 'remote', 'add', remote, repo], check_rc=True) + +def has_local_mods(git_path, dest, bare): + if bare: + return False os.chdir(dest) cmd = "%s status -s" % (git_path,) lines = os.popen(cmd).read().splitlines() @@ -147,7 +172,7 @@ def reset(git_path, module, dest): cmd = "%s reset --hard HEAD" % (git_path,) return module.run_command(cmd, check_rc=True) -def get_remote_head(git_path, module, dest, version, remote): +def get_remote_head(git_path, module, dest, version, remote, bare): cloning = False if remote == module.params['repo']: cloning = True @@ -158,7 +183,7 @@ def get_remote_head(git_path, module, dest, version, remote): # cloning the repo, just get the remote's HEAD version cmd = '%s ls-remote %s -h HEAD' % (git_path, remote) else: - head_branch = get_head_branch(git_path, module, dest, remote) + head_branch = get_head_branch(git_path, module, dest, remote, bare) cmd = '%s ls-remote %s -h refs/heads/%s' % (git_path, remote, head_branch) elif is_remote_branch(git_path, module, dest, remote, version): cmd = '%s ls-remote %s -h refs/heads/%s' % (git_path, remote, version) @@ -218,7 +243,7 @@ def is_not_a_branch(git_path, module, dest): return True return False -def get_head_branch(git_path, module, dest, remote): +def get_head_branch(git_path, module, dest, remote, bare=False): ''' Determine what branch HEAD is associated with. This is partly taken from lib/ansible/utils/__init__.py. It finds the correct @@ -226,7 +251,10 @@ def get_head_branch(git_path, module, dest, remote): associated with. In the case of a detached HEAD, this will look up the branch in .git/refs/remotes//HEAD. ''' - repo_path = os.path.join(dest, '.git') + if bare: + repo_path = dest + else: + repo_path = os.path.join(dest, '.git') # Check if the .git is a file. If it is a file, it means that we are in a submodule structure. if os.path.isfile(repo_path): try: @@ -249,14 +277,20 @@ def get_head_branch(git_path, module, dest, remote): f.close() return branch -def fetch(git_path, module, repo, dest, version, remote): +def fetch(git_path, module, repo, dest, version, remote, bare): ''' updates repo from remote sources ''' os.chdir(dest) - (rc, out1, err1) = module.run_command("%s fetch %s" % (git_path, remote)) + if bare: + (rc, out1, err1) = module.run_command([git_path, 'fetch', remote, '+refs/heads/*:refs/heads/*']) + else: + (rc, out1, err1) = module.run_command("%s fetch %s" % (git_path, remote)) if rc != 0: module.fail_json(msg="Failed to download remote objects and refs") - (rc, out2, err2) = module.run_command("%s fetch --tags %s" % (git_path, remote)) + if bare: + (rc, out2, err2) = module.run_command([git_path, 'fetch', remote, '+refs/tags/*:refs/tags/*']) + else: + (rc, out2, err2) = module.run_command("%s fetch --tags %s" % (git_path, remote)) if rc != 0: module.fail_json(msg="Failed to download remote objects and refs") (rc, out3, err3) = submodule_update(git_path, module, dest) @@ -315,24 +349,31 @@ def main(): repo=dict(required=True, aliases=['name']), version=dict(default='HEAD'), remote=dict(default='origin'), + reference=dict(default=None), force=dict(default='yes', type='bool'), depth=dict(default=None, type='int'), update=dict(default='yes', type='bool'), executable=dict(default=None), + bare=dict(default='no', type='bool'), ), supports_check_mode=True ) - dest = os.path.abspath(os.path.expanduser(module.params['dest'])) - repo = module.params['repo'] - version = module.params['version'] - remote = module.params['remote'] - force = module.params['force'] - depth = module.params['depth'] - update = module.params['update'] - git_path = module.params['executable'] or module.get_bin_path('git', True) + dest = os.path.abspath(os.path.expanduser(module.params['dest'])) + repo = module.params['repo'] + version = module.params['version'] + remote = module.params['remote'] + force = module.params['force'] + depth = module.params['depth'] + update = module.params['update'] + bare = module.params['bare'] + reference = module.params['reference'] + git_path = module.params['executable'] or module.get_bin_path('git', True) - gitconfig = os.path.join(dest, '.git', 'config') + if bare: + gitconfig = os.path.join(dest, 'config') + else: + gitconfig = os.path.join(dest, '.git', 'config') rc, out, err, status = (0, None, None, None) @@ -344,7 +385,7 @@ def main(): if module.check_mode: remote_head = get_remote_head(git_path, module, dest, version, repo) module.exit_json(changed=True, before=before, after=remote_head) - clone(git_path, module, repo, dest, remote, depth, version) + clone(git_path, module, repo, dest, remote, depth, version, bare, reference) elif not update: # Just return having found a repo already in the dest path # this does no checking that the repo is the actual repo @@ -353,9 +394,8 @@ def main(): module.exit_json(changed=False, before=before, after=before) else: # else do a pull - local_mods = has_local_mods(git_path, dest) + local_mods = has_local_mods(git_path, dest, bare) before = get_version(git_path, dest) - remote_head = get_remote_head(git_path, module, dest, version, remote) if local_mods: # failure should happen regardless of check mode if not force: @@ -364,6 +404,7 @@ def main(): if not module.check_mode: reset(git_path, module, dest) # exit if already at desired sha version + remote_head = get_remote_head(git_path, module, dest, version, remote, bare) if before == remote_head: if local_mods: module.exit_json(changed=True, before=before, after=remote_head, @@ -372,11 +413,12 @@ def main(): module.exit_json(changed=False, before=before, after=remote_head) if module.check_mode: module.exit_json(changed=True, before=before, after=remote_head) - fetch(git_path, module, repo, dest, version, remote) + fetch(git_path, module, repo, dest, version, remote, bare) # switch to version specified regardless of whether # we cloned or pulled - switch_version(git_path, module, dest, remote, version) + if not bare: + switch_version(git_path, module, dest, remote, version) # determine if we changed anything after = get_version(git_path, dest)