<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://amrbashir.me/</id>
    <title>Amr Bashir</title>
    <updated>2026-01-25T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Amr Bashir</name>
        <uri>https://amrbashir.me/</uri>
    </author>
    <link rel="alternate" href="https://amrbashir.me/"/>
    <link rel="self" href="/index.xml"/>
    <subtitle>Amr Bashir's blog about programming and computers.</subtitle>
    <icon>https://amrbashir.me/favicon.svg</icon>
    <category term="programming"/>
    <category term="computers"/>
    <category term="technology"/>
    <entry>
        <title type="html"><![CDATA[Managing multiple Git Configurations]]></title>
        <id>https://amrbashir.me/posts/manage-multiple-git-configs</id>
        <link href="https://amrbashir.me/posts/manage-multiple-git-configs"/>
        <updated>2026-01-25T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<p>Managing multiple Git configurations is essential for developers juggling work and personal projects
while ensuring proper commit attribution and correct signing keys.</p>
<p>There are several strategies to manage this, I will outline two of them here.</p>
<h2>Using Conditional Includes in Git Configuration</h2>
<p>For the past few years, a <a href="https://github.com/chippers">colleague</a> of mine introduced me to the concept of conditional includes in Git
configuration.</p>
<pre><code>[user]
	name = Your Name
	email =	personal@email.com

[includeIf "gitdir:~/work/"]
	path = ~/.gitconfig-work
</code></pre>
<p>In this setup, the main Git configuration file (<code>~/.gitconfig</code>) contains your default user information,
which applies to personal projects. The <code>includeIf</code> directive checks if the Git directory is within
the <code>~/work/</code> path and includes the additional configuration file (<code>~/.gitconfig-work</code>),
which contains your work-specific settings.</p>
<p>This approach is solid and works very well and is battle-tested.</p>
<h2>Using <code>cd</code> hooks for Dynamic Configuration</h2>
<p>Because I work on multiple projects, each requiring different Git configurations,
I needed a more dynamic solution. I wanted to automatically apply the correct Git configuration
based on the current working directory (or rather the nearest <code>.gitconfig</code> file in the directory tree).</p>
<p>Popularized by tools like <a href="https://direnv.net/">direnv</a>, <a href="https://asdf-vm.com/">asdf</a>, and others,
using <code>cd</code> hooks allows you to run custom scripts whenever you change directories.</p>
<p>This is perfect for my use case, and the idea is simple:</p>
<ol>
<li>When changing directories, search upwards in the directory tree for the nearest <code>.gitconfig</code> file.</li>
<li>If found, merge it with the global Git configuration for the current session using <code>GIT_CONFIG_GLOBAL</code>.</li>
<li>If no <code>.gitconfig</code> is found, revert to the default global configuration.</li>
</ol>
<p>And I just did that for <code>Bash</code>, <code>Zsh</code> and <code>PowerShell</code> which are the shells I use the most.</p>
<p>&lt;details data-group="shell-config" open&gt;
&lt;summary&gt;Bash&lt;/summary&gt;</p>
<pre><code>merge_nearest_gitconfig() {
    # Reset any previous temporary git config
	# so if we cd out of a project with a .gitconfig we go back to normal
    if [[ -n "$GIT_CONFIG_GLOBAL" ]]; then
        rm -f "$GIT_CONFIG_GLOBAL" 2&gt;/dev/null
        unset GIT_CONFIG_GLOBAL
    fi

    local current_dir="$PWD"
    local nearest_gitconfig=""

    # Find the nearest .gitconfig file upwards in the directory tree
    while [[ -n "$current_dir" ]]; do
        local gitconfig_path="$current_dir/.gitconfig"
        if [[ -f "$gitconfig_path" ]]; then
            nearest_gitconfig="$gitconfig_path"
            break
        fi

        # Move up one directory
        current_dir="${current_dir%/*}"
    done

    # if the found .gitconfig is not the same as user's global config, merge it
    if [[ -n "$nearest_gitconfig" &amp;&amp; "$nearest_gitconfig" != "$HOME/.gitconfig" ]]; then
        # Create a temporary config file with the user's global config ~/.gitconfig
        local temp_config=$(mktemp)
        cat "$HOME/.gitconfig" &gt; "$temp_config"

        # Append an include directive for the nearest .gitconfig
        echo -e "\n[include]\n    path = $nearest_gitconfig" &gt;&gt; "$temp_config"

        # Point Git to this merged config for the session
        export GIT_CONFIG_GLOBAL="$temp_config"
        echo "Merged git config from \e[36m$nearest_gitconfig\e[0m into current session."
    fi

}

# Function to change directory and immediately merge neatest git config
_cd_with_nearest_git() {
	\cd "$@" || return $?
	merge_nearest_gitconfig
}


