gitconfig

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 17, 2026 License: MIT Imports: 17 Imported by: 2

README

gitconfig for Go

GoDoc Go Report Card

A pure Go library for reading and writing Git configuration files without depending on the git CLI. This library is particularly useful when:

  • Git might not be installed or available
  • You need consistent config parsing across platforms
  • You want to avoid breaking changes when Git's behavior changes
  • You need fine-grained control over config scopes and precedence

Originally developed to support gopass, this library aims for full Git compatibility while maintaining a simple, clean API.

Features

  • Multi-scope support - System, global, local, worktree, and environment configs
  • Include directives - Basic [include] and [includeIf] support (gitdir conditions)
  • Round-trip preservation - Maintains comments, whitespace, and formatting when writing
  • Cross-platform - Works on Linux, macOS, Windows, and other Unix systems
  • Customizable - Override config paths and environment prefixes for your application
  • Pure Go - No CGo dependencies, easy cross-compilation
  • Well-tested - Comprehensive test coverage including edge cases

Quick Start

package main

import (
    "fmt"
    "github.com/gopasspw/gitconfig"
)

func main() {
    // Load all config scopes (system, global, local)
    cfg := gitconfig.New()
    if err := cfg.LoadAll("."); err != nil {
        panic(err)
    }

    // Read a configuration value
    name, ok := cfg.Get("user.name")
    if ok {
        fmt.Printf("User name: %s\n", name)
    }

    // Write a configuration value (to local scope by default)
    cfg.Set("user.email", "[email protected]")
    if err := cfg.Write(); err != nil {
        panic(err)
    }
}

See the examples/ directory for more detailed usage patterns.

Reference Documentation

The reference for this implementation is the Git config documentation.

Installation

go get github.com/gopasspw/gitconfig

Usage

Loading Configuration

Use gitconfig.LoadAll with an optional workspace argument to process configuration from these locations in order (later ones take precedence):

  1. System - /etc/gitconfig
  2. Global - $XDG_CONFIG_HOME/git/config or ~/.gitconfig
  3. Local - <workdir>/.git/config
  4. Worktree - <workdir>/.git/config.worktree
  5. Environment - GIT_CONFIG_{COUNT,KEY,VALUE} environment variables
cfg := gitconfig.New()
if err := cfg.LoadAll("/path/to/repo"); err != nil {
    log.Fatal(err)
}

// Read from unified config (respects precedence)
value, ok := cfg.Get("core.editor")
Reading Values
// Get single value (returns last matching value)
editor, ok := cfg.Get("core.editor")
if !ok {
    editor = "vi" // default
}

// Get all values for a key (for multi-valued config)
remotes, ok := cfg.GetAll("remote.origin.fetch")

// Read from specific scope
email := cfg.GetGlobal("user.email")
autocrlf := cfg.GetLocal("core.autocrlf")
Writing Values
// Write to default scope (local)
cfg.Set("user.name", "Jane Doe")
cfg.Set("user.email", "[email protected]")

// Write to specific scope
cfg.SetGlobal("core.editor", "vim")
cfg.SetSystem("core.autocrlf", "false")

// Save changes
if err := cfg.Write(); err != nil {
    log.Fatal(err)
}
Scope-Specific Operations
// Load only a specific config file
localCfg, err := gitconfig.LoadConfig("/path/to/repo/.git/config")
if err != nil {
    log.Fatal(err)
}

// Work with single scope
localCfg.Set("branch.main.remote", "origin")
localCfg.Write()

Customization for Your Application

Applications like gopass can easily customize file paths and environment variable prefixes:

cfg := gitconfig.New()

// Customize config file locations
cfg.SystemConfig = "/etc/gopass/config"
cfg.GlobalConfig = "~/.config/gopass/config"
cfg.LocalConfig = ".gopass-config"
cfg.WorktreeConfig = ""  // Disable worktree config

// Customize environment variable prefix
cfg.EnvPrefix = "GOPASS_CONFIG"  // Uses GOPASS_CONFIG_COUNT, etc.

