Last active
March 22, 2016 22:51
-
-
Save RogerGee/daf8304f098d9bba6125 to your computer and use it in GitHub Desktop.
A git post-receive hook to deploy from git repositories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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