alias cd='_cd_with_nearest_git'  # Override cd to our custom function
merge_nearest_gitconfig          # Run it once for the initial directory
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details data-group="shell-config"&gt;
&lt;summary&gt;Zsh&lt;/summary&gt;</p>
<pre><code>merge_nearest_gitconfig() {
    # Reset any previous temporary git config
	# so if we cd out of a project with a .gitconfig we go back to normal
    if [[ -n "$GIT_CONFIG_GLOBAL" ]]; then
        rm -f "$GIT_CONFIG_GLOBAL" 2&gt;/dev/null
        unset GIT_CONFIG_GLOBAL
    fi

    local current_dir="$PWD"
    local nearest_gitconfig=""

    # Find the nearest .gitconfig file upwards in the directory tree
    while [[ -n "$current_dir" ]]; do
        local gitconfig_path="$current_dir/.gitconfig"
        if [[ -f "$gitconfig_path" ]]; then
            nearest_gitconfig="$gitconfig_path"
            break
        fi

        # Move up one directory
        current_dir="${current_dir%/*}"
    done

    # if the found .gitconfig is not the same as user's global config, merge it
    if [[ -n "$nearest_gitconfig" &amp;&amp; "$nearest_gitconfig" != "$HOME/.gitconfig" ]]; then
        # Create a temporary config file with the user's global config ~/.gitconfig
        local temp_config=$(mktemp)
        cat "$HOME/.gitconfig" &gt; "$temp_config"

        # Append an include directive for the nearest .gitconfig
        echo -e "\n[include]\n    path = $nearest_gitconfig" &gt;&gt; "$temp_config"

        # Point Git to this merged config for the session
        export GIT_CONFIG_GLOBAL="$temp_config"
        echo "Merged git config from \e[36m$nearest_gitconfig\e[0m into current session."
    fi

}

autoload -U add-zsh-hook                   # load the add-zsh-hook function
add-zsh-hook chpwd merge_nearest_gitconfig # Run it on directory change
merge_nearest_gitconfig                    # Run it once for the initial directory
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details data-group="shell-config"&gt;
&lt;summary&gt;PowerShell&lt;/summary&gt;</p>
<pre><code>Function Merge-NearestGitConfig {
    # Reset any previous temporary git config
    if ($Env:GIT_CONFIG_GLOBAL) {
        Remove-Item $Env:GIT_CONFIG_GLOBAL -ErrorAction SilentlyContinue
        Remove-Item Env:GIT_CONFIG_GLOBAL
    }

    $currentDir = Get-Location
    $nearestGitconfig = $null

    # Find the nearest .gitconfig file upwards in the directory tree
    while ($currentDir) {
        $gitconfigPath = Join-Path -Path $currentDir -ChildPath ".gitconfig"
        if (Test-Path $gitconfigPath) {
            $nearestGitconfig = $gitconfigPath
            break
        }

        # Move up one directory
        $currentDir = Split-Path -Path $currentDir -Parent
    }

    # if the found .gitconfig is not the user's global config, merge it
    if ($nearestGitconfig -and ($nearestGitconfig -ne "$HOME\.gitconfig")) {
        # Create a temporary config file with the user's global config ~/.gitconfig
        $tempConfig = [System.IO.Path]::GetTempFileName()
        Get-Content "$HOME\.gitconfig" | Set-Content $tempConfig

        # Append an include directive for the nearest .gitconfig
        $nearestGitconfig = $nearestGitconfig -replace '\\', '/' # Normalize path for git
        Add-Content $tempConfig "`n[include]`n    path = $nearestGitconfig"

        # Point Git to this merged config for the session
        $Env:GIT_CONFIG_GLOBAL = $tempConfig
        Write-Host "Merged git config from " -NoNewline
        Write-Host $nearestGitconfig -NoNewline -ForegroundColor Cyan
        Write-Host " into current session."
    }
}

# Function to change directory and immediately merge neatest git config
Function __cd_with_nearest_git {
	Set-Location @args
	Merge-NearestGitConfig
}

Remove-Alias cd -ErrorAction SilentlyContinue             # Remove existing cd
Set-Alias cd __cd_with_nearest_git -Scope Global -Force   # Override cd to our custom function
Merge-NearestGitConfig                                    # Run it once for the initial directory
</code></pre>
<p>&lt;/details&gt;</p>
<p>This way I can have the following structure:</p>
<pre><code>~/
├── .gitconfig
├── work/
│   ├── .gitconfig
│   └── project-a/
└── work2/
│   ├── .gitconfig
│   ├── project-b/
│   └── project-c/
</code></pre>
<p><code>~/.gitconfig</code> would contain my default personal settings, while each work-related directory
contains its own <code>.gitconfig</code> file with the appropriate user information.</p>
]]></content>
        <category label="git"/>
        <category label="tools"/>
        <published>2026-01-25T00:00:00.000Z</published>
    </entry>
</feed>