// For testing: prevent accidentally overwriting real configs
cfg.NoWrites = true

// Load with custom settings
cfg.LoadAll(".")
Customization Options
  • SystemConfig - Path to system-wide configuration file
  • GlobalConfig - Path to user's global configuration (set to "" to disable)
  • LocalConfig - Filename for repository-local config
  • WorktreeConfig - Filename for worktree-specific config (or "" to disable)
  • EnvPrefix - Prefix for environment variables (e.g., MYAPP_CONFIG)
  • NoWrites - Set to true to prevent Write() from modifying files (useful for testing)

Advanced Features

Include Directives

The library supports Git's [include] and [includeIf] directives:

# .git/config
[include]
    path = /path/to/common.gitconfig

[includeIf "gitdir:/path/to/work/"]
    path = ~/.gitconfig-work

Supported includeIf conditions:

  • gitdir: - Include if git directory matches pattern
  • gitdir/i: - Case-insensitive gitdir match

Current limitations:

  • Other conditional types (onbranch, hasconfig) are not yet supported
  • Relative paths in includes are resolved from the config file's directory
Subsections

Access subsections using dot notation:

// Set remote URL
cfg.Set("remote.origin.url", "https://github.com/user/repo.git")

// Set branch tracking
cfg.Set("branch.main.remote", "origin")
cfg.Set("branch.main.merge", "refs/heads/main")

// Access subsections
url, _ := cfg.Get("remote.origin.url")
Multi-valued Keys

Some config keys can have multiple values:

// Add multiple fetch refspecs
cfg.Set("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")
cfg.Set("remote.origin.fetch", "+refs/pull/*/head:refs/remotes/origin/pr/*")

// Retrieve all values
fetchSpecs, ok := cfg.GetAll("remote.origin.fetch")
// fetchSpecs = ["+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*/head:refs/remotes/origin/pr/*"]

Known Limitations

Current implementation has the following known limitations:

  • Bare boolean values - Keys without values (bare booleans) are not supported
  • Worktree support - Only partial worktree config support
  • includeIf conditions - Only gitdir and gitdir/i are supported
  • URL matching - url.<base>.insteadOf patterns are not yet implemented
  • Multivar operations - No special handling for replacing specific multivar instances
  • Whitespace preservation - Insignificant whitespace is not always perfectly preserved

These limitations reflect the primary use case supporting gopass. Contributions to address these are welcome!

Project Documentation

Versioning and Compatibility

This library aims to support the latest stable release of Git. We currently do not provide semantic versioning guarantees but aim to maintain backwards compatibility where possible.

Compatibility goals:

  • Parse any valid Git config file correctly
  • Preserve config file structure when writing
  • Handle edge cases gracefully (malformed input, missing files, etc.)

Contributing

We welcome contributions! Please see CONTRIBUTING.md for:

  • Development setup instructions
  • Code style guidelines
  • Testing requirements
  • Pull request process
Quick Development Setup
# Clone the repository
git clone https://github.com/gopasspw/gitconfig.git
cd gitconfig

# Run tests
make test

# Run code quality checks
make codequality

# Format code
make fmt

Testing

Run the full test suite:

# Run all tests
go test ./...

# Run with verbose output
go test -v ./...

# Run with coverage
go test -cover ./...

# Generate a coverage report
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out | tail -1

# Current coverage (2026-02-17): 89.9%

# Run specific test
go test -run TestLoadConfig

License and Credit

This package is licensed under the MIT License.

This repository is maintained to support the needs of gopass, a password manager for the command line. We aim to make it universally useful for all Go projects that need Git config parsing.

Maintainers:

  • Primary development and maintenance for gopass integration

Contributing: Contributions are welcome! Please read our contributing guidelines before submitting pull requests.

Support

Documentation

Overview

