Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subversion #922

Merged
merged 6 commits into from
Aug 24, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 18 additions & 4 deletions library/git
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,23 @@ def clone(repo, dest):
rc = cmd.returncode
return (rc, out, err)

def reset(dest):

def has_local_mods(dest):
os.chdir(dest)
cmd = "git status -s"
lines = os.popen(cmd).read().splitlines()
lines = filter(lambda c: re.search('^\\?\\?.*$',c) == None,lines)
return len(lines) > 0

def reset(module,dest,force):
'''
Resets the index and working tree to HEAD.
Discards any changes to tracked files in working
tree since that commit.
'''
os.chdir(dest)
if not force and has_local_mods(dest):
module.fail_json(msg="Local modifications exist in repository (force=no).")
cmd = "git reset --hard HEAD"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
Expand Down Expand Up @@ -140,14 +150,16 @@ def main():
dest=dict(required=True),
repo=dict(required=True, aliases=['name']),
version=dict(default='HEAD'),
remote=dict(default='origin')
remote=dict(default='origin'),
force=dict(default='yes', choices=['yes', 'no'], aliases=['force'])
)
)

dest = module.params['dest']
repo = module.params['repo']
version = module.params['version']
remote = module.params['remote']
force = module.boolean(module.params['force'])

gitconfig = os.path.join(dest, '.git', 'config')

Expand All @@ -156,14 +168,16 @@ def main():
# if there is no git configuration, do a clone operation
# else pull and switch the version
before = None
local_mods = False
if not os.path.exists(gitconfig):
(rc, out, err) = clone(repo, dest)
if rc != 0:
module.fail_json(msg=err)
else:
# else do a pull
local_mods = has_local_mods(dest)
before = get_version(dest)
(rc, out, err) = reset(dest)
(rc, out, err) = reset(module,dest,force)
if rc != 0:
module.fail_json(msg=err)
(rc, out, err) = pull(module, repo, dest, version)
Expand All @@ -182,7 +196,7 @@ def main():
after = get_version(dest)
changed = False

if before != after:
if before != after or local_mods:
changed = True

module.exit_json(changed=changed, before=before, after=after)
Expand Down
152 changes: 152 additions & 0 deletions library/subversion
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.

# I wanted to keep this simple at first, so for now this checks out
# from the given branch of a repo at a particular SHA or
# tag. Latest is not supported, you should not be doing
# that. Contribs welcome! -- MPD

# requires subversion and grep on the client.

import re

def get_version(dest):
''' samples the version of the git repo '''
os.chdir(dest)
cmd = "svn info"
revision = filter(lambda l: re.search('Revision',l) != None,os.popen(cmd).read().splitlines())
url = filter(lambda l: re.search('^URL',l) != None,os.popen(cmd).read().splitlines())
return [revision[0],url[0]]

def checkout(repo, dest):
''' makes a new svn repo if it does not already exist '''
cmd = "svn co %s %s" % (repo, dest)
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)

def switch(repo, dest):
''' makes a new svn repo if it does not already exist '''
cmd = "svn sw %s %s" % (repo, dest)
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)

def has_local_mods(dest):
os.chdir(dest)
cmd = "svn status"
lines = os.popen(cmd).read().splitlines()
filtered = filter(lambda c: re.search('^\\?.*$',c) == None,lines)
return len(filtered) > 0

def reset(dest,force):
'''
Reset the repo:
force: if true, then remove any local modifications. Else, fail if there are local modifications
'''
if has_local_mods(dest):
if force:
cmd = "svn revert -R ."
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)
else:
return (-1,"ERROR: modified files exist in the repository.","")
return (0,"","")

def update(module, dest, version):
''' update an existing svn repo '''
os.chdir(dest)
cmd = ''
if version != 'HEAD':
cmd = "svn up -r %s" % version
else:
cmd = "svn up"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)

# ===========================================

def main():
module = AnsibleModule(
argument_spec = dict(
dest=dict(required=True),
repo=dict(required=True, aliases=['name']),
revision=dict(default='HEAD'),
force=dict(default='yes', choices=['yes', 'no'], aliases=['force'])
)
)

dest = module.params['dest']
repo = module.params['repo']
revision = module.params['revision']
force = module.boolean(module.params['force'])

rc, out, err, status = (0, None, None, None)

# if there is no .svn folder, do a checkout
# else update.
before = None
local_mods = False
if not os.path.exists("%s/.svn" % (dest)):
if os.path.exists(dest):
module.fail_json(msg="%s folder already exists, but its not a subversion repository." % (dest))
else:
(rc, out, err) = checkout(repo, dest)
if rc != 0:
module.fail_json(msg=err)
else:
local_mods = has_local_mods(dest)
# else do an update
before = get_version(dest)
(rc, out, err) = reset(dest,force)
if rc != 0:
module.fail_json(msg=err)
(rc, out, err) = switch(repo, dest)
if rc != 0:
module.fail_json(msg=err)

