git 5.91 KB
Newer Older
1 2
#!/usr/bin/python

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# (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/>.

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

try:
    import json
except ImportError:
    import simplejson as json
import os
30
import re
31 32 33
import sys
import shlex
import subprocess
34
import syslog
35 36

# ===========================================
37 38 39 40 41 42 43 44 45 46 47
# Basic support methods

def exit_json(rc=0, **kwargs):
   print json.dumps(kwargs)
   sys.exit(rc)

def fail_json(**kwargs):
   kwargs['failed'] = True
   exit_json(rc=1, **kwargs)

# ===========================================
48
# convert arguments of form a=b c=d
49 50 51
# to a dictionary
# FIXME: make more idiomatic

52
if len(sys.argv) == 1:
53
   fail_json(msg="the command module requires arguments (-a)")
54 55 56

argfile = sys.argv[1]
if not os.path.exists(argfile):
57
   fail_json(msg="Argument file not found")
58 59

args = open(argfile, 'r').read()
60
items = shlex.split(args)
61 62
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args)
63 64

if not len(items):
65
   fail_json(msg="the command module requires arguments (-a)")
66

67 68 69 70 71 72 73
params = {}
for x in items:
    (k, v) = x.split("=")
    params[k] = v

dest = params['dest']
repo = params['repo']
74
branch  = params.get('branch', 'master')
75
version = params.get('version', 'HEAD')
76 77 78 79 80 81 82 83 84 85 86

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

def get_version(dest):
   ''' samples the version of the git repo '''
   os.chdir(dest)
   cmd = "git show --abbrev-commit"
   sha = os.popen(cmd).read().split("\n")
   sha = sha[0].split()[1]
   return sha

87
def clone(repo, dest, branch):
88 89
   ''' makes a new git repo if it does not already exist '''
   try:
90
       os.makedirs(os.path.dirname(dest))
91 92 93 94
   except:
       pass
   cmd = "git clone %s %s" % (repo, dest)
   cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
95 96 97 98 99 100 101 102 103
   (out, err) = cmd.communicate()
   rc = cmd.returncode
   
   if branch is None or rc != 0: 
     return (out, err)

   os.chdir(dest)
   cmd = "git checkout -b %s origin/%s" % (branch, branch) 
   cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
104 105
   return cmd.communicate()

106 107 108 109 110 111 112 113 114 115 116 117 118
def reset(dest):
   '''
   Resets the index and working tree to HEAD.
   Discards any changes to tracked files in working
   tree since that commit.
   '''
   os.chdir(dest)
   cmd = "git reset --hard HEAD"
   cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   (out, err) = cmd.communicate()
   rc = cmd.returncode
   return (rc, out, err)

119 120 121 122 123 124
def switchLocalBranch( branch ):
   cmd = "git checkout %s" % branch
   cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   return cmd.communicate()

def pull(repo, dest, branch):
125 126
   ''' updates repo from remote sources '''
   os.chdir(dest)
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
   cmd = "git branch -a"
   cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   (gbranch_out, gbranch_err) = cmd.communicate()

   try:
      m = re.search( '^\* (\S+|\(no branch\))$', gbranch_out, flags=re.M )
      cur_branch = m.group(1)
      m = re.search( '\s+remotes/origin/HEAD -> origin/(\S+)', gbranch_out, flags=re.M )
      default_branch = m.group(1)
   except:
      fail_json(msg="could not determine branch data - received: %s" % gbranch_out)

   if branch is None:
      if cur_branch != default_branch:
         (out, err) = switchLocalBranch( default_branch )

      cmd = "git pull -u origin"

   elif branch == cur_branch:
      cmd = "git pull -u origin"

   else:
      m = re.search( '^\s+%s$' % branch, gbranch_out, flags=re.M ) #see if we've already checked it out
      if m is None:
Stephen Fromm committed
151
         cmd = "git checkout --track -b %s origin/%s" % (branch, branch)
152 153 154 155

      else:
         cmd = "git pull -u origin"

156 157 158
   cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   return cmd.communicate()

159
def switchver(version, dest):
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
   ''' once pulled, switch to a particular SHA or tag '''
   os.chdir(dest)
   if version != 'HEAD':
      cmd = "git checkout %s --force" % version
   else:
      # is there a better way to do this?
      cmd = "git rebase origin"
   cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   (out, err) = cmd.communicate()
   return (out, err)
 

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

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

# if there is no git configuration, do a clone operation
# else pull and switch the version

before = None
if not os.path.exists(gitconfig):
181
   (out, err) = clone(repo, dest, branch)
182 183 184
else:
   # else do a pull   
   before = get_version(dest)
185 186 187
   (rc, out, err) = reset(dest)
   if rc != 0:
      fail_json(out=out, err=err)
188
   (out, err) = pull(repo, dest, branch)
189 190 191

# handle errors from clone or pull

192
if out.find('error') != -1 or err.find('ERROR') != -1:
193
   fail_json(out=out, err=err)
194 195 196 197

# switch to version specified regardless of whether
# we cloned or pulled

198
(out, err) = switchver(version, dest)
199
if err.find('error') != -1:
200
   fail_json(out=out, err=err)
201 202 203 204 205 206 207 208 209

# determine if we changed anything

after = get_version(dest)
changed = False

if before != after:
   changed = True

210
exit_json(changed=changed, before=before, after=after)