Package gitconfig implements a pure Go parser of Git SCM config files. The support is currently not matching git exactly, e.g. includes, urlmatches and multivars are currently not supported. And while we try to preserve the original file a much as possible when writing we currently don't exactly retain (insignificant) whitespaces.

The reference for this implementation is https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-config.html

Usage

Use gitconfig.LoadAll with an optional workspace argument to process configuration input from these locations in order (i.e. the later ones take precedence):

  • `system` - /etc/gitconfig
  • `global` - `$XDG_CONFIG_HOME/git/config` or `~/.gitconfig`
  • `local` - `<workdir>/config`
  • `worktree` - `<workdir>/config.worktree`
  • `command` - GIT_CONFIG_{COUNT,KEY,VALUE} environment variables

Note: We do not support parsing command line flags directly, but one can use the SetEnv method to set flags from the command line in the config.

Customization

`gopass` and other users of this package can easily customize file and environment names by utilizing the exported variables from the Configs struct:

  • SystemConfig - Path to system-wide config (e.g., /etc/gitconfig)
  • GlobalConfig - Path to user config (e.g., ~/.gitconfig) or "" to disable
  • LocalConfig - Per-repository config name (e.g., .git/config)
  • WorktreeConfig - Per-worktree config name (e.g., .git/config.worktree)
  • EnvPrefix - Environment variable prefix (defaults to GIT_CONFIG)

Note: For tests users will want to set `NoWrites = true` to avoid overwriting their real configs.

Examples

## Loading and Reading Configuration

Basic reading from all scopes (respects precedence):

cfg := gitconfig.New()
cfg.LoadAll(".")
value := cfg.Get("user.name")
fmt.Println(value)  // Reads from highest priority scope available

## Reading from Specific Scopes

Access configuration from a specific scope:

cfg := gitconfig.New()
cfg.LoadAll(".")
local := cfg.GetLocal("core.editor")
global := cfg.GetGlobal("user.email")
system := cfg.GetSystem("core.pager")

## Customization for Other Applications

Configure for a different application (like gopass):

cfg := gitconfig.New()
cfg.SystemConfig = "/etc/gopass/config"
cfg.GlobalConfig = ""
cfg.LocalConfig = ".gopass-config"
cfg.EnvPrefix = "GOPASS_CONFIG"
cfg.LoadAll(".")
notifications := cfg.Get("core.notifications")

## Writing Configuration

Modify and persist changes:

cfg, _ := gitconfig.LoadConfig(".git/config")
cfg.Set("user.name", "John Doe")
cfg.Set("user.email", "[email protected]")
cfg.Write()  // Persist changes to disk

## Scope-Specific Writes

Write to specific scopes in multi-scope configs:

cfg := gitconfig.New()
cfg.LoadAll(".")
cfg.SetLocal("core.autocrlf", "true")   // Write to .git/config
cfg.SetGlobal("user.signingkey", "...")  // Write to ~/.gitconfig
cfg.SetSystem("core.pager", "less")      // Write to /etc/gitconfig

## Error Handling

Use errors.Is to detect common error categories:

if err := cfg.Set("invalid", "value"); err != nil {
	if errors.Is(err, gitconfig.ErrInvalidKey) {
		// handle invalid key
	}
}

if err := cfgs.SetLocal("core.editor", "vim"); err != nil {
	if errors.Is(err, gitconfig.ErrWorkdirNotSet) {
		// call LoadAll or provide a workdir
	}
}

Versioning and Compatibility

We aim to support the latest stable release of Git only. Currently we do not provide any backwards compatibility and semantic versioning.

Known limitations

* Worktree support is only partial * Bare boolean values are not supported (e.g. a setting were only the key is present) * includeIf suppport is only partial, i.e. we only support the gitdir option

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidKey indicates a config key missing section or key name.
	ErrInvalidKey = errors.New("invalid key")
	// ErrWorkdirNotSet indicates a workdir is required but not configured.
	ErrWorkdirNotSet = errors.New("no workdir set")
	// ErrCreateConfigDir indicates a config directory could not be created.
	ErrCreateConfigDir = errors.New("failed to create config directory")
	// ErrWriteConfig indicates a config file could not be written.
	ErrWriteConfig = errors.New("failed to write config")
)
View Source
var (

	// CompatMode enables compatibility mode, which disables certain features like value unescaping.
	CompatMode bool
)

Functions

This section is empty.

Types

type Config

type Config struct {
	// contains filtered or unexported fields
}

Config represents a single git configuration file from one scope.

Config handles reading and writing a single configuration file while attempting to preserve the original formatting (comments, whitespace, section order).

Fields: - path: File path of this config file - readonly: If true, prevents any modifications (even in-memory) - noWrites: If true, prevents persisting changes to disk (useful for testing) - raw: Maintains the raw text representation for round-trip fidelity - vars: Map of normalized keys to their values (may be multiple values per key) - branch: Current git branch name (for onbranch conditionals)

Note: Config is not thread-safe. Concurrent access from multiple goroutines is not supported. Callers must provide synchronization if needed.

Typical Usage:

cfg, err := LoadConfig("~/.gitconfig")
if err != nil { ... }
value, ok := cfg.Get("core.editor")
if err := cfg.Set("core.pager", "less"); err != nil { ... }

func LoadConfig

func LoadConfig(fn string) (*Config, error)

LoadConfig tries to load a gitconfig from the given path.

func LoadConfigFromEnv

func LoadConfigFromEnv(envPrefix string) *Config

LoadConfigFromEnv will try to parse an overlay config from the environment variables. If no environment variables are set the resulting config will be valid but empty. Either way it will not be writeable.

func LoadConfigWithWorkdir

func LoadConfigWithWorkdir(fn, workdir string) (*Config, error)

LoadConfigWithWorkdir tries to load a gitconfig from the given path and a workdir. The workdir is used to resolve relative paths in the config.

func NewFromMap

func NewFromMap(data map[string]string) *Config

NewFromMap allows creating a new preset config from a map.

func ParseConfig

func ParseConfig(r io.Reader) *Config

ParseConfig will try to parse a gitconfig from the given io.Reader. It never fails. Invalid configs will be silently rejected.

func (*Config) Get

func (c *Config) Get(key string) (string, bool)

Get returns the first value of the key.

For keys with multiple values, Get returns only the first one. Use GetAll to retrieve all values for a key.

The key is case-insensitive for sections and key names but case-sensitive for subsection names (per git-config specification).

Returns (value, true) if the key is found, ("", false) otherwise.

Example:

v, ok := cfg.Get("core.editor")
if ok {
  fmt.Printf("Editor: %s\n", v)
}

func (*Config) GetAll

func (c *Config) GetAll(key string) ([]string, bool)

GetAll returns all values of the key.

Git config allows multiple values for the same key. This is common for: - Multiple include paths - Multiple aliases - Arrays in custom configurations

Returns (values, true) if the key is found, (nil, false) otherwise. If found, values will be non-nil but may be empty.

Example:

paths, ok := cfg.GetAll("include.path")
if ok {
  for _, path := range paths {
    fmt.Printf("Include: %s\n", path)
  }
}

func (*Config) IsEmpty

func (c *Config) IsEmpty() bool

IsEmpty returns true if the config is empty (no configuration loaded).

An empty config is one that: - Is nil - Has no variables loaded - Has no raw content (not just missing path reference)

This is used to distinguish between "not yet loaded" and "loaded but empty file".

func (*Config) IsSet

func (c *Config) IsSet(key string) bool

IsSet returns true if the key was set in this config.

Returns true even if the value is empty string (unlike checking Get with ok).

Example:

if cfg.IsSet("core.editor") {
  fmt.Println("Editor is configured")
}

func (*Config) Set

func (c *Config) Set(key, value string) error

Set updates or adds a key in the config.

Behavior: - If the key exists, the first value is updated - If the key doesn't exist, it's added to an existing section or a new section - If possible, the underlying config file is written to disk - Original formatting (comments, whitespace) is preserved where possible

