Your Git config is not reproducible (and here's how to fix it)
Configure Git in a way you might not have seen before
What if the first thing you did on every machine was to guarantee that Git was configured exactly the same way you did for the first time? Even without writing a single line of code?
I think one of the most overlooked and part of the last checklist is the git config commands. You probably recognize getting a dusty notes file somewhere to type them in (or you go to the git-scm.com book section).
Are these potentially common issues you've faced:
- A teammate onboards and ends up with CRLF line endings that pollute every diff for a week?
- A build agent gets provisioned without
core.longpathsand starts failing on Windows?
The problem is not that Git configuration is hard. It's just that it's so invisible that it gets written once by hand and forgotten until it starts breaking things.
In this blog post, you will learn how to use the new GitConfig class-based DSC resource to manage any Git configuration setting. By using this class, it's going to be repeatable, and you can enforce it even more.
By the end, your Git configuration will be something you describe once, and you don't have to store it in your brain anymore.
What makes GitConfig different
If you know the winget-dsc repository, you know that the GitDsc module ships individual resources. Each resource can configure specific settings, such as setting the username or email. But even that it works, Git has many more useful configuration keys
The GitConfig resource is a bit different. It takes a setting name as a data property. That makes it not part of a resource name. For example, if you want to set the default branch, that would be:
Name = 'init.defaultBranch'
Value = 'main'The same resource can handle any Git setting that was just mentioned, and anything else git config supports. One resource, all settings basically. It's class exposes five properties:
| Property | Purpose |
|---|---|
Name |
The Git config key, e.g. core.longpaths |
ConfigLocation |
Scope: global, system, local, worktree, or none |
Value |
The desired value. Required when Exist is $true |
Exist |
Whether the setting should exist. Defaults to $true |
ProjectDirectory |
Required for local and worktree scopes |
Using the module
Class-based DSC resources can be instantiated to see what happens on each method. But before doing so, you need to module available in your session. If you are working from the source code directly, use:
git clone --branch gh-244/main/add-gitconfig-resource https://github.com/Gijsreyn/winget-dsc.git C:\source
$env:PSModulePath += ";C:\source\winget-dsc\resources\GitDsc"
using module GitDscInstall-PSResource -Name GitDsc.Discover your current configuration
Before you start modifying your existing system, what better way is there to extract your current system using Export(). This is a new method added to GitConfig that reads all settings at a given scope and returns them as a list of GitConfig objects.
To grab all settings from the global scope, you can run:
$resource = [GitConfig]::new()
$globalSettings = $resource.Export([ConfigLocation]::global)
$globalSettings | Format-Table Name, ValueA sample output looks like:
Name Value
---- -----
user.name Your Name
user.email you@example.com
init.defaultBranch main
core.longpaths trueYou can save these results to a JSON file, so you have an audit of your existing machine. If you want to do it for a specific repository, you can use the local config location with a path:
$localSettings = $resource.Export([ConfigLocation]::local, 'C:\repos\MyProject')
$localSettings | Format-Table Name, ValueExport(), it doesn't modify anything.Using the class directly: Get, Test, Set
Each class-based DSC resource in PowerShell exposes three standard methods. You can call them directly on an instance, ideal when you want to explore the resource. Let's take a look at when you want to see if your default branch is main:
using module GitDsc
$config = [GitConfig]::new()
$config.Name = 'init.defaultBranch'
$config.ConfigLocation = [ConfigLocation]::global
$config.Value = 'main'
$config.Exist = $true
$current = $config.Get()
$current
In the above image, you can see the default branch isn't main as Exist is False.
Exist is the new canonical property in Microsoft DSC (or to be more precise _exist).Whilst Get() shows you the current state, Test() verifies if it is in the desired state:
if ($config.Test()) {
Write-Host 'Already configured correctly.'
} else {
Write-Host 'Configuration needs to be applied.'
}
Now, say that you want to set it as the main branch by default, you simply call the Set() method:
$config.Set()
The Get() method now returns True, indicating that the setting has been set on global scope. Under the hood, this constructs the equivalent of git config --global init.defaultBranch main. If the setting is already correct, Set() does basically nothing (as it performs Test() inside Set()).
A practical example: Configuring a new machine
There are plenty of settings that Git has, but here's a common sequence that covers what most developers use globally. You can save this file and store it in a Git repository to easily apply:
# GitConfigure.ps1
using module GitDsc
$settings = @(
@{ Name = 'user.name'; Value = 'Your Name'; ConfigLocation = [ConfigLocation]::global },
@{ Name = 'user.email'; Value = 'you@example.com'; ConfigLocation = [ConfigLocation]::global },
@{ Name = 'init.defaultBranch'; Value = 'main'; ConfigLocation = [ConfigLocation]::global },
@{ Name = 'core.longpaths'; Value = 'true'; ConfigLocation = [ConfigLocation]::global },
@{ Name = 'core.autocrlf'; Value = 'input'; ConfigLocation = [ConfigLocation]::global }
)
foreach ($setting in $settings) {
$config = [GitConfig]::new()
$config.Name = $setting.Name
$config.ConfigLocation = $setting.ConfigLocation
$config.Value = $setting.Value
$config.Exist = $true
if (-not $config.Test()) {
Write-Host "Applying: $($config.Name) = $($config.Value)"
$config.Set()
} else {
Write-Host "Already set: $($config.Name)"
}
}You can run this script every time without worrying. This script runs in an idempotent way.
Removing a setting
What if you want to remove a setting? Maybe you want to temporarily disable a proxy that was set by your organization. To make a setting absent, you can set the Exist to False, which in turn constructs the git command as such: git config --global --unset http.proxy:
using module GitDsc
$config = [GitConfig]::new()
$config.Name = 'http.proxy'
$config.ConfigLocation = [ConfigLocation]::global
$config.Exist = $false
$config.Set()If the setting didn't exist at all, again, it would do nothing.
Using Microsoft DSC
Instantiating classes is a bit tedious to do. Especially if you have to constantly use that using module statement for each session.
Microsoft DSC is a command-line utility that can invoke PSDSC resources through an adapter. If you want to try out the following example, the GitDsc module should be discoverable through the $PSModulePath:
# Install module to install dsc.exe
Install-PSResource -Name PSDSC -Repository PSGallery
# Use the install command
Install-DscExe
# Save the following file
# git-global.dsc.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Set default branch name
type: GitDsc/GitConfig
directives:
requireAdapter: Microsoft.Adapter/PowerShell
properties:
Name: init.defaultBranch
ConfigLocation: global
Value: main
Exist: true
dsc config set --file git-global.dsc.yamlThis is way easier, and you can declare more resources for each setting in a declarative YAML file. If you want to extract an existing system, you would run dsc resource export --resource GitDsc/GitConfig.
Wrap up
One thing that is still a bit ugly is typing the Git setting name like init.defaultBranch. That still requires you to have some Git familiarity. That's a rough edge of using the module. But it is on the roadmap to address this in future releases to have a sort of key translation happening inside the class and allowing you to tab-complete between the available options.
Regardless, that's how Git configuration looks like for every machine you start working on. It doesn't become a mental checklist; the configuration document is.