# handle errors from switch or pull
if err.find('ERROR') != -1:
module.fail_json(msg=err)

# switch to version specified regardless of whether
# we cloned or pulled
(rc, out, err) = update(module, dest, revision)
if rc != 0:
module.fail_json(msg=err)

# determine if we changed anything
after = get_version(dest)
changed = False

if before != after or local_mods:
changed = True

module.exit_json(changed=changed, before=before, after=after)

# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()
81 changes: 79 additions & 2 deletions test/TestRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,85 @@ def test_command(self):
assert 'failed' not in result
assert result['rc'] == 0

def test_git(self):
self._run('file',['path=/tmp/gitdemo','state=absent'])
self._run('file',['path=/tmp/gd','state=absent'])
self._run('command',['git init gitdemo', 'chdir=/tmp'])
self._run('command',['touch a', 'chdir=/tmp/gitdemo'])
self._run('command',['git add *', 'chdir=/tmp/gitdemo'])
self._run('command',['git commit -m "test commit 2"', 'chdir=/tmp/gitdemo'])
self._run('command',['touch b', 'chdir=/tmp/gitdemo'])
self._run('command',['git add *', 'chdir=/tmp/gitdemo'])
self._run('command',['git commit -m "test commit 2"', 'chdir=/tmp/gitdemo'])
result = self._run('git', [ "repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd" ])
assert result['changed']
# test the force option not set
self._run('file',['path=/tmp/gd/a','state=absent'])
result = self._run('git', [ "repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd", "force=no" ])
assert result['failed']
# test the force option when set
result = self._run('git', [ "repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd", "force=yes" ])
assert result['changed']

def test_subversion(self):
# TODO make an svn repo locally so as to avoid tests failing on network calls
self._run('file',['path=/tmp/meetings','state=absent'])
# hacking/test-module -m library/subversion
result = self._run('subversion', [ ])
assert result['failed']
assert "dest" in result['msg']
assert "repo" in result['msg']
# hacking/test-module -m library/subversion -a "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\""
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"" ])
assert result['failed']
assert "dest" in result['msg']
# hacking/test-module -m library/subversion -a "dest=\"/tmp/gnconf\""
result = self._run('subversion', [ "dest=\"/tmp/gnconf\"" ])
assert result['failed']
assert "repo" in result['msg']
# when /tmp/meetings doesn't exist:
# hacking/test-module -m library/subversion -a "repo=\"repo\" dest=\"/tmp/gnconf\""
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"" ])
assert result['changed']
# when /tmp/meetings exists, but nothing has changed.
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"" ])
assert not result['changed']
# when /tmp/meetings is a folder, but its not an svn repo
self._run('file',['path=/tmp/meetings','state=absent'])
self._run('file',['path=/tmp/meetings','state=directory'])
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"" ])
assert result['failed']
# when /tmp/meetings is a file
self._run('file',['path=/tmp/meetings','state=absent'])
self._run('command',['touch /tmp/meetings'])
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"" ])
assert result['failed']
# when /tmp/meetings is a folder, but its a different svn URL - should automatically switch
self._run('file',['path=/tmp/meetings','state=absent'])
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/api-errata\"","dest=\"/tmp/meetings\"" ])
assert result['changed']
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"" ])
assert result['changed']
assert result['after'][1] == 'URL: http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings'
# when /tmp/meetings is a folder, when its an older revision it should update
self._run('command',['svn up -r926415','chdir=/tmp/meetings'])
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"" ])
assert result['changed']
assert result['before'][0] == 'Revision: 926415'
assert result['after'][0] != 'Revision: 926415'
# when /tmp/meetings has dirty files in it, ignore them:
self._run('command',['touch /tmp/meetings/adirtyfile'])
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"" ])
assert not result['changed'] # no changes to the repo
# when /tmp/meetings has modified file in it, fail:
self._run('file',['path=/tmp/meetings/adirtyfile','state=absent'])
self._run('command',['cp /tmp/meetings/berlin-11-agenda /tmp/meetings/svn-vision-agenda'])
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"","force=no" ])
assert result['failed']
# when /tmp/meetings has a modified file but force is set to yes, then just override it.
result = self._run('subversion', [ "repo=\"http://svn.apache.org/repos/asf/subversion/trunk/notes/meetings\"","dest=\"/tmp/meetings\"","force=yes" ])
assert result['changed'] # no changes to the repo

def test_large_output(self):
large_path = "/usr/share/dict/words"
if not os.path.exists(large_path):
Expand Down Expand Up @@ -198,5 +277,3 @@ def test_assemble(self):
"dest=%s" % output,
])
assert result['changed'] == False