Errors: - Returns error if readonly or key is invalid (missing section or key name) - Returns error if file write fails (but in-memory value may be set)

This method normalizes the key (lowercase sections and key names) but preserves subsect names' case.

Example:

if err := cfg.Set("core.pager", "less"); err != nil {
  log.Fatal(err)
}

func (*Config) Unset

func (c *Config) Unset(key string) error

Unset deletes a key from the config.

Behavior: - If the key exists, it's removed from vars and the raw config string - If the key doesn't exist, this is a no-op (no error) - The underlying config file is updated if possible - Readonly configs silently ignore the unset operation

Note: Currently does not remove entire sections, only individual keys within sections.

Example:

if err := cfg.Unset("core.pager"); err != nil {
  log.Fatal(err)
}

type Configs

type Configs struct {
	Preset *Config

	Name           string
	SystemConfig   string
	GlobalConfig   string
	LocalConfig    string
	WorktreeConfig string
	EnvPrefix      string
	NoWrites       bool
	// contains filtered or unexported fields
}

Configs represents all git configuration files for a repository.

Configs manages multiple Config objects from different scopes with a unified interface. It handles loading and merging configurations from multiple sourc with priority.

Scope Priority (highest to lowest): 1. Environment variables (GIT_CONFIG_*) 2. Worktree-specific config (.git/config.worktree) 3. Local/repository config (.git/config) 4. Global/user config (~/.gitconfig) 5. System config (/etc/gitconfig) 6. Preset/built-in defaults

Fields: - Preset: Built-in default configuration (optional) - system, global, local, worktree, env: Config objects for each scope - workdir: Working directory (used to locate local and worktree configs) - Name: Configuration set name (e.g., "git" or "gopass") - SystemConfig, GlobalConfig, LocalConfig, WorktreeConfig: File paths - EnvPrefix: Prefix for environment variables (e.g., "GIT_CONFIG") - NoWrites: If true, prevents all writes to disk

Usage:

cfg := New()
cfg.LoadAll(".")  // Load from current directory
value := cfg.Get("core.editor")  // Reads from all scopes
cfg.SetLocal("core.pager", "less")  // Write to local only

func New

func New() *Configs

New creates a new Configs instance with default configuration.

The returned instance is not yet loaded. Call LoadAll() to load configurations.

Default settings: - Name: "git" - SystemConfig: "/etc/gitconfig" (Unix) or auto-detected (Windows) - GlobalConfig: "~/.gitconfig" - LocalConfig: "config" (relative to workdir) - WorktreeConfig: "config.worktree" (relative to workdir) - EnvPrefix: "GIT_CONFIG" - NoWrites: false (allows persisting changes)

These settings can be customized before calling LoadAll():

cfg := New()
cfg.SystemConfig = "/etc/myapp/config"
cfg.EnvPrefix = "MYAPP_CONFIG"
cfg.LoadAll(".")

func (*Configs) Get

func (cs *Configs) Get(key string) string

Get returns the value for the given key from the first scope that contains it.

Lookup Order (by scope priority): 1. Environment variables (GIT_CONFIG_*) 2. Worktree config (.git/config.worktree) 3. Local config (.git/config) 4. Global config (~/.gitconfig) 5. System config (/etc/gitconfig) 6. Preset/defaults

The search stops at the first scope that has the key. Earlier scopes override later ones.

Returns the value as a string. For keys with multiple values, returns the first one. Returns empty string if key not found in any scope.

Example:

editor := cfg.Get("core.editor")
if editor != "" {
  fmt.Printf("Using editor: %s\n", editor)
}

func (*Configs) GetAll

func (cs *Configs) GetAll(key string) []string

GetAll returns all values for the given key from the first scope that contains it.

Like Get but returns all values for keys that can have multiple entries. See Get documentation for scope priority.

Returns nil if key not found in any scope.

func (*Configs) GetFrom added in v0.0.4

func (cs *Configs) GetFrom(key string, scope string) (string, bool)

GetFrom returns the value for the given key from the given scope. Valid scopes are: env, worktree, local, global, system and preset.

func (*Configs) GetGlobal

func (cs *Configs) GetGlobal(key string) string

GetGlobal specifically asks the per-user (global) config for a key.

This bypasses the scope priority and only reads from the global config. Useful when you specifically want settings from ~/.gitconfig.

Returns empty string if the key is not found in the global config.

Example:

name, _ := cfg.GetGlobal("user.name")

func (*Configs) GetLocal

func (cs *Configs) GetLocal(key string) string

GetLocal specifically asks the per-directory (local) config for a key.

This bypasses the scope priority and only reads from the local config (.git/config). Useful when you specifically want settings from the repository's config.

Returns empty string if the key is not found in the local config.

Example:

url, _ := cfg.GetLocal("remote.origin.url")

func (*Configs) HasGlobalConfig

func (cs *Configs) HasGlobalConfig() bool

HasGlobalConfig indicates if a per-user config can be found.

Returns true if a global config file exists at one of the configured locations.

func (*Configs) IsSet

func (cs *Configs) IsSet(key string) bool

IsSet returns true if this key is set in any of our configs.

func (*Configs) KVList added in v0.0.2

func (cs *Configs) KVList(prefix, sep string) []string

KVList returns a list of all keys and values matching the given prefix.

func (*Configs) Keys

func (cs *Configs) Keys() []string

Keys returns a list of all keys from all available scopes. Every key has section and possibly a subsection. They are separated by dots. The subsection itself may contain dots. The final key name and the section MUST NOT contain dots.

Examples

  • remote.gist.gopass.pw.path -> section: remote, subsection: gist.gopass.pw, key: path
  • core.timeout -> section: core, key: timeout

func (*Configs) List

func (cs *Configs) List(prefix string) []string

List returns all keys matching the given prefix. The prefix can be empty, then this is identical to Keys().

func (*Configs) ListSections

func (cs *Configs) ListSections() []string

ListSections returns a sorted list of all sections.

func (*Configs) ListSubsections

func (cs *Configs) ListSubsections(wantSection string) []string

ListSubsections returns a sorted list of all subsections in the given section.

func (*Configs) LoadAll

func (cs *Configs) LoadAll(workdir string) *Configs

LoadAll loads all known configuration files from their configured locations.

Behavior: - Loads configs from all scopes (system, global, local, worktree, env) - Missing or invalid files are silently ignored - Never returns an error (always returns &cs for chaining) - workdir is optional; if empty, local and worktree configs are not loaded - Processes include and includeIf directives - Merges all configs with proper scope priority

Parameters: - workdir: Working directory (usually repo root) to locate local/worktree configs

Example:

cfg := New()
cfg.LoadAll("/path/to/repo")
// Now ready to use Get, Set, etc.

func (*Configs) Reload

func (cs *Configs) Reload()

Reload reloads all configuration files from disk.

This is useful when configuration files have been modified externally. Uses the same workdir that was provided to the last LoadAll call.

func (*Configs) SetEnv

func (cs *Configs) SetEnv(key, value string) error

SetEnv sets (or adds) a key in the per-process (env) config. Useful for persisting flags during the invocation.

func (*Configs) SetGlobal

func (cs *Configs) SetGlobal(key, value string) error

SetGlobal sets (or adds) a key only in the per-user (global) config.

func (*Configs) SetLocal

func (cs *Configs) SetLocal(key, value string) error

SetLocal sets (or adds) a key only in the per-directory (local) config.

func (*Configs) String

func (cs *Configs) String() string

String implements fmt.Stringer for debugging.

func (*Configs) UnsetGlobal

func (cs *Configs) UnsetGlobal(key string) error

UnsetGlobal deletes a key from the global config.

func (*Configs) UnsetLocal

func (cs *Configs) UnsetLocal(key string) error

UnsetLocal deletes a key from the local config.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL