Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,13 @@ The application is structured into four main components that communicate asynchr

## Setup & Configuration

Configuration is managed securely through JSON files and an interactive TUI setup wizard.
Configuration is managed locally through a JSON file and an interactive TUI setup wizard.

### Configuration Files

`gitpoll` uses a hierarchical JSON configuration system. You do not need to create these files manually; the application provides an interactive setup wizard that will generate them for you if they are missing.
`gitpoll` uses a local JSON configuration system. You do not need to create this file manually; the application provides an interactive, paginated setup wizard that will generate it for you if it is missing.

- **Global Configuration**: `~/.config/gitpoll/config.json`
- **Local Configuration**: `./.gitpoll.json` (Overrides global configuration)

When the application starts, it will merge the local configuration over the global configuration.
- **Local Configuration**: `./gitpoll.config.json`

### Running the Application

Expand All @@ -75,8 +72,9 @@ When the application starts, it will merge the local configuration over the glob
./gitpoll
```

3. If this is your first time running the program or if the configuration is incomplete, an interactive **Setup Wizard** will launch.
4. Follow the on-screen prompts in the terminal to configure the repository URL, branch, local directory, polling interval, and execution command. The wizard will save your preferences to the appropriate configuration file.
3. If this is your first time running the program or if the configuration is incomplete, an interactive **Setup Wizard** will launch, displaying the project's ASCII art header.
4. Follow the paginated on-screen prompts in the terminal to configure the repository URL, local directory, branch, execution command, and polling interval. You can leave fields blank to use the suggested defaults shown in brackets.
5. Review the summary of your settings and confirm to save them to `./gitpoll.config.json`.

## License

Expand Down
71 changes: 12 additions & 59 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,9 @@ func UnmarshalString(data string, v any) error {
return Unmarshal([]byte(data), v)
}

// GetGlobalConfigPath returns the path to the global configuration file
func GetGlobalConfigPath() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, ".config", "gitpoll", "config.json"), nil
}

// GetLocalConfigPath returns the path to the local configuration file
func GetLocalConfigPath() string {
return "./.gitpoll.json"
return "./gitpoll.config.json"
}

// Save writes the configuration to the specified path
Expand Down Expand Up @@ -92,63 +83,25 @@ func loadFromFile(path string) (*Config, error) {
return &cfg, nil
}

// Merge overrides the fields of base with the non-empty fields of override
func Merge(base, override *Config) *Config {
if base == nil {
base = &Config{}
}

merged := *base // Copy base

if override == nil {
return &merged
}

if override.RepoURL != "" {
merged.RepoURL = override.RepoURL
}
if override.RepoDir != "" {
merged.RepoDir = override.RepoDir
}
if override.Branch != "" {
merged.Branch = override.Branch
}
if override.Command != "" {
merged.Command = override.Command
}
if override.Interval != 0 {
merged.Interval = override.Interval
}

return &merged
}

// LoadConfig reads global and local configs, merges them, and checks if the config is fully valid
// LoadConfig reads local config and checks if the config is fully valid
func LoadConfig() (*Config, bool, error) {
globalPath, err := GetGlobalConfigPath()
if err != nil {
return nil, false, fmt.Errorf("failed to get global config path: %w", err)
}
localPath := GetLocalConfigPath()

globalCfg, err := loadFromFile(globalPath)
cfg, err := loadFromFile(localPath)
if err != nil {
return nil, false, fmt.Errorf("failed to load global config: %w", err)
return nil, false, fmt.Errorf("failed to load config: %w", err)
}

localCfg, err := loadFromFile(localPath)
if err != nil {
return nil, false, fmt.Errorf("failed to load local config: %w", err)
if cfg == nil {
return nil, false, nil
}

mergedCfg := Merge(globalCfg, localCfg)

// Determine if config is completely valid
isValid := mergedCfg.RepoURL != "" &&
mergedCfg.RepoDir != "" &&
mergedCfg.Branch != "" &&
mergedCfg.Command != "" &&
mergedCfg.Interval > 0
isValid := cfg.RepoURL != "" &&
cfg.RepoDir != "" &&
cfg.Branch != "" &&
cfg.Command != "" &&
cfg.Interval > 0

return mergedCfg, isValid, nil
return cfg, isValid, nil
}
29 changes: 0 additions & 29 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,35 +53,6 @@ func TestMarshalUnmarshalString(t *testing.T) {
}
}

func TestMerge(t *testing.T) {
global := &Config{
RepoURL: "global_url",
Branch: "global_branch",
Interval: 60 * time.Second,
}

local := &Config{
Branch: "local_branch",
Command: "local_command",
Interval: 30 * time.Second,
}

merged := Merge(global, local)

if merged.RepoURL != "global_url" {
t.Errorf("Expected global_url, got %s", merged.RepoURL)
}
if merged.Branch != "local_branch" {
t.Errorf("Expected local_branch, got %s", merged.Branch)
}
if merged.Command != "local_command" {
t.Errorf("Expected local_command, got %s", merged.Command)
}
if merged.Interval != 30*time.Second {
t.Errorf("Expected 30s, got %v", merged.Interval)
}
}

func TestLoadConfig_MissingFile(t *testing.T) {
// Creating temp dir to override paths
tempDir := t.TempDir()
Expand Down
Loading