gitconfig

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 16 Imported by: 0

README

gitconfig is a Go library to read and edit git configuration the way git does.

  • same result as git: same parsing, same value types (bool, int, path), tested against the git binary.
  • every source: system, global, local and worktree files, GIT_CONFIG_* variables, include and includeIf. Each value says which file and line it came from.
  • lossless edits: an edit changes only the lines it touches; comments and layout are kept.
  • safe alongside git: writes take git's lock, so a concurrent git config and this library never overwrite each other.
  • safe on hostile repositories: untrusted repositories are refused, as git refuses them (safe.directory).

Where this library deliberately differs from git, and why, is listed in COMPATIBILITY.md.

Installation

go get github.com/git-bug/gitconfig

Example

package main

import (
	"fmt"
	"log"

	"github.com/git-bug/gitconfig"
)

func main() {
	// Find the repository and where git reads its configuration from.
	env, err := gitconfig.Discover(".")
	if err != nil {
		log.Fatal(err) // ErrNotRepository, or ErrUntrusted as git refuses it
	}

	// Read the effective configuration: system, global, local, worktree
	// and command scopes, with includes, as git would use it.
	cfg, err := env.Load()
	if err != nil {
		log.Fatal(err)
	}
	if e, ok := cfg.Get("user.name"); ok {
		fmt.Printf("user.name = %s (%s)\n", e.Value, e.Origin.File)
	}

	// Edit the repository's config. Comments and layout are kept, and the
	// file is not touched if the value is already set.
	err = env.UpdateScope(gitconfig.ScopeLocal, func(f *gitconfig.File) error {
		return f.Set("core.editor", "vim")
	})
	if err != nil {
		log.Fatal(err)
	}
}

License

MIT, see LICENSE. This is a clean room implementation: no code is derived from git, see CLEAN-ROOM.md. It is meant to interoperate with git.

Documentation

Overview

Package gitconfig reads and edits git configuration files exactly like git does: same parsing, same value semantics, same scopes and includes, and edits that change only the lines they touch.

The API has three parts:

  • Config is an immutable set of entries, for reading.
  • File is the lossless, editable form of a single file.
  • Env says where git's configuration comes from, and is the only way to the disk: it loads scopes or files (following includes) and edits them with git's lock file protocol, so that git and this package can safely write the same file.

Reading the effective configuration of a repository and changing a value:

env, err := gitconfig.Discover(".") // the working tree
if err != nil { ... }               // ErrNotRepository, or ErrUntrusted

cfg, err := env.Load()
if err != nil { ... }
if e, ok := cfg.Get("user.name"); ok {
	fmt.Println(e.Value)
}

err = env.UpdateScope(gitconfig.ScopeLocal, func(f *gitconfig.File) error {
	return f.Set("core.editor", "vim")
})

Nothing in this package reads the process environment or the home directory, except Discover, and only Discover and the methods of Env touch the filesystem. An Env built by hand is fully described by its fields. Parse, File and Config do no I/O, for callers that store configuration their own way.

Errors wrap the sentinel errors of this package (ErrLocked, ErrInvalidKey, ...) in a typed error giving the key, file or value concerned; the documentation of the error variables says which function returns which type.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is returned when the section or subsection to rename is
	// absent. Removing something absent is not an error.
	ErrNotFound = errors.New("gitconfig: not found")

	// ErrMultipleValues is returned when a single-value operation finds
	// several values.
	ErrMultipleValues = errors.New("gitconfig: multiple values")

	// ErrInvalidKey is returned when an edit, ParseKey or a CommandEntry is
	// given a key or section name that git rejects. Reads treat such a key
	// as absent.
	ErrInvalidKey = errors.New("gitconfig: invalid key")

	// ErrInvalidValue is returned when an edit is given a value git cannot
	// store: one that contains a NUL byte.
	ErrInvalidValue = errors.New("gitconfig: invalid value")

	// ErrLocked is returned when the lock file exists: another process is
	// writing.
	ErrLocked = errors.New("gitconfig: config file is locked")

	// ErrIncludeDepth is returned when includes nest more than 10 levels,
	// usually because of a cycle.
	ErrIncludeDepth = errors.New("gitconfig: exceeded maximum include depth")

	// ErrLoadLimit is returned when one load would read more than 1000
	// files, 4 MiB, or 2¹⁶ entries and distinct section headers in total
	// from files (entries of Env.Command are not counted).
	// Parse applies the byte and entry limits to its one file, and keeps at
	// most 2¹⁸ pieces of it (blank lines, comments, ...); an edit that would
	// take a File beyond these limits fails with it.
	ErrLoadLimit = errors.New("gitconfig: exceeded load limits")

	// ErrNotRegular is returned when a configuration file or an include is
	// not a regular file (a directory, a FIFO, a device).
	ErrNotRegular = errors.New("gitconfig: not a regular file")

	// ErrNotAbsolute is returned when Env.GitDir, or an include.path given
	// on the command line, is a relative path.
	ErrNotAbsolute = errors.New("gitconfig: path is not absolute")

	// ErrNoScope is returned when a scope does not exist or cannot be written
	// in an Env.
	ErrNoScope = errors.New("gitconfig: scope not available")

	// ErrUntrusted is returned by Discover for a repository git would
	// refuse: not owned by the current user, and not listed in
	// safe.directory. The *FileError's Path is the path to list.
	ErrUntrusted = errors.New("gitconfig: repository not owned by the current user nor listed in safe.directory")

	// ErrNotRepository is returned by Discover for a directory that is not
	// a repository: the git directory expected, its .git when it exists and
	// the directory itself otherwise, holds no HEAD file, or its common
	// directory no objects and refs directories. The *FileError's Path is
	// that git directory.
	ErrNotRepository = errors.New("gitconfig: not a git repository")

	// ErrClosed is returned when a File is edited after the callback of
	// Env.UpdateFile or Env.UpdateScope has returned.
	ErrClosed = errors.New("gitconfig: file is closed")
)

The sentinel errors below are returned wrapped in a typed error that says which key, file or value is concerned; test them with errors.Is, and get the context with errors.As:

  • ParseKey and File edits return a *KeyError.
  • Parse returns a *SyntaxError, or a *FileError for load limits.
  • Typed reads (Config.Bool, Entry.Int, ...) return a *ValueError.
  • Env methods and Discover return a *FileError for anything about a file (reading, a *SyntaxError, includes, load limits, locking, writing), a *KeyError for an invalid CommandEntry key, and ErrNoScope unwrapped, as no file is concerned. The error returned by the callback of Env.UpdateFile or Env.UpdateScope is returned as is.
  • Discover also returns an *EnvError for an environment variable it cannot interpret, ErrNotRepository in a *FileError for a directory that is not a repository, and ErrUntrusted in a *FileError for a repository git would refuse.

Error messages name files, whose paths can come from a configuration file someone else wrote. As in git, but stricter, control characters (newline and tab included), bidirectional controls and bytes that are not UTF-8 are replaced with '?', so that a path cannot drive the terminal it is printed to, nor forge a line or disguise itself. The fields of the errors, and Entry, Origin and Key, hold the raw data: quote them before printing them yourself.

Functions

This section is empty.

Types

type CommandEntry

type CommandEntry struct {
	Key      string
	Value    string
	Implicit bool // "git -c key" without "=": true as a boolean
}

CommandEntry is one value given on the command line. Key is validated by Load: a key git rejects makes Load fail with ErrInvalidKey, as git does. An include.path given this way is followed; a relative one is ErrNotAbsolute, as git refuses relative includes that do not come from a file.

type Config

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

Config is an immutable, ordered set of entries, from a single file or from several scopes. It is safe for concurrent use. A nil *Config is empty.

Keys are given as in `git config`: "section.name" or "section.subsection.name". Section and name are case-insensitive, the subsection is case-sensitive; a legacy header [a.B] holds subsection "b", as git lowers it. An invalid key is never present: every read treats it as absent. Keys are usually constants in the caller's code; check them once with ParseKey, for example in a test.

When a key has several values, the last one wins, as in git: Get, Value, Bool, Int and Path use it, GetAll returns them all.

func (*Config) All

func (c *Config) All() iter.Seq[Entry]

All returns every entry, in order.

func (*Config) Bool

func (c *Config) Bool(key string, def bool) (bool, error)

Bool returns the last value for key as a boolean, or def if key is absent. A value that is not a valid boolean is a *ValueError; the result is then def. To tell an absent key from one set to def, use Get and Entry.Bool.

func (*Config) Get

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

Get returns the last entry for key, as `git config --get` does.

func (*Config) GetAll

func (c *Config) GetAll(key string) []Entry

GetAll returns every entry for key, in order. The returned slice is owned by the caller.

func (*Config) Int

func (c *Config) Int(key string, def int64) (int64, error)

Int returns the last value for key as an integer, or def if key is absent. A value that is not a valid integer is a *ValueError; the result is then def.

func (*Config) Path

func (c *Config) Path(key, home, def string) (string, error)

Path returns the last value for key expanded as a path by Entry.Path, or def if key is absent, expanded the same way. home is the home directory, usually Env.Home.

A value that cannot be expanded is a *ValueError, and the result is then def, unexpanded. This also holds for def itself: its error reports it with an empty Origin, and a zero Key if key is invalid.

func (*Config) Section

func (c *Config) Section(section string) iter.Seq[Entry]

Section returns every entry of section [section] itself, in order; entries of its subsections are not included. section is case-insensitive.

func (*Config) Subsection

func (c *Config) Subsection(section, sub string) iter.Seq[Entry]

Subsection returns every entry of subsection [section "sub"], in order. section is case-insensitive, sub is case-sensitive. sub "" is the empty subsection [section ""], distinct from [section].

func (*Config) Subsections

func (c *Config) Subsections(section string) []string

Subsections returns the distinct subsections of section, in order of first appearance. For example Subsections("remote") lists the remote names. Entries of [section] itself have no subsection and are not listed; "" stands for [section ""].

func (*Config) Value

func (c *Config) Value(key string, def string) string

Value returns the last value for key, or def if key is absent.

type Entry

type Entry struct {
	Key Key

	// Value is the decoded value: quotes removed, escapes resolved,
	// continuation lines joined, unquoted leading and trailing whitespace
	// trimmed.
	Value string

	// Implicit is true for a key written without '=' ("[a]\n\tk"). Value is
	// then "", and the entry reads as true as a boolean.
	Implicit bool

	Origin Origin
}

Entry is one occurrence of a key in a configuration file. It is plain data: two entries with the same fields are equal.

func (Entry) Bool

func (e Entry) Bool() (bool, error)

Bool interprets the value as git does: true, yes, on and non-zero integers are true; false, no, off, 0 and "" are false; an implicit value is true. Case is ignored. Anything else is a *ValueError.

func (Entry) Int

func (e Entry) Int() (int64, error)

Int interprets the value as git does: a decimal number, or octal with a leading "0" and hexadecimal with a leading "0x" as in C (so "010" is 8), optionally followed by a unit k, m or g (×1024, ×1024², ×1024³), case ignored. An invalid number or unit, a value out of the int64 range or an implicit value is a *ValueError.

func (Entry) Path

func (e Entry) Path(home string) (string, error)

Path interprets the value as a path, as git does for `--type=path` and for include paths: a leading "~/", or a value of "~" alone, is replaced by home, usually Env.Home. Other values are returned unchanged; relative paths are not resolved.

These are a *ValueError: "~" or "~/" with an empty home, "~user/" (not supported), a leading "%(prefix)/" (the install prefix of the git binary, unknown to this package) and an implicit value.

type Env

type Env struct {
	// Home is the user's home directory. Load uses it to expand "~/" in
	// include paths; pass it to Config.Path and Entry.Path for values.
	// "" makes such paths an error.
	Home string

	// GitDir is the repository's git directory, "" outside a repository.
	// It must be absolute: includeIf "gitdir:" matches absolute paths, and
	// a relative GitDir makes Load fail with ErrNotAbsolute rather than
	// silently not match. For a linked worktree, this is the worktree's
	// own git directory (.git/worktrees/<name>): the shared config is found
	// through its commondir file, and config.worktree is read from it.
	// includeIf "onbranch:" reads <GitDir>/HEAD when such a condition is
	// met.
	GitDir string

	// SystemConfig is the system configuration file, "" for none.
	SystemConfig string

	// GlobalConfig lists the global configuration files, read in order.
	// Writes go to the last one that exists, or to the last one if none
	// exists: for git's [XDG file, ~/.gitconfig] this picks ~/.gitconfig
	// unless only the XDG file exists. A symbolic link exists if its target
	// does.
	GlobalConfig []string

	// Command holds the values given on the command line (git -c,
	// GIT_CONFIG_COUNT, GIT_CONFIG_PARAMETERS), in order.
	Command []CommandEntry
}

Env says where git's configuration comes from: the system file, the global files, the repository and the command line. Discover fills it as git does; tests and embedders can fill it by hand. Every field is used as given, and a zero source field means "none": an Env reads nothing it was not given. An Env is a value, but copies share the GlobalConfig and Command slices: replace them rather than modify them in place.

Configuration can come from someone else: a repository on a shared disk, an unpacked archive, a file reached through include.path. Loading such a file is safe (bounded, see Load), but its values are not to be trusted. Discover refuses a repository git would refuse (ErrUntrusted); an Env filled by hand is not checked, as git does not check a repository named by GIT_DIR.

func Discover

func Discover(dir string) (Env, error)

Discover finds where git's configuration comes from, the way git does, using the running process: environment variables (GIT_CONFIG_GLOBAL, XDG_CONFIG_HOME, …), the user's home directory, the working directory and the platform's system configuration file.

dir is where the repository is found, as git finds it when started there (git -C dir): a working tree (a directory holding .git), its .git, or a git directory, such as a bare repository; it is made absolute. A .git file holding "gitdir: <path>", as in a linked worktree or a submodule, is followed. Finding the repository is left to the caller: Discover does not search parent directories and does not read GIT_DIR. Pass "" when not in a repository: only the system, global and command-line scopes exist then. A dir that is not a repository fails with ErrNotRepository, in a *FileError naming the git directory expected: its .git when it exists (or the directory a gitdir file names), dir itself otherwise. As for git, a git directory must hold a HEAD file, and its common directory objects and refs directories; any other would have its config read and written as the local scope. A caller that wants the repository only when there is one can fall back to Discover("") on ErrNotRepository.

The repository must be trusted, as git requires: owned by the current user (on Unix, also the SUDO_UID user for root; on Windows, the Administrators group for an administrator), or listed in safe.directory. Otherwise Discover fails with ErrUntrusted, in a *FileError naming the path to list. As in git, that path is the working tree when dir is one, dir itself otherwise: pass the working tree when there is one.

Without a home directory, Home is "" and the global files are those git reads then: the XDG file if XDG_CONFIG_HOME is set, no ~/.gitconfig.

On Windows, SystemConfig is "" unless GIT_CONFIG_SYSTEM is set: the system file lives in git's installation directory, which Discover does not look for. Set the field to read it.

It fails if a gitdir file is malformed (a *FileError), or if GIT_CONFIG_NOSYSTEM, GIT_CONFIG_COUNT or GIT_CONFIG_PARAMETERS is malformed (an *EnvError), as git does.

It is the only function of this package that reads the process environment.

func (Env) Load

func (e Env) Load() (*Config, error)

Load returns the effective configuration: system, global, local, worktree and command-line scopes, in that order of precedence, with includes followed. This is what git itself uses. Each entry's Origin.Scope tells which scope it comes from. Missing files are skipped.

Includes are followed as git does (include.path and includeIf with the gitdir:, gitdir/i: and onbranch: conditions; hasconfig: is never true), up to 10 levels deep (ErrIncludeDepth). Two safety bounds git lacks are added: an include must be a regular file (ErrNotRegular, so a FIFO or a device cannot hang or exhaust the process), and one load reads at most 1000 files, 4 MiB and 2¹⁶ entries and distinct section headers in total from files (ErrLoadLimit, so that a file including itself many times cannot multiply the work exponentially, and a file of tiny entries cannot exhaust memory). Real configurations never come near these bounds.

As in git, includes are followed as a file is read: an error in an included file is reported before a syntax error further down the file that includes it.

A global file that cannot be read for lack of permission is skipped, as git does; LoadProtected skips such a system file too.

Example

Read the effective configuration of a repository, as git would use it.

package main

import (
	"fmt"
	"log"

	"github.com/git-bug/gitconfig"
)

func main() {
	env, err := gitconfig.Discover(".")
	if err != nil {
		log.Fatal(err)
	}
	cfg, err := env.Load()
	if err != nil {
		log.Fatal(err)
	}

	if e, ok := cfg.Get("user.name"); ok {
		fmt.Println("user:", e.Value)
	}

	bare, err := cfg.Bool("core.bare", false)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("bare:", bare)
}

func (Env) LoadFile

func (e Env) LoadFile(path string) (*Config, error)

LoadFile returns the configuration of the file at path, with includes followed, like `git config --includes -f <path>` (git itself follows includes there only with --includes). Its entries have ScopeFile. A missing file is empty.

func (Env) LoadProtected

func (e Env) LoadProtected() (*Config, error)

LoadProtected returns the configuration of the protected scopes only, with includes followed: system, global and command line, in that order. This is what git uses for keys a repository must not be able to set, such as safe.directory or safe.bareRepository. The repository's configuration is not read.

func (Env) LoadScope

func (e Env) LoadScope(s Scope) (*Config, error)

LoadScope returns the configuration of exactly one scope, with includes followed, like `git config --includes --<scope>` (git itself follows includes there only with --includes). Lower scopes are not included.

ScopeGlobal is the whole global layer, every file of GlobalConfig, as in Load. `git config --global` differs: it reads only the file it writes to (WritePath), so keys of the XDG file are not listed while ~/.gitconfig exists.

A scope with no file (SystemConfig "", a global or worktree file that does not exist) is empty, as for `git config --get`. ScopeWorktree reads the file WritePath gives: without extensions.worktreeConfig, that is the repository's config, and its entries have ScopeLocal, as git reports. It fails with ErrNoScope for ScopeFile, and for ScopeLocal and ScopeWorktree where WritePath does.

func (Env) UpdateFile

func (e Env) UpdateFile(path string, fn func(*File) error) error

UpdateFile edits the file at path with git's lock protocol, like `git config -f <path>`:

  1. if path is a symbolic link, follow it (up to 5 levels, relative targets resolved against the link's directory) and edit the target: the link itself is kept;
  2. create <target>.lock exclusively, never through a symbolic link, or fail with ErrLocked immediately, as git does;
  3. read and parse the target under the lock (a missing file is empty), into a File;
  4. call fn, exactly once;
  5. if the content changed, write it to the lock file, give it the target's permission bits exactly (chmod, so the umask does not apply; a new file gets 0666 minus the umask), and rename it over the target; otherwise leave the target alone.

On Windows, opening or replacing the target is retried for up to a second while another process holds it, as a concurrent writer or a reader does for a moment.

If fn returns an error or panics, the lock is released and nothing is written; the panic then continues. Readers see either the old or the new file, never a partial one. After fn returns, edits of the File fail with ErrClosed.

Following the symbolic link in step 1 is what git does, and what dotfile managers rely on. It also means that whoever controls the directory of path controls which file is written: do not edit the configuration of a repository that Discover has not accepted.

func (Env) UpdateScope

func (e Env) UpdateScope(s Scope, fn func(*File) error) error

UpdateScope edits the file of scope s, which WritePath gives, as UpdateFile does; it fails with ErrNoScope where WritePath does. The File's entries have the scope LoadScope gives them. Writes never go into an included file.

Example

Change a value in the repository's config. Comments and layout are kept, and the file is not touched if the value is already set.

package main

import (
	"log"

	"github.com/git-bug/gitconfig"
)

func main() {
	env, err := gitconfig.Discover(".")
	if err != nil {
		log.Fatal(err)
	}
	err = env.UpdateScope(gitconfig.ScopeLocal, func(f *gitconfig.File) error {
		return f.Set("core.editor", "vim")
	})
	if err != nil {
		log.Fatal(err)
	}
}

func (Env) WritePath

func (e Env) WritePath(s Scope) (string, error)

WritePath returns the file that `git config --<scope>` writes to. For ScopeWorktree, that is config.worktree if extensions.worktreeConfig is set, otherwise the repository's config as for ScopeLocal, provided the repository has a single worktree. It fails with ErrNoScope if the scope cannot be written in this Env: ScopeFile and ScopeCommand always, ScopeSystem with no SystemConfig, ScopeGlobal with no GlobalConfig, ScopeLocal and ScopeWorktree outside a repository, ScopeWorktree in a repository with several worktrees and no extensions.worktreeConfig.

type EnvError

type EnvError struct {
	Var string // the variable's name, e.g. "GIT_CONFIG_COUNT"
	Err error  // the reason
}

EnvError reports an environment variable Discover cannot interpret, as git refuses it: a malformed GIT_CONFIG_NOSYSTEM, GIT_CONFIG_COUNT or GIT_CONFIG_PARAMETERS, or a missing GIT_CONFIG_KEY_<n> or GIT_CONFIG_VALUE_<n>.

func (*EnvError) Error

func (e *EnvError) Error() string

func (*EnvError) Unwrap

func (e *EnvError) Unwrap() error

type File

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

File is the lossless, editable form of one configuration file. Edits change only the lines `git config` would change, and produce the same bytes; everything else (comments, blank lines, indentation) is kept byte for byte.

A File comes from Parse, for content that is not on disk (tests, in-memory repositories, a .gitmodules blob), or from Env.UpdateScope and Env.UpdateFile, to edit a file on disk. The zero value is an empty file.

A File is mutable and must not be used by several goroutines at once. Use Config to take an immutable snapshot; its entries have the scope the File was opened for by Env.UpdateScope, and ScopeFile otherwise.

Edits find keys, sections and subsections as Config does: section and name case-insensitive, subsection exact, a legacy header [a.B] holding subsection "b". git differs in two places: it places a key with an upper-case subsection into a matching legacy header, where it reads back lowered, and it removes or renames a section only if the header is spelled as given.

A new key goes at the end of the last section that holds its section or subsection, or into a new section at the end of the file, as git does.

Edits return a *KeyError wrapping ErrInvalidKey if a key or section name is one git rejects, or a key has an empty section (".sub.name", which git reads from a header [ "sub"]), and wrapping ErrInvalidValue if a value contains a NUL byte. An edit that would take the file beyond the limits of Parse is a *KeyError wrapping ErrLoadLimit. Removing something that is absent is not an error: the methods report what they removed.

An edit cannot inject anything: whatever the key, subsection and value, the edited file parses to the entries before the edit with exactly that one change. Values are quoted and escaped as git does, and a subsection that cannot be written in a header (newline, NUL) is ErrInvalidKey.

func Parse

func Parse(name string, data []byte) (*File, error)

Parse parses a configuration file. name is used in errors and in the Origin of entries, which have ScopeFile. A file git rejects is a *SyntaxError. An entry before any section header, which git reads under a key no command can ask for, is kept in the file but is not an entry of Config.

data may come from anyone (a .gitmodules blob from a remote): Parse never panics, and its time and memory are linear in len(data). It applies the limits of a load to its one file: more than 4 MiB, more than 2¹⁶ entries and distinct section headers, or more than 2¹⁸ pieces (blank lines, comments, ...) is a *FileError wrapping ErrLoadLimit.

func (*File) Add

func (f *File) Add(key, value string) error

Add adds a value to key, keeping existing ones, like `git config --add`. The new value is read last, so it is the one Config.Get returns.

func (*File) Bytes

func (f *File) Bytes() []byte

Bytes returns the content of the file. It is identical to the parsed data if the file was not edited.

func (*File) Config

func (f *File) Config() *Config

Config returns an immutable snapshot of the entries of this file. Include directives are not followed; use Env to load configuration with includes.

After the callback of Env.UpdateFile returns, Bytes and Config still give the last content, but edits fail with ErrClosed.

The snapshot is kept until the next edit, so calling Config repeatedly, for example to read values inside an Env.UpdateFile callback, is cheap.

func (*File) RemoveSection

func (f *File) RemoveSection(section string) (removed bool, err error)

RemoveSection removes section [section] and all its entries, like `git config --remove-section <section>`, and reports whether it was present. Its subsections are kept.

func (*File) RemoveSubsection

func (f *File) RemoveSubsection(section, sub string) (removed bool, err error)

RemoveSubsection removes subsection [section "sub"] and all its entries, like `git config --remove-section <section>.<sub>`, and reports whether it was present. sub "" is the empty subsection [section ""].

func (*File) RenameSection

func (f *File) RenameSection(oldName, newName string) error

RenameSection renames section [oldName] to [newName], like `git config --rename-section`. Subsections are not renamed. It fails with ErrNotFound if the section is absent.

Only the headers change, and whether [newName] exists is not checked: if it does, both sections remain and read as one, as with git. To refuse instead, check f.Config() first, in the same Env.UpdateFile callback.

func (*File) RenameSubsection

func (f *File) RenameSubsection(section, oldSub, newSub string) error

RenameSubsection renames subsection [section "oldSub"] to [section "newSub"], as `git remote rename` does for the remote's section. It fails with ErrNotFound if the subsection is absent. As for RenameSection, an existing [section "newSub"] is not checked: `git remote rename` refuses one, and a caller doing the same checks f.Config().Subsections first.

func (*File) ReplaceAll

func (f *File) ReplaceAll(key, value string) error

ReplaceAll replaces all values of key with a single value, or adds it if key is absent, like `git config --replace-all <key> <value>`. The new value takes the place of the last old one.

func (*File) Set

func (f *File) Set(key, value string) error

Set sets the value of key, like `git config <key> <value>`, adding it if absent. It fails with ErrMultipleValues if key has several values.

If the value is already set, the file is left unchanged. git would rewrite the line in its own format; keeping it is the one deliberate difference with git's edits. An implicit entry ("[a]\n\tk") is never already set, even for value "": it reads as true, and "k = " as false.

func (*File) Unset

func (f *File) Unset(key string) (removed bool, err error)

Unset removes the value of key, like `git config --unset <key>`, and reports whether there was one. It fails with ErrMultipleValues if key has several values.

func (*File) UnsetAll

func (f *File) UnsetAll(key string) (removed int, err error)

UnsetAll removes all values of key, like `git config --unset-all <key>`, and returns how many were removed.

type FileError

type FileError struct {
	Path string
	Err  error
}

FileError reports a failure to load, lock or write a file. Path is the file concerned: the lock file for ErrLocked, as in git's "could not lock config file".

func (*FileError) Error

func (e *FileError) Error() string

func (*FileError) Unwrap

func (e *FileError) Unwrap() error

type Key

type Key struct {
	Section string

	// Subsection is meaningful only if HasSubsection is set. git
	// distinguishes an empty subsection ("a..k", header [a ""]) from none
	// ("a.k", header [a]).
	Subsection    string
	HasSubsection bool

	Name string
}

Key is the canonical form of a key, as printed by `git config --list`: section and name in lower case, subsection verbatim.

func ParseKey

func ParseKey(s string) (Key, error)

ParseKey parses "section.name" or "section.subsection.name" as git does: the subsection is everything between the first and the last dot, section and name are lowered. A key git rejects is a *KeyError wrapping ErrInvalidKey: the section must hold only letters, digits and '-', the name must also start with a letter, and the subsection may hold anything but a newline or a NUL byte. Reads treat an invalid key as absent; only ParseKey and edits report it.

func (Key) String

func (k Key) String() string

String returns the key as "section.name" or "section.subsection.name" ("section..name" for an empty subsection).

type KeyError

type KeyError struct {
	Key string // as given by the caller
	Err error
}

KeyError reports an edit or a command-line entry that failed for a key or section name.

func (*KeyError) Error

func (e *KeyError) Error() string

func (*KeyError) Unwrap

func (e *KeyError) Unwrap() error

type Origin

type Origin struct {
	// Scope is the scope the entry was loaded for. Entries of Env.LoadFile,
	// and of the Config of a File from Parse or Env.UpdateFile, have
	// ScopeFile (git reports "command" for `git config -f`). A File from
	// Env.UpdateScope has that scope.
	Scope Scope

	// File is the file that holds the entry, possibly an included one, or
	// "" for the command line. Like git, an included file's relative path
	// is appended to the including file's directory without cleaning it:
	// "/home/u/.config/git/../work.inc". On Windows the separators are
	// kept as given, where git shows some paths with '/'.
	File string

	Line int // 1-based; 0 for git -c
}

Origin tells where an entry comes from, as `git config --show-scope --show-origin` does.

type Scope

type Scope int

Scope is one layer of git's configuration. Values from later scopes take precedence over earlier ones.

ScopeFile, the zero value, is not a layer: it marks entries of a file read on its own (Parse, Env.LoadFile, Env.UpdateFile), which may come from anyone. git reports such entries as "command"; this package keeps them apart so that they are never mistaken for the protected command scope.

const (
	ScopeFile     Scope = iota // a file read on its own, as with git config -f
	ScopeSystem                // /etc/gitconfig or GIT_CONFIG_SYSTEM
	ScopeGlobal                // ~/.gitconfig, the XDG file, or GIT_CONFIG_GLOBAL
	ScopeLocal                 // the repository's config
	ScopeWorktree              // config.worktree, if extensions.worktreeConfig is enabled
	ScopeCommand               // git -c, GIT_CONFIG_COUNT/KEY/VALUE; read-only
)

func (Scope) String

func (s Scope) String() string

String returns the scope as git names it ("system", "global", ...), and "file" for ScopeFile.

type SyntaxError

type SyntaxError struct {
	File string
	Line int
	Msg  string
}

SyntaxError reports a file git refuses to parse. Msg describes the problem but never quotes the file's content, which may be a file that is not a configuration file at all, reached through an include.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

type ValueError

type ValueError struct {
	Entry Entry
	Type  string // "bool", "int", "path"
	Err   error  // the reason, e.g. strconv.ErrRange; may be nil
}

ValueError reports a value that cannot be read as the requested type. Its message quotes the value with %q, so that control characters in a value cannot reach a terminal.

func (*ValueError) Error

func (e *ValueError) Error() string

func (*ValueError) Unwrap

func (e *ValueError) Unwrap() error

Jump to

Keyboard shortcuts

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