all repos

mugit @ 8703591

馃惍 git server that your cow will love
3 files changed, 55 insertions(+), 98 deletions(-)
config: file path validating strategy
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-13 00:07:29 +0200
Authored at: 2026-02-12 22:15:35 +0200
Change ID: lwxymwsrprsnylporrqztkzquyxktwpv
Parent: c9e6e1a
M internal/cli/cli.go
路路路
        35
        35
         			},

      
        36
        36
         		},

      
        37
        37
         		Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {

      
        38
        
        -			loadedCfg, err := config.Load(cmd.String("config"))

      
        
        38
        +			loadedCfg, err := config.Load(

      
        
        39
        +				config.PathOrDefault(cmd.String("config")))

      
        39
        40
         			if err != nil {

      
        40
        41
         				return ctx, err

      
        41
        42
         			}

      
M internal/config/config.go
路路路
        49
        49
         	Mirror MirrorConfig `yaml:"mirror"`

      
        50
        50
         }

      
        51
        51
         

      
        52
        
        -// Load loads configuration with the following priority:

      
        53
        
        -// 1. User provided fpath (if provided and exists)

      
        54
        
        -// 2. /var/lib/mugit/config.yaml

      
        55
        
        -// 3. $XDG_CONFIG_HOME/mugit/config.yaml or $HOME/.config/mugit/config.yaml

      
        56
        52
         func Load(fpath string) (*Config, error) {

      
        57
        
        -	// 4. /etc/mugit/config.yaml

      
        58
        
        -	configPath, err := findConfigFile(fpath)

      
        59
        
        -	if err != nil {

      
        60
        
        -		return nil, err

      
        61
        
        -	}

      
        62
        
        -

      
        63
        
        -	configBytes, err := os.ReadFile(configPath)

      
        
        53
        +	configBytes, err := os.ReadFile(fpath)

      
        64
        54
         	if err != nil {

      
        65
        55
         		return nil, err

      
        66
        56
         	}

      路路路
        83
        73
         	return &config, nil

      
        84
        74
         }

      
        85
        75
         

      
        
        76
        +// PathOrDefault uses userPath, if it's "", or invalid path, will default to one of those(in priority order)

      
        
        77
        +// 1. ./config.yaml

      
        
        78
        +// 2. /etc/mugit.yaml

      
        
        79
        +// 3. /var/lib/mugit/config.yaml

      
        
        80
        +func PathOrDefault(userPath string) string {

      
        
        81
        +	return pathOrDefaultWithCandidates(userPath, []string{

      
        
        82
        +		"./config.yaml",

      
        
        83
        +		"/etc/mugit.yaml",

      
        
        84
        +		"/var/lib/mugit/config.yaml",

      
        
        85
        +	})

      
        
        86
        +}

      
        
        87
        +

      
        
        88
        +func pathOrDefaultWithCandidates(path string, candidates []string) string {

      
        
        89
        +	if isFileExists(path) {

      
        
        90
        +		return path

      
        
        91
        +	}

      
        
        92
        +

      
        
        93
        +	for _, fpath := range candidates {

      
        
        94
        +		if isFileExists(fpath) {

      
        
        95
        +			return fpath

      
        
        96
        +		}

      
        
        97
        +	}

      
        
        98
        +

      
        
        99
        +	return ""

      
        
        100
        +}

      
        
        101
        +

      
        86
        102
         func (c *Config) ensureDefaults() {

      
        87
        103
         	// ports

      
        88
        104
         	if c.Server.Port == 0 {

      路路路
        116
        132
         	if c.Mirror.Interval == "" {

      
        117
        133
         		c.Mirror.Interval = "8h"

      
        118
        134
         	}

      
        119
        
        -}

      
        120
        
        -

      
        121
        
        -func findConfigFile(userPath string) (string, error) {

      
        122
        
        -	if userPath != "" {

      
        123
        
        -		if _, err := os.Stat(userPath); err == nil {

      
        124
        
        -			return userPath, nil

      
        125
        
        -		}

      
        126
        
        -	}

      
        127
        
        -

      
        128
        
        -	path := "/var/lib/mugit/config.yaml"

      
        129
        
        -	if _, err := os.Stat(path); err == nil {

      
        130
        
        -		return path, nil

      
        131
        
        -	}

      
        132
        
        -

      
        133
        
        -	if configDir, err := os.UserConfigDir(); err == nil {

      
        134
        
        -		p := filepath.Join(configDir, "mugit", "config.yaml")

      
        135
        
        -		if _, err := os.Stat(p); err == nil {

      
        136
        
        -			return p, nil

      
        137
        
        -		}

      
        138
        
        -	}

      
        139
        
        -

      
        140
        
        -	path = "/etc/mugit/config.yaml"

      
        141
        
        -	if _, err := os.Stat(path); err == nil {

      
        142
        
        -		return path, nil

      
        143
        
        -	}

      
        144
        
        -

      
        145
        
        -	return "", ErrConfigNotFound

      
        146
        135
         }

      
        147
        136
         

      
        148
        137
         func isFileExists(path string) bool {

      
M internal/config/config_test.go
路路路
        8
        8
         	"olexsmir.xyz/x/is"

      
        9
        9
         )

      
        10
        10
         

      
        11
        
        -func TestFindConfigFile(t *testing.T) {

      
        12
        
        -	t.Run("returns user provided path when it exists", func(t *testing.T) {

      
        13
        
        -		path, err := findConfigFile("testdata/hostkey")

      
        14
        
        -		is.Err(t, err, nil)

      
        15
        
        -		is.Equal(t, path, "testdata/hostkey")

      
        16
        
        -	})

      
        
        11
        +func TestPathOrDefaultWithCandidates(t *testing.T) {

      
        
        12
        +	first := candidateFile(t, "first.yaml")

      
        
        13
        +	second := candidateFile(t, "second.yaml")

      
        
        14
        +	third := candidateFile(t, "third.yaml")

      
        17
        15
         

      
        18
        
        -	t.Run("falls back when user path doesn't exist", func(t *testing.T) {

      
        19
        
        -		path, err := findConfigFile("/nonexistent/user/config.yaml")

      
        20
        
        -		if err != nil {

      
        21
        
        -			is.Err(t, err, ErrConfigNotFound)

      
        22
        
        -		} else {

      
        23
        
        -			_, statErr := os.Stat(path)

      
        24
        
        -			is.Err(t, statErr, nil)

      
        
        16
        +	t.Run("returns user path when exists", func(t *testing.T) {

      
        
        17
        +		userPath := candidateFile(t, "user.yaml")

      
        
        18
        +		candidates := []string{first, second, third}

      
        
        19
        +		got := pathOrDefaultWithCandidates(userPath, candidates)

      
        
        20
        +		if got != userPath {

      
        
        21
        +			t.Errorf("got %q, want %q", got, userPath)

      
        25
        22
         		}

      
        26
        23
         	})

      
        27
        24
         

      
        28
        
        -	t.Run("finds config in user config directory", func(t *testing.T) {

      
        29
        
        -		tmpDir := t.TempDir()

      
        30
        
        -		configDir := filepath.Join(tmpDir, "mugit")

      
        31
        
        -		if err := os.MkdirAll(configDir, 0o755); err != nil {

      
        32
        
        -			t.Fatal(err)

      
        33
        
        -		}

      
        34
        
        -		configFile := filepath.Join(configDir, "config.yaml")

      
        35
        
        -		if err := os.WriteFile(configFile, []byte("test"), 0o644); err != nil {

      
        36
        
        -			t.Fatal(err)

      
        37
        
        -		}

      
        38
        
        -

      
        39
        
        -		t.Setenv("XDG_CONFIG_HOME", tmpDir)

      
        40
        
        -

      
        41
        
        -		path, err := findConfigFile("")

      
        42
        
        -		is.Err(t, err, nil)

      
        43
        
        -		is.Equal(t, path, configFile)

      
        
        25
        +	t.Run("returns first existing candidate", func(t *testing.T) {

      
        
        26
        +		candidates := []string{first, second, third}

      
        
        27
        +		got := pathOrDefaultWithCandidates("", candidates)

      
        
        28
        +		is.Equal(t, got, first)

      
        44
        29
         	})

      
        45
        30
         

      
        46
        
        -	t.Run("returns error when no config found anywhere", func(t *testing.T) {

      
        47
        
        -		t.Setenv("XDG_CONFIG_HOME", "/nonexistent")

      
        48
        
        -		t.Setenv("HOME", "/nonexistent")

      
        49
        
        -

      
        50
        
        -		path, err := findConfigFile("/nonexistent/config.yaml")

      
        51
        
        -		is.Err(t, err, ErrConfigNotFound)

      
        52
        
        -		is.Equal(t, path, "")

      
        53
        
        -	})

      
        54
        
        -

      
        55
        
        -	t.Run("prefers data directory over user config", func(t *testing.T) {

      
        56
        
        -		tmpDir := t.TempDir()

      
        57
        
        -		configDir := filepath.Join(tmpDir, "mugit")

      
        58
        
        -		if err := os.MkdirAll(configDir, 0o755); err != nil {

      
        59
        
        -			t.Fatal(err)

      
        60
        
        -		}

      
        61
        
        -		userConfigFile := filepath.Join(configDir, "config.yaml")

      
        62
        
        -		if err := os.WriteFile(userConfigFile, []byte("user config"), 0o644); err != nil {

      
        63
        
        -			t.Fatal(err)

      
        64
        
        -		}

      
        65
        
        -

      
        66
        
        -		t.Setenv("XDG_CONFIG_HOME", tmpDir)

      
        67
        
        -

      
        68
        
        -		path, err := findConfigFile("")

      
        69
        
        -		is.Err(t, err, nil)

      
        70
        
        -

      
        71
        
        -		if path == "/var/lib/mugit/config.yaml" {

      
        72
        
        -			_, statErr := os.Stat(path)

      
        73
        
        -			is.Err(t, statErr, nil)

      
        74
        
        -		} else {

      
        75
        
        -			is.Equal(t, path, userConfigFile)

      
        
        31
        +	t.Run("returns empty when nothing exists", func(t *testing.T) {

      
        
        32
        +		candidates := []string{}

      
        
        33
        +		got := pathOrDefaultWithCandidates("", candidates)

      
        
        34
        +		if got != "" {

      
        
        35
        +			t.Errorf("got %q, want empty", got)

      
        76
        36
         		}

      
        77
        37
         	})

      
        78
        38
         }

      
        
        39
        +

      
        
        40
        +func candidateFile(t *testing.T, name string) string {

      
        
        41
        +	t.Helper()

      
        
        42
        +	out := filepath.Join(t.TempDir(), name)

      
        
        43
        +	os.WriteFile(out, []byte("test"), 0o644)

      
        
        44
        +	return out

      
        
        45
        +}