CastorisCausa

Personal blog and hacks by Raymundo Cassani

Git hook at changing branches


Updated: The post-checkout hook the hook code below is intended to run only when changing branches, but it is invoked also when retrieving files from the index and rebasing. Thus these two cases need to be addressed in the code. See more details below.


Often when working on experimental Git branches, it is necessary to modify files outside of the Git repository when we change branches to properly test some of the new features. An example of this type of files would be configuration files.

Through the use of Git hooks it is possible to execute a program when a Git action happens, for example when checkout occurs. However, the post-checkout hook is given the SHA-1 ref of the old (origin) and new (destination) branches, rather than their name. Thus if two branches share the same HEAD, it's not possible to distinguish the correct branch only from the ref. A walk-around is to use git reflog to get the names of the old and new branches.


Updated: Note that the post-checkout hook will be invoked when retrieving a file from the index, thus if checkoutType == 0 the hooks returns before doing its action. The hook is also invoked by git rebase. For example, being in the new branch and rebasing it to old, this is to say git rebase old, will invoke the hook, the name of the old branch will be correctly identified by git reflog, but the new branch name will be empty. Thus if any of the branches is empty the hook exits without performing its action.


This Bash script (available here) is a post-checkout hook example to do "something" if the new branch name is in a give list, function exists_in_list(). Moreover, it checks if the old or new branch names have ftr- as prefix, and takes does something for the different combinations, function starts_with().

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
bash
#!/bin/bash

set -e  # Stop execution of script if a command or pipeline has an error
printf '\nExample post-checkout hook\n'

# These are the parameters given to the hook
# https://git-scm.com/docs/githooks.html#_post_checkout
oldHEAD=$1       # ref of the previous (old) HEAD
newHEAD=$2       # ref of the new HEAD 
checkoutType=$3  # checkout type flag: 1 = changing branches, 
                 #                     0 = retrieving a file from the index

# If checkoutType is 0, exit
if [[ $checkoutType -eq 0 ]]; then
    exit 0
fi

# Since $1 and $2 are the 40-character SHA-1,
# it is not possible to unambiguously determine old- and new branch names
# [[ $checkoutType == 1 ]] && checkoutType='branch' ||  checkoutType='file' ;
# echo 'Checkout type: '$checkoutType
# echo '     old HEAD: '`git name-rev --name-only $oldHEAD`
# echo '     new HEAD: '`git name-rev --name-only $newHEAD`
# https://stackoverflow.com/q/25590267/4859684

# Get branch names from git-reflog
oldBRANCH=`git reflog | awk 'NR==1{ print $6; exit }'`
newBRANCH=`git reflog | awk 'NR==1{ print $8; exit }'`
echo "Old branch: $oldBRANCH"
echo "New branch: $newBRANCH"
echo ""

# If any of the branches is empty, it's rebase
if [ -z "$oldBRANCH" ] || [ -z "$newBRANCH" ]; then
    exit 0
fi

exists_in_list() {
# Function for determine if a word is in a space-separated list
# https://www.baeldung.com/linux/check-variable-exists-in-list
    LIST=$1
    WORD=$2
    if [[ "$LIST" =~ (" "|^)$WORD(" "|$) ]]; then
        echo 1
    else
        echo 0
    fi
}

starts_with(){
# Function to check if a word start with a given prefix
    WORD=$1
    PREF=$2
    if [[ $WORD =~ ^$PREF* ]]; then
        echo 1
    else
        echo 0
    fi
}

# ACTION IF NEW BRANCH IS IN A GIVEN LIST
# Determine if NEW branch name belongs to a list of names
listBranches="dev tmp test"
isNewList="$(exists_in_list "$listBranches" "$newBRANCH")"
# NEW branch was in list
if [[ $isNewList -eq 1 ]]; then
    printf "New branch: $newBRANCH was in list\n"
    printf ' Do something 0\n'
    exit 0
fi

# ACTION ACCORDING THE NAMES OF OLD TO NEW BRANCH
# Determine if OLD and NEW branch names have a given prefix
isOldFtr="$(starts_with "$oldBRANCH" "ftr-")"
isNewFtr="$(starts_with "$newBRANCH" "ftr-")"

# If any of the branches is empty, it's rebase
if [ -z "$oldBRANCH" ] || [ -z "$newBRANCH" ]; then
    exit 0
fi

# NoFeature branch to Feature branch
if [[ $isOldFtr -eq 0 ]] && [[ $isNewFtr -eq 1 ]]; then
    printf "Branch switch: $oldBRANCH -> $newBRANCH\n"
    printf ' Do something 1\n'
    exit 0
fi

# Feature branch to NoFeature branch
if [[ $isOldFtr -eq 1 ]] && [[ $isNewFtr -eq 0 ]]; then
    printf "Branch switch: $oldBRANCH -> $newBRANCH\n"
    printf ' Do something 2\n'
    exit 0
fi

# No change in type of branch
if ([[ $isOldFtr -eq 0 ]] && [[ $isNewFtr -eq 0 ]]) || ([[ $isOldFtr -eq 1 ]] && [[ $isNewFtr -eq 1 ]]); then
    printf "Branch switch: $oldBRANCH -> $newBRANCH\n"
    printf ' Do something 3\n'
    exit 0
fi

exit 0

Comments

comments powered by Disqus