Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

GitHub.com: GIT Directory-Based Configs

About

So, you are a proud OpenSource developer and work for a company using GitHub.com for GIT repository hosting?

While SSH auth to GitHub.com is based on public keys and it automatically chooses the right account you have to take care when you quickly clone a repository, change something and whoops your company has your private email address or hacker pseudo within their repository.

The scenario we are speaking about looks like following:

$HOME/
  Projects/

    acme/            ← use acme.config here with email="user@acme.com", name="John Barleycorn"
      ui
      backend
      infra      

    foss/            ← use foss.config here with email="user@foss.org", name="Acid Burn"
      bitcoin-miner
      dotfiles 
      fancy-themes

Here we are using GIT’s global hooks and include.path to setup repository-specific configs.

This guide is aimed at Linux users but should be able to be used on Unix (OSX) or Windows as well.

Setup

Default Config Path

We're using a temporary config path here following FreeDesktop specifications. This is just used for the copy-and-paste in this tutorial and should not be put somewhere in a profile.

CONFIG_PATH="$HOME/.config/git" 

Setup Example Configs

mkdir -p $CONFIG_PATH/config
echo -e "[user]\nemail=user@fsf.org" > $CONFIG_PATH/foss.config
echo -e "[user]\nemail=user@acme.com" > $CONFIG_PATH/acme.config

Setup global hooks directory

mkdir -p $CONFIG_PATH/hooks
git config --global --replace-all core.hooksPath $CONFIG_PATH/hooks

Create Pre Commit Hook

Create $CONFIG_PATH/hooks/pre-commit and don’t forget to chmod a+x $CONFIG_PATH/hooks/pre-commit!

The lines with acme and foss will do the matching. Customize for the companies you're working for! Also keep in mind that include.path might get overwritten and if no match takes please git will use the global config!

#!/bin/bash

# Config directory
CONFIG_PATH="$( cd "$(dirname ${BASH_SOURCE[-1]})/.." && pwd)"

# default config
GIT_INCLUDE_CONFIG="" 

# Git Hooks documentation
# https://git-scm.com/docs/githooks

# If we have a local commit hook, execute it
localHook="${PWD}/.git/hooks/$(basename "${0}")"
if [[ -e  "${localHook}" ]]; then
	echo "Executing local hook"
	$localHook "$@" || exit $?
fi

echo "Executing global hook"

case "${PWD}" in
	*acme*) GIT_INCLUDE_CONFIG="acme.config";;
	*foss*) GIT_INCLUDE_CONFIG="foss.config";;
esac

currentConfig=$(git config --local include.path || true)
if [[ -n "${currentConfig}" ]] && [[ ! -e "${currentConfig}" ]]; then
	echo "${currentConfig} does not exist! Fix or remove by git config --local --replace-all include.path \"\"" >&2
	exit 1
fi
# If we have a config to be applied
if [[ -n "${CONFIG_PATH}/${GIT_INCLUDE_CONFIG}" ]]; then
	if [[ ! -e "${CONFIG_PATH}/${GIT_INCLUDE_CONFIG}" ]]; then
		echo "${CONFIG_PATH}/${currentConfig} does not exist! Please fix ${BASH_SOURCE[-1]}!" >&2
		exit 1
	fi
	if [[ "${currentConfig}" != "${CONFIG_PATH}/${GIT_INCLUDE_CONFIG}" ]]; then
		echo "Applying config: ${CONFIG_PATH}/${GIT_INCLUDE_CONFIG}"
		git config --local --replace-all include.path "${CONFIG_PATH}/${GIT_INCLUDE_CONFIG}"
	fi
fi

Testing

Create temporary directory and empty repository

TMP=$(mktemp -d) && cd $TMP
mkdir acme && cd acme
git init

Create a File with Random Content, Add and Commit it

echo "$RANDOM" > README.md
git add README.md
git commit -m "Initial Commit"

Now within the acme-directory this should read:

git add README.md
git commit -m "Initial Commit"
Executing global hook
Applying config: /home/ctang/.config/git/acme.config
[master (root-commit) 3931374] Initial Commit
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

The user.email should now read from the acme.config

$ git config --get user.email
user@acme.com

The user.email can be locally overwritten git config --replace-all user.email "" and reset by git config --unset user.email (in repo.fodler)

Alternatives

Using ZSH and directory based config

With ZSH you could also take advantage of the GIT_CONFIG environment variable when on directory change. If you are using bash you should change to a better shell ;) It’s much less of a hassle and a much cleaner solution for the problem.

chpwd_git() {
  local configPath="$HOME/.config/git"
  case "${PWD}" in
    *acme*) export GIT_CONFIG="${configPath}/acme.config";;
    *foss*) export GIT_CONFIG="${configPath}/foss.config";;
  esac
}
chpwd_functions=("${chpwd_functions[@]}" "chpwd_git")

GIT Global Templates

If a global hook exists local hooks will be ignored. Therefore we check for a local hook in the script and execute it as well.

Previous to GIT version 2 GIT was using Templates which still can be used to initialize repositories. But they are just copied when initailizing repositories. You can setup them as well. Note that the hooks path will be $CONFIG_PATH/templates/hooks!

git config --global --replace-all init.templatedir $CONFIG_PATH/templates

Use commit hooks. They probably make your life as developer easier ;)

Trackback

@blurayne

This comment has been minimized.

Copy link
Owner Author

commented Feb 8, 2019

@blurayne

This comment has been minimized.

Copy link
Owner Author

commented Feb 12, 2019

Note: If using zsh/GIT_CONFIG solution please move your ~/.gitconfig to .config/git/config and everything will be fine again when using git config --global.

Also it's possible to add global ignore like with
git config --global core.excludesfile "$HOME/.config/git/ignore"

@blurayne

This comment has been minimized.

Copy link
Owner Author

commented Feb 12, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.