Skip to content

Instantly share code, notes, and snippets.

@RogerGee
Last active March 22, 2016 22:51
Show Gist options
  • Save RogerGee/daf8304f098d9bba6125 to your computer and use it in GitHub Desktop.
Save RogerGee/daf8304f098d9bba6125 to your computer and use it in GitHub Desktop.
A git post-receive hook to deploy from git repositories
#!/usr/bin/env python
# unpacker.py
# Author: Roger Gee <rpg11a@acu.edu>
# this script unpacks the lastest commit(s) on master and scans them
# for syncing; the script checks a file called '.unpack' which
# contains key/value pairs describing how to unpack the repository or
# any others into the filesystem; symlink this script as
# hooks/post-receive in the bare repository on your remote
import os
import re
import sys
import subprocess
# globals
REGEX = re.compile("^(.+?)=(.+)$")
BRANCH = "master"
LOCALFILE = "unpack.config"
CONFIGFILE = ".unpack"
localConfig = {
}
foreignConfig = {
'entries': []
}
class GitRepo:
def __init__(self,url,branch,**kwargs):
self.url = url
self.branch = branch
if 'workcopy' in kwargs and os.path.isdir(kwargs['workcopy']):
self.localCopy = kwargs['workcopy']
else:
pre = []
for k in ['TEMPDIR','TMPDIR','TMP']:
if k in os.environ:
pre.append(os.environ[k])
if len(pre) == 0:
pre.append('/tmp')
self.localCopy = pre[0] + '/git-unpack'
if not os.path.exists(self.localCopy):
os.makedirs(self.localCopy)
m = self.git_command("clone --branch {} {}".format(branch,url),"Cloning into '(.+?)'")
self.localCopy += '/' + m.group(1)
def get_latest_rev(self):
return self.git_command("rev-parse {}".format(self.branch)).strip()
def load_foreign_config(self):
change_dir(self.localCopy)
load_config(CONFIGFILE,update_foreign_config,True)
change_dir()
def sync_trees(self,tree,dst):
change_dir(self.localCopy)
if tree[len(tree)-1] != '/':
tree += '/' # rsync requires this
subprocess.check_call(['rsync','--exclude=.git/','--exclude=.unpack','-rvu','--chmod=ugo=rwX',tree,dst])
change_dir()
def git_command(self,cmdline,regex=""):
# change working directories and run 'git'; if it returns
# non-zero the function will throw an exception
change_dir(self.localCopy)
o = subprocess.check_output(['git']+cmdline.split(),stderr=subprocess.STDOUT)
change_dir()
if regex == "":
return o
lines = o.split("\n")
rx = re.compile(regex)
for line in lines:
m = rx.match(line)
if not m is None:
return m
raise Exception('git_command: regex did not match any output lines')
def change_dir(dirto=None):
global oldCurrentDir
if not dirto is None:
oldCurrentDir = os.getcwd()
os.chdir(dirto)
elif not oldCurrentDir is None:
os.chdir(oldCurrentDir)
oldCurrentDir = None
def clean_exit(msg):
sys.stdout.write(os.path.basename(sys.argv[0])+": "+msg+"\n")
sys.exit(0)
def warn(msg):
sys.stderr.write(os.path.basename(sys.argv[0])+": warning: "+msg+"\n")
sys.exit(1)
def fatal(msg):
sys.stderr.write(os.path.basename(sys.argv[0])+": error: "+msg+"\n")
sys.exit(1)
# update configuration values for the global local configuration
def update_local_config(key,value):
global localConfig
ks = map(str.strip,key.split('.'))
i = 0
thing = localConfig
while i < len(ks)-1:
if not ks[i] in thing:
thing[ks[i]] = {}
thing = ks[i]
i += 1
if ks[i] in thing:
fatal("local config property is incorrect: '"+ks[i]+"'")
thing[ks[i]] = value
# update and validate a specific configuration value for the global
# foreign configuration
def update_foreign_config(key,value):
global foreignConfig
if key == "entry":
foreignConfig['entries'].append( value.split(':') )
else:
fatal("bad config key '"+key+"'")
# processes all unpacks specified in the unpacks file by updating the
# global configuration with their values
def load_config(unpackFile,fn,fatalFail):
try:
with open(unpackFile) as f:
while True:
line = f.readline()
if len(line) > 0:
m = REGEX.match(line)
if not m is None:
fn(*map(str.strip,m.groups()))
else:
break
except IOError as e:
if fatalFail:
raise Exception("no config file '"+unpackFile+"' found in repository")
def save_config(configFile,config):
with open(configFile,'w') as f:
for (k,v) in config.iteritems():
f.write(k+"="+v+"\n")
if 'GIT_DIR' in os.environ:
del os.environ['GIT_DIR']
try:
load_config(LOCALFILE,update_local_config,False)
if 'workcopy' in localConfig:
repo = GitRepo(os.getcwd(),BRANCH,workcopy=localConfig['workcopy'])
repo.git_command("pull")
rev = repo.get_latest_rev()
if 'latest-rev' in localConfig and rev == localConfig['latest-rev']:
clean_exit("no new revision available: stopping")
localConfig['latest-rev'] = rev
else:
repo = GitRepo(os.getcwd(),BRANCH)
localConfig['latest-rev'] = repo.get_latest_rev()
localConfig['workcopy'] = repo.localCopy # this could have changed
repo.load_foreign_config()
if len(foreignConfig['entries']) == 0:
raise Exception("no entries were specified in .unpack file")
for (s,d) in foreignConfig['entries']:
repo.sync_trees(s,d)
change_dir()
save_config(LOCALFILE,localConfig)
except Exception as e:
change_dir()
save_config(LOCALFILE,localConfig)
fatal(str(e))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment