Support --separate-git-dir
option in Git module (#41712)
Make git module support --separate-git-dir option. When git version is higher than 1.7.5, use built-in --separate-git-dir option during the clone. When lower, adjust the location of git dir manually after clone to achieve the same effect.
This commit is contained in:
parent
a66588129f
commit
d7921b4d5b
4 changed files with 143 additions and 12 deletions
|
@ -161,6 +161,12 @@ options:
|
||||||
all git servers support git archive.
|
all git servers support git archive.
|
||||||
version_added: "2.4"
|
version_added: "2.4"
|
||||||
|
|
||||||
|
separate_git_dir:
|
||||||
|
description:
|
||||||
|
- The path to place the cloned repository. If specified, Git repository
|
||||||
|
can be separated from working tree.
|
||||||
|
version_added: "2.7"
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
- git>=1.7.1 (the command line tool)
|
- git>=1.7.1 (the command line tool)
|
||||||
|
|
||||||
|
@ -210,6 +216,11 @@ EXAMPLES = '''
|
||||||
dest: /src/ansible-examples
|
dest: /src/ansible-examples
|
||||||
archive: /tmp/ansible-examples.zip
|
archive: /tmp/ansible-examples.zip
|
||||||
|
|
||||||
|
# Example clone a repo with separate git directory
|
||||||
|
- git:
|
||||||
|
repo: https://github.com/ansible/ansible-examples.git
|
||||||
|
dest: /src/ansible-examples
|
||||||
|
separate_git_dir: /src/ansible-examples.git
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
|
@ -233,6 +244,16 @@ warnings:
|
||||||
returned: error
|
returned: error
|
||||||
type: string
|
type: string
|
||||||
sample: Your git version is too old to fully support the depth argument. Falling back to full checkouts.
|
sample: Your git version is too old to fully support the depth argument. Falling back to full checkouts.
|
||||||
|
git_dir_now:
|
||||||
|
description: Contains the new path of .git directory if it's changed
|
||||||
|
returned: success
|
||||||
|
type: string
|
||||||
|
sample: /path/to/new/git/dir
|
||||||
|
git_dir_before:
|
||||||
|
description: Contains the original path of .git directory if it's changed
|
||||||
|
returned: success
|
||||||
|
type: string
|
||||||
|
sample: /path/to/old/git/dir
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import filecmp
|
import filecmp
|
||||||
|
@ -250,6 +271,24 @@ from ansible.module_utils.six import b, string_types
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
|
|
||||||
|
def relocate_repo(module, result, repo_dir, old_repo_dir, worktree_dir):
|
||||||
|
if os.path.exists(repo_dir):
|
||||||
|
module.fail_json(msg='Separate-git-dir path %s already exists.' % repo_dir)
|
||||||
|
if worktree_dir:
|
||||||
|
dot_git_file_path = os.path.join(worktree_dir, '.git')
|
||||||
|
try:
|
||||||
|
shutil.move(old_repo_dir, repo_dir)
|
||||||
|
with open(dot_git_file_path, 'w') as dot_git_file:
|
||||||
|
dot_git_file.write('gitdir: %s' % repo_dir)
|
||||||
|
result['git_dir_before'] = old_repo_dir
|
||||||
|
result['git_dir_now'] = repo_dir
|
||||||
|
except (IOError, OSError) as err:
|
||||||
|
# if we already moved the .git dir, roll it back
|
||||||
|
if os.path.exists(repo_dir):
|
||||||
|
shutil.move(repo_dir, old_repo_dir)
|
||||||
|
module.fail_json(msg='Unable to move git dir. %s' % str(err))
|
||||||
|
|
||||||
|
|
||||||
def head_splitter(headfile, remote, module=None, fail_on_error=False):
|
def head_splitter(headfile, remote, module=None, fail_on_error=False):
|
||||||
'''Extract the head reference'''
|
'''Extract the head reference'''
|
||||||
# https://github.com/ansible/ansible-modules-core/pull/907
|
# https://github.com/ansible/ansible-modules-core/pull/907
|
||||||
|
@ -404,9 +443,8 @@ def get_submodule_versions(git_path, module, dest, version='HEAD'):
|
||||||
|
|
||||||
|
|
||||||
def clone(git_path, module, repo, dest, remote, depth, version, bare,
|
def clone(git_path, module, repo, dest, remote, depth, version, bare,
|
||||||
reference, refspec, verify_commit):
|
reference, refspec, verify_commit, separate_git_dir, result):
|
||||||
''' makes a new git repo if it does not already exist '''
|
''' makes a new git repo if it does not already exist '''
|
||||||
|
|
||||||
dest_dirname = os.path.dirname(dest)
|
dest_dirname = os.path.dirname(dest)
|
||||||
try:
|
try:
|
||||||
os.makedirs(dest_dirname)
|
os.makedirs(dest_dirname)
|
||||||
|
@ -432,8 +470,23 @@ def clone(git_path, module, repo, dest, remote, depth, version, bare,
|
||||||
"HEAD, branches, tags or in combination with refspec.")
|
"HEAD, branches, tags or in combination with refspec.")
|
||||||
if reference:
|
if reference:
|
||||||
cmd.extend(['--reference', str(reference)])
|
cmd.extend(['--reference', str(reference)])
|
||||||
|
needs_separate_git_dir_fallback = False
|
||||||
|
|
||||||
|
if separate_git_dir:
|
||||||
|
git_version_used = git_version(git_path, module)
|
||||||
|
if git_version_used is None:
|
||||||
|
module.fail_json(msg='Can not find git executable at %s' % git_path)
|
||||||
|
if git_version_used < LooseVersion('1.7.5'):
|
||||||
|
# git before 1.7.5 doesn't have separate-git-dir argument, do fallback
|
||||||
|
needs_separate_git_dir_fallback = True
|
||||||
|
else:
|
||||||
|
cmd.append('--separate-git-dir=%s' % separate_git_dir)
|
||||||
|
|
||||||
cmd.extend([repo, dest])
|
cmd.extend([repo, dest])
|
||||||
module.run_command(cmd, check_rc=True, cwd=dest_dirname)
|
module.run_command(cmd, check_rc=True, cwd=dest_dirname)
|
||||||
|
if needs_separate_git_dir_fallback:
|
||||||
|
relocate_repo(module, result, separate_git_dir, os.path.join(dest, ".git"), dest)
|
||||||
|
|
||||||
if bare and remote != 'origin':
|
if bare and remote != 'origin':
|
||||||
module.run_command([git_path, 'remote', 'add', remote, repo], check_rc=True, cwd=dest)
|
module.run_command([git_path, 'remote', 'add', remote, repo], check_rc=True, cwd=dest)
|
||||||
|
|
||||||
|
@ -972,7 +1025,9 @@ def main():
|
||||||
track_submodules=dict(default='no', type='bool'),
|
track_submodules=dict(default='no', type='bool'),
|
||||||
umask=dict(default=None, type='raw'),
|
umask=dict(default=None, type='raw'),
|
||||||
archive=dict(type='path'),
|
archive=dict(type='path'),
|
||||||
|
separate_git_dir=dict(type='path'),
|
||||||
),
|
),
|
||||||
|
mutually_exclusive=[('separate_git_dir', 'bare')],
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -993,6 +1048,7 @@ def main():
|
||||||
ssh_opts = module.params['ssh_opts']
|
ssh_opts = module.params['ssh_opts']
|
||||||
umask = module.params['umask']
|
umask = module.params['umask']
|
||||||
archive = module.params['archive']
|
archive = module.params['archive']
|
||||||
|
separate_git_dir = module.params['separate_git_dir']
|
||||||
|
|
||||||
result = dict(changed=False, warnings=list())
|
result = dict(changed=False, warnings=list())
|
||||||
|
|
||||||
|
@ -1023,6 +1079,9 @@ def main():
|
||||||
# call run_command()
|
# call run_command()
|
||||||
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||||
|
|
||||||
|
if separate_git_dir:
|
||||||
|
separate_git_dir = os.path.realpath(separate_git_dir)
|
||||||
|
|
||||||
gitconfig = None
|
gitconfig = None
|
||||||
if not dest and allow_clone:
|
if not dest and allow_clone:
|
||||||
module.fail_json(msg="the destination directory must be specified unless clone=no")
|
module.fail_json(msg="the destination directory must be specified unless clone=no")
|
||||||
|
@ -1030,6 +1089,11 @@ def main():
|
||||||
dest = os.path.abspath(dest)
|
dest = os.path.abspath(dest)
|
||||||
try:
|
try:
|
||||||
repo_path = get_repo_path(dest, bare)
|
repo_path = get_repo_path(dest, bare)
|
||||||
|
if separate_git_dir and os.path.exists(repo_path) and separate_git_dir != repo_path:
|
||||||
|
result['changed'] = True
|
||||||
|
if not module.check_mode:
|
||||||
|
relocate_repo(module, result, separate_git_dir, repo_path, dest)
|
||||||
|
repo_path = separate_git_dir
|
||||||
except (IOError, ValueError) as err:
|
except (IOError, ValueError) as err:
|
||||||
# No repo path found
|
# No repo path found
|
||||||
"""``.git`` file does not have a valid format for detached Git dir."""
|
"""``.git`` file does not have a valid format for detached Git dir."""
|
||||||
|
@ -1073,7 +1137,7 @@ def main():
|
||||||
result['diff'] = diff
|
result['diff'] = diff
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
# there's no git config, so clone
|
# there's no git config, so clone
|
||||||
clone(git_path, module, repo, dest, remote, depth, version, bare, reference, refspec, verify_commit)
|
clone(git_path, module, repo, dest, remote, depth, version, bare, reference, refspec, verify_commit, separate_git_dir, result)
|
||||||
elif not update:
|
elif not update:
|
||||||
# Just return having found a repo already in the dest path
|
# Just return having found a repo already in the dest path
|
||||||
# this does no checking that the repo is the actual repo
|
# this does no checking that the repo is the actual repo
|
||||||
|
|
|
@ -37,5 +37,3 @@
|
||||||
- include_tasks: ambiguous-ref.yml
|
- include_tasks: ambiguous-ref.yml
|
||||||
- include_tasks: archive.yml
|
- include_tasks: archive.yml
|
||||||
- include_tasks: separate-git-dir.yml
|
- include_tasks: separate-git-dir.yml
|
||||||
when:
|
|
||||||
- git_version.stdout is version("1.7.5", '>=')
|
|
||||||
|
|
|
@ -7,17 +7,85 @@
|
||||||
state: absent
|
state: absent
|
||||||
path: '{{ checkout_dir }}'
|
path: '{{ checkout_dir }}'
|
||||||
|
|
||||||
- name: create a tempdir for separate git dir
|
- name: SEPARATE-GIT-DIR | make a pre-exist repo dir
|
||||||
local_action: shell mktemp -du
|
file:
|
||||||
register: tempdir
|
state: directory
|
||||||
|
path: '{{ separate_git_dir }}'
|
||||||
|
|
||||||
- name: SEPARATE-GIT-DIR | clone with a separate git dir
|
- name: SEPARATE-GIT-DIR | clone with a separate git dir
|
||||||
command: git clone {{ repo_format1 }} {{ checkout_dir }} --separate-git-dir={{ tempdir.stdout }}
|
git:
|
||||||
|
repo: '{{ repo_format1 }}'
|
||||||
|
dest: '{{ checkout_dir }}'
|
||||||
|
separate_git_dir: '{{ separate_git_dir }}'
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | the clone will fail due to pre-exist dir
|
||||||
|
assert:
|
||||||
|
that: 'result is failed'
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | delete pre-exist dir
|
||||||
|
file:
|
||||||
|
state: absent
|
||||||
|
path: '{{ separate_git_dir }}'
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | clone again with a separate git dir
|
||||||
|
git:
|
||||||
|
repo: '{{ repo_format1 }}'
|
||||||
|
dest: '{{ checkout_dir }}'
|
||||||
|
separate_git_dir: '{{ separate_git_dir }}'
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | check the stat of git dir
|
||||||
|
stat:
|
||||||
|
path: '{{ separate_git_dir }}'
|
||||||
|
register: stat_result
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | the git dir should exist
|
||||||
|
assert:
|
||||||
|
that: 'stat_result.stat.exists == True'
|
||||||
|
|
||||||
- name: SEPARATE-GIT-DIR | update repo the usual way
|
- name: SEPARATE-GIT-DIR | update repo the usual way
|
||||||
git:
|
git:
|
||||||
repo: "{{ repo_format1 }}"
|
repo: '{{ repo_format1 }}'
|
||||||
dest: "{{ checkout_dir }}"
|
dest: '{{ checkout_dir }}'
|
||||||
|
separate_git_dir: '{{ separate_git_dir }}'
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | update should not fail
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is not failed
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | move the git dir to new place
|
||||||
|
git:
|
||||||
|
repo: '{{ repo_format1 }}'
|
||||||
|
dest: '{{ checkout_dir }}'
|
||||||
|
separate_git_dir: '{{ separate_git_dir }}_new'
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | the movement should not failed
|
||||||
|
assert:
|
||||||
|
that: 'result is not failed'
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | check the stat of new git dir
|
||||||
|
stat:
|
||||||
|
path: '{{ separate_git_dir }}_new'
|
||||||
|
register: stat_result
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | the new git dir should exist
|
||||||
|
assert:
|
||||||
|
that: 'stat_result.stat.exists == True'
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | test the update
|
||||||
|
git:
|
||||||
|
repo: '{{ repo_format1 }}'
|
||||||
|
dest: '{{ checkout_dir }}'
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: SEPARATE-GIT-DIR | the update should not failed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is not failed
|
||||||
|
|
||||||
- name: SEPARATE-GIT-DIR | set git dir to non-existent dir
|
- name: SEPARATE-GIT-DIR | set git dir to non-existent dir
|
||||||
shell: "echo gitdir: /dev/null/non-existent-dir > .git"
|
shell: "echo gitdir: /dev/null/non-existent-dir > .git"
|
||||||
|
@ -56,7 +124,7 @@
|
||||||
- name: SEPARATE-GIT-DIR | clear separate git dir
|
- name: SEPARATE-GIT-DIR | clear separate git dir
|
||||||
file:
|
file:
|
||||||
state: absent
|
state: absent
|
||||||
path: "{{ tempdir.stdout }}"
|
path: "{{ separate_git_dir }}_new"
|
||||||
|
|
||||||
- name: SEPARATE-GIT-DIR | clear checkout_dir
|
- name: SEPARATE-GIT-DIR | clear checkout_dir
|
||||||
file:
|
file:
|
||||||
|
|
|
@ -11,6 +11,7 @@ git_archive_extensions:
|
||||||
|
|
||||||
checkout_dir: '{{ output_dir }}/git'
|
checkout_dir: '{{ output_dir }}/git'
|
||||||
repo_dir: '{{ output_dir }}/local_repos'
|
repo_dir: '{{ output_dir }}/local_repos'
|
||||||
|
separate_git_dir: '{{ output_dir }}/sep_git_dir'
|
||||||
repo_format1: 'https://github.com/jimi-c/test_role'
|
repo_format1: 'https://github.com/jimi-c/test_role'
|
||||||
repo_format2: 'git@github.com:jimi-c/test_role.git'
|
repo_format2: 'git@github.com:jimi-c/test_role.git'
|
||||||
repo_format3: 'ssh://git@github.com/jimi-c/test_role.git'
|
repo_format3: 'ssh://git@github.com/jimi-c/test_role.git'
|
||||||
|
|
Loading…
Reference in a new issue