Skip to content

Instantly share code, notes, and snippets.

@jbzdak
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbzdak/904e07a94ad3b71bd8bc to your computer and use it in GitHub Desktop.
Save jbzdak/904e07a94ad3b71bd8bc to your computer and use it in GitHub Desktop.
Random collection of git hooks
#!/usr/bin/env python
from __future__ import unicode_literals, print_function
import os, sys, logging, re, codecs
logging.basicConfig(level=logging.ERROR)
MIN_COMMIT_LENGTH = 15
REQUIRED_REGEXPS = [
'KK-\d+'
]
def main():
file_path = sys.argv[1]
with codecs.open(file_path, encoding="utf-8") as f:
lines = map(lambda x: x.strip(), f)
non_comment_lines = filter(lambda x: x and x[0] != '#', lines)
commit = "".join(non_comment_lines)
if MIN_COMMIT_LENGTH and len(commit) < MIN_COMMIT_LENGTH:
logging.error("Commit message to short")
sys.exit(1)
if REQUIRED_REGEXPS:
for regexp in REQUIRED_REGEXPS:
if not re.findall(regexp, commit):
logging.error("Commit should contain pattern %s", regexp)
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python
from __future__ import unicode_literals, print_function
import os, sys, subprocess, logging
logging.basicConfig(level=logging.DEBUG)
REMOTES_TO_UPDATE = ['origin']
AUTOMERGE_SUFFIX = "-automerge"
def branch_mapper(remote, local_branch):
if remote not in REMOTES_TO_UPDATE:
return None
return local_branch + AUTOMERGE_SUFFIX
BRANCH_MAPPING = branch_mapper
def get_checked_out_branch():
"""
:return: String with currently checked out branch
"""
return subprocess.check_output("git rev-parse --symbolic-full-name --abbrev-ref HEAD".split()).strip()
def get_list_of_remotes():
return map(str.strip, subprocess.check_output("git remote".split()).split())
def main():
current_branch = get_checked_out_branch()
remotes = get_list_of_remotes()
logging.debug("Remotes %s", remotes)
for r in remotes:
push_to_branch = BRANCH_MAPPING(r, current_branch)
logging.debug("%s", (current_branch, r, push_to_branch))
if push_to_branch is None:
continue
subprocess.check_call(['git', 'push', r, '{}:{}'.format(current_branch,push_to_branch)])
if __name__ == "__main__":
main()
#!/usr/bin/env python
"""
Description
-----------
Update hook that automatically merges changes inside the repository.
It's creation was sparked by
`my question on SO <http://stackoverflow.com/q/25207942/7918>`_.
Especially by `this answer <http://stackoverflow.com/a/25209198/7918>`_.
Logic is as follows:
--------------------
If repository recleives a push we will merge pushed branch to curren branch if
following conditions are met:
* This repo is not bare. It is bare **push will fail**
* pushed branch has a special suffix (by default ``-automerge``). If
pushed branch has no suffix push will not be aborted.
* Currently checked out branch is named as branch we push to without suffix.
so if push was to ``foo-automerge``, we will merge iff checked out branch
is ``foo``. If other branch is checked out push will not be aborted.
* Working directory is clean. If working directory is dirty (and previous
condition are met) **push will fail**.
* We can guess working copy directory. If not can't **push will fail**.
* Merge is fast-forward if not **push will fail**.
Installation:
-------------
Copy this file to .git/hooks, and link it to **both**, ``update``
and ``pre-update``. You need to add it to both hooks because ``pre-update`` is
needed to fail push when error conditions are met, and ``update`` is needed
to actually do merge.
Add executable permissions to both hooks.
"""
from __future__ import unicode_literals, print_function
import os, sys, subprocess, logging
logging.basicConfig(level=logging.INFO)
"""
Set do ``DEBUG`` for debug info, or to ``ERROR`` to get error conditions only.
"""
AUTOMERGE_SUFFIX = "-automerge"
FORCE_WORKING_DIR = None
"""
You may override working copy by setting this to a path
"""
CAN_GUESS_GIT_WORK_TREE = True
"""
If false we will not quess working tree directory, so either set ``GIT_WORK_TREE``
envvar or ``FORCE_WORKING_DIR`` python variable.
"""
def get_working_copy_dir():
"""
Tries to guess and returns working copy directory.
"""
if FORCE_WORKING_DIR:
return FORCE_WORKING_DIR
working_copy = os.environ.get("GIT_WORK_TREE", None)
if working_copy is not None:
return working_copy
if not CAN_GUESS_GIT_WORK_TREE:
logging.error("Can't quess working copy dir and 'GIT_WORK_TREE' was "
"not set")
sys.exit(1)
path = os.path.abspath(os.environ['GIT_DIR'])
working_copy, git_dir = os.path.split(path)
git_dir = git_dir.strip()
if git_dir != ".git":
logging.error("Can't guess working copy dir, because GIT_DIR does not "
"point to directory named '.git'")
sys.exit(1)
return working_copy
def strip_branch(branch):
"""
Removes git decorations from branch name.
>>> strip_branch("refs/heads/feature/master-automerge")
'feature/master-automerge'
>>> strip_branch("refs/heads/master-automerge")
'master-automerge'
>>> strip_branch('refs/heads/master-automerge ')
'master-automerge'
"""
return "/".join(branch.split("/")[2:]).strip()
def checked_out_branch_is_valid(pushed_branch):
"""
Check whether checked out branch mathes branch we are pushing to.
"""
pushed_branch_sans_automerge = pushed_branch[:-len(AUTOMERGE_SUFFIX)]
checked_out_branch = get_checked_out_branch()
if checked_out_branch != pushed_branch_sans_automerge:
logging.info("Other branch is checked out, will not merge working copy")
sys.exit(0)
def is_this_repo_bare():
"""
:return: True if this repository is bare, False otherwise.
"""
result = subprocess.check_output("git rev-parse --is-bare-repository".split()).strip()
if result == "true":
return True
if result == "false":
return False
raise ValueError("Can't guess whether this repository is bare")
def git_subprocess(args):
"""
Utility function to call git process in the working copy directory.
:param list args: list of string containing command to call
"""
new_cwd = get_working_copy_dir()
logging.debug("Working copy dir %s", new_cwd)
new_env = dict(os.environ)
new_env["GIT_DIR"] = os.path.abspath(os.environ['GIT_DIR'])
return subprocess.check_output(args, cwd=new_cwd, env=new_env)
def get_checked_out_branch():
"""
:return: String with currently checked out branch
"""
return git_subprocess("git rev-parse --symbolic-full-name --abbrev-ref HEAD".split()).strip()
def check_working_directory_clean():
"""
This is adapted from here: http://stackoverflow.com/a/3879077/7918
Will exit id working copy is dirty.
"""
try:
# Update the index
git_subprocess("git update-index -q --ignore-submodules --refresh".split())
# Disallow unstaged changes in the working tree
git_subprocess("git diff-files --quiet --ignore-submodules --".split())
# Disallow uncommitted changes in the index
git_subprocess("git diff-index --cached --quiet HEAD --ignore-submodules --".split())
except subprocess.CalledProcessError:
logging.error("Working directory here is not clean. Will not merge")
sys.exit(1)
def validate(pushed_branch):
"""
If we can't merge ``pushed_branch`` to current branch this function will
print error and call ``sys.exit`` with apropriate exit code.
"""
if is_this_repo_bare():
logging.error("Sorry this hook will not work on bare repository")
sys.exit(1)
logging.debug("Updating branch %s", pushed_branch)
if not pushed_branch.endswith(AUTOMERGE_SUFFIX):
logging.debug("Branch has no automerge suffix, will not automatically merge")
sys.exit(0)
checked_out_branch_is_valid(pushed_branch)
check_working_directory_clean()
logging.debug("OK to merge")
def update_hook(pushed_branch):
validate(pushed_branch)
def post_update_hook(pushed_branch):
validate(pushed_branch)
try:
git_subprocess(['git', 'merge', '--ff-only', pushed_branch])
except subprocess.CalledProcessError:
logging.error("Couldn't merge --- merge was not fast forward")
sys.exit(1)
def main():
script_name = sys.argv[0]
pushed_branch = sys.argv[1]
pushed_branch = strip_branch(pushed_branch)
if script_name.endswith('/update'):
update_hook(pushed_branch)
if script_name.endswith('/post-update'):
post_update_hook(pushed_branch)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment