chore: modernize CI and update Go toolchain

- Bump Go from 1.19 to 1.26 and update all dependencies
- Rewrite CI workflow with matrix strategy (Linux, macOS, Windows)
- Update GitHub Actions to current versions (checkout@v4, setup-go@v5)
- Update CodeQL actions from v1 to v3
- Fix cross-platform bug in mock/path.go (path.Join -> filepath.Join)
- Clean up dependabot config (weekly schedule, remove stale ignore)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Allen Lane
2026-02-14 20:58:51 -05:00
parent cc85a4bdb1
commit 2a19755804
657 changed files with 49050 additions and 32001 deletions

View File

@@ -8,7 +8,7 @@
// the host name to match on ("example.com"), and the second argument is the key
// you want to retrieve ("Port"). The keywords are case insensitive.
//
// port := ssh_config.Get("myhost", "Port")
// port := ssh_config.Get("myhost", "Port")
//
// You can also manipulate an SSH config file and then print it or write it back
// to disk.
@@ -24,9 +24,6 @@
//
// // Write the cfg back to disk:
// fmt.Println(cfg.String())
//
// BUG: the Match directive is currently unsupported; parsing a config with
// a Match directive will trigger an error.
package ssh_config
import (
@@ -43,7 +40,7 @@ import (
"sync"
)
const version = "1.2"
const version = "1.5.0"
var _ = version
@@ -53,6 +50,8 @@ type configFinder func() string
// files are parsed and cached the first time Get() or GetStrict() is called.
type UserSettings struct {
IgnoreErrors bool
customConfig *Config
customConfigFinder configFinder
systemConfig *Config
systemConfigFinder configFinder
userConfig *Config
@@ -203,6 +202,13 @@ func (u *UserSettings) GetStrict(alias, key string) (string, error) {
if u.onceErr != nil && u.IgnoreErrors == false {
return "", u.onceErr
}
// TODO this is getting repetitive
if u.customConfig != nil {
val, err := findVal(u.customConfig, alias, key)
if err != nil || val != "" {
return val, err
}
}
val, err := findVal(u.userConfig, alias, key)
if err != nil || val != "" {
return val, err
@@ -228,6 +234,12 @@ func (u *UserSettings) GetAllStrict(alias, key string) ([]string, error) {
if u.onceErr != nil && u.IgnoreErrors == false {
return nil, u.onceErr
}
if u.customConfig != nil {
val, err := findAll(u.customConfig, alias, key)
if err != nil || val != nil {
return val, err
}
}
val, err := findAll(u.userConfig, alias, key)
if err != nil || val != nil {
return val, err
@@ -243,16 +255,38 @@ func (u *UserSettings) GetAllStrict(alias, key string) ([]string, error) {
return []string{}, nil
}
// ConfigFinder will invoke f to try to find a ssh config file in a custom
// location on disk, instead of in /etc/ssh or $HOME/.ssh. f should return the
// name of a file containing SSH configuration.
//
// ConfigFinder must be invoked before any calls to Get or GetStrict and panics
// if f is nil. Most users should not need to use this function.
func (u *UserSettings) ConfigFinder(f func() string) {
if f == nil {
panic("cannot call ConfigFinder with nil function")
}
u.customConfigFinder = f
}
func (u *UserSettings) doLoadConfigs() {
u.loadConfigs.Do(func() {
// can't parse user file, that's ok.
var filename string
var err error
if u.customConfigFinder != nil {
filename = u.customConfigFinder()
u.customConfig, err = parseFile(filename)
// IsNotExist should be returned because a user specified this
// function - not existing likely means they made an error
if err != nil {
u.onceErr = err
}
return
}
if u.userConfigFinder == nil {
filename = userConfigFinder()
} else {
filename = u.userConfigFinder()
}
var err error
u.userConfig, err = parseFile(filename)
//lint:ignore S1002 I prefer it this way
if err != nil && os.IsNotExist(err) == false {
@@ -351,9 +385,6 @@ func (c *Config) Get(alias, key string) (string, error) {
case *KV:
// "keys are case insensitive" per the spec
lkey := strings.ToLower(t.Key)
if lkey == "match" {
panic("can't handle Match directives")
}
if lkey == lowerKey {
return t.Value, nil
}
@@ -386,9 +417,6 @@ func (c *Config) GetAll(alias, key string) ([]string, error) {
case *KV:
// "keys are case insensitive" per the spec
lkey := strings.ToLower(t.Key)
if lkey == "match" {
panic("can't handle Match directives")
}
if lkey == lowerKey {
all = append(all, t.Value)
}
@@ -433,6 +461,9 @@ type Pattern struct {
// String prints the string representation of the pattern.
func (p Pattern) String() string {
if p.not {
return "!" + p.str
}
return p.str
}
@@ -491,7 +522,7 @@ func NewPattern(s string) (*Pattern, error) {
return &Pattern{str: s, regex: r, not: negated}, nil
}
// Host describes a Host directive and the keywords that follow it.
// Host describes a Host or Match directive and the keywords that follow it.
type Host struct {
// A list of host patterns that should match this host.
Patterns []*Pattern
@@ -506,6 +537,11 @@ type Host struct {
leadingSpace int // TODO: handle spaces vs tabs here.
// The file starts with an implicit "Host *" declaration.
implicit bool
// isMatch is true if this block was created by a Match directive.
isMatch bool
// matchKeyword stores the original text after "Match" (e.g. "Host" or
// "all") so we can round-trip correctly.
matchKeyword string
}
// Matches returns true if the Host matches for the given alias. For
@@ -537,17 +573,36 @@ func (h *Host) String() string {
//lint:ignore S1002 I prefer to write it this way
if h.implicit == false {
buf.WriteString(strings.Repeat(" ", int(h.leadingSpace)))
buf.WriteString("Host")
if h.hasEquals {
buf.WriteString(" = ")
} else {
buf.WriteString(" ")
}
for i, pat := range h.Patterns {
buf.WriteString(pat.String())
if i < len(h.Patterns)-1 {
if h.isMatch {
buf.WriteString("Match")
if h.hasEquals {
buf.WriteString(" = ")
} else {
buf.WriteString(" ")
}
buf.WriteString(h.matchKeyword)
if !strings.EqualFold(h.matchKeyword, "all") {
buf.WriteString(" ")
for i, pat := range h.Patterns {
buf.WriteString(pat.String())
if i < len(h.Patterns)-1 {
buf.WriteString(" ")
}
}
}
} else {
buf.WriteString("Host")
if h.hasEquals {
buf.WriteString(" = ")
} else {
buf.WriteString(" ")
}
for i, pat := range h.Patterns {
buf.WriteString(pat.String())
if i < len(h.Patterns)-1 {
buf.WriteString(" ")
}
}
}
buf.WriteString(h.spaceBeforeComment)
if h.EOLComment != "" {