Skip to content

Commit 3407e2e

Browse files
committed
fix: move original starship config to backup if existent
1 parent b828a68 commit 3407e2e

4 files changed

Lines changed: 126 additions & 8 deletions

File tree

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,24 @@ stellar update
6161

6262
## Local configs
6363

64+
### Automatic backup of your original config
65+
66+
When you first use `stellar apply`, if you have an existing `~/.config/starship.toml` that's not managed by stellar, it will be automatically backed up to `~/.config/stellar/<username>/backup/latest.toml` before creating the symlink.
67+
68+
This ensures your carefully crafted config is never lost! You can apply it anytime with:
69+
```bash
70+
stellar apply <username>/backup
71+
```
72+
73+
You can also rename the backup folder to give it a proper theme name:
74+
```bash
75+
mv ~/.config/stellar/<username>/backup ~/.config/stellar/<username>/my-custom-theme
76+
stellar apply <username>/my-custom-theme
77+
```
78+
6479
### Switching between local configs
6580

66-
You can just put your own configs under `~/.config/stellar/local/<your-theme>/latest.toml`,
81+
You can just put your own configs under `~/.config/stellar/local/<your-theme>/latest.toml`,
6782
and then switch to them using `stellar apply local/<your-theme>`.
6883

6984
> [!INFO]
@@ -72,7 +87,7 @@ and then switch to them using `stellar apply local/<your-theme>`.
7287
7388
### Customizing themes
7489

75-
You can similarily copy one existing downloaded theme to the `stellar/local` folder, edit it,
90+
You can similarily copy one existing downloaded theme to the `stellar/local` folder, edit it,
7691
and then switch to it using `stellar apply`.
7792

7893
> [!INFO]

cmd/apply.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"fmt"
55
"log"
6+
"os/user"
67

78
"github.com/a3chron/stellar/internal/api"
89
"github.com/a3chron/stellar/internal/cache"
@@ -13,6 +14,14 @@ import (
1314
"github.com/spf13/cobra"
1415
)
1516

17+
func getCurrentUsername() string {
18+
currentUser, err := user.Current()
19+
if err != nil {
20+
return "local"
21+
}
22+
return currentUser.Username
23+
}
24+
1625
var applyCmd = &cobra.Command{
1726
Use: "apply [author/theme[@version]]",
1827
Short: "Apply a Starship theme",
@@ -85,10 +94,18 @@ var applyCmd = &cobra.Command{
8594
}
8695

8796
// 6. Create symlink
88-
if err := symlink.CreateSymlink(themePath); err != nil {
97+
backupPath, err := symlink.CreateSymlink(themePath)
98+
if err != nil {
8999
return err
90100
}
91101

102+
// Notify user if their original config was backed up
103+
if backupPath != "" {
104+
color.Yellow("Your original starship.toml has been backed up to:")
105+
color.Yellow(" %s", backupPath)
106+
color.Cyan("\nYou can apply it later with: stellar apply %s/backup \n", getCurrentUsername())
107+
}
108+
92109
color.Green("Applied %s", t)
93110
return nil
94111
},

cmd/rollback.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ var rollbackCmd = &cobra.Command{
7575
}
7676

7777
// Update symlink
78-
if err := symlink.CreateSymlink(cfg.CurrentPath); err != nil {
78+
_, err = symlink.CreateSymlink(cfg.CurrentPath)
79+
if err != nil {
7980
return fmt.Errorf("failed to update symlink: %w", err)
8081
}
8182

internal/symlink/symlink.go

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package symlink
22

33
import (
4+
"fmt"
5+
"io"
46
"log"
57
"os"
8+
"os/user"
69
"path/filepath"
710
)
811

@@ -14,19 +17,101 @@ func StarshipConfigPath() (string, error) {
1417
return filepath.Join(home, ".config", "starship.toml"), nil
1518
}
1619

17-
func CreateSymlink(target string) error {
20+
// isSymlink checks if the given path is a symlink
21+
func isSymlink(path string) bool {
22+
info, err := os.Lstat(path)
23+
if err != nil {
24+
return false
25+
}
26+
return info.Mode()&os.ModeSymlink != 0
27+
}
28+
29+
// backupOriginalConfig backs up the user's original starship.toml to ~/.config/stellar/<username>/backup/latest.toml
30+
// Returns the backup path if successful, empty string otherwise
31+
func backupOriginalConfig(configPath string) (backupPath string, err error) {
32+
// Check if the file exists and is NOT a symlink
33+
if _, err := os.Stat(configPath); os.IsNotExist(err) {
34+
return "", nil // No file to back up
35+
}
36+
37+
if isSymlink(configPath) {
38+
return "", nil // Already a symlink, no need to back up
39+
}
40+
41+
// Get the current user's username
42+
currentUser, err := user.Current()
43+
if err != nil {
44+
return "", fmt.Errorf("failed to get current user: %w", err)
45+
}
46+
47+
// Construct backup path: ~/.config/stellar/<username>/backup/latest.toml
48+
home, err := os.UserHomeDir()
49+
if err != nil {
50+
return "", fmt.Errorf("failed to get home directory: %w", err)
51+
}
52+
53+
backupDir := filepath.Join(home, ".config", "stellar", currentUser.Username, "backup")
54+
backupPath = filepath.Join(backupDir, "latest.toml")
55+
56+
// Create backup directory
57+
if err := os.MkdirAll(backupDir, 0755); err != nil {
58+
return "", fmt.Errorf("failed to create backup directory: %w", err)
59+
}
60+
61+
// Copy the original file to backup location
62+
source, err := os.Open(configPath)
63+
if err != nil {
64+
return "", fmt.Errorf("failed to open original config: %w", err)
65+
}
66+
defer func() {
67+
if cerr := source.Close(); cerr != nil && err == nil {
68+
err = fmt.Errorf("failed to close source file: %w", cerr)
69+
}
70+
}()
71+
72+
destination, err := os.Create(backupPath)
73+
if err != nil {
74+
return "", fmt.Errorf("failed to create backup file: %w", err)
75+
}
76+
defer func() {
77+
if cerr := destination.Close(); cerr != nil && err == nil {
78+
err = fmt.Errorf("failed to close destination file: %w", cerr)
79+
}
80+
}()
81+
82+
if _, err := io.Copy(destination, source); err != nil {
83+
return "", fmt.Errorf("failed to copy config to backup: %w", err)
84+
}
85+
86+
return backupPath, nil
87+
}
88+
89+
// CreateSymlink creates a symlink from ~/.config/starship.toml to the target file.
90+
// If an original (non-symlink) starship.toml exists, it's backed up first.
91+
// Returns the backup path if a backup was created (empty string if no backup was needed).
92+
func CreateSymlink(target string) (backupPath string, err error) {
1893
configPath, err := StarshipConfigPath()
1994
if err != nil {
20-
return err
95+
return "", err
96+
}
97+
98+
// Back up original config if it exists and is not a symlink
99+
backupPath, err = backupOriginalConfig(configPath)
100+
if err != nil {
101+
return "", fmt.Errorf("failed to backup original config: %w", err)
21102
}
22103

23104
// Remove existing symlink/file
24-
if err := os.Remove(configPath); err != nil {
105+
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
25106
log.Printf("warning: failed to remove %s: %v", configPath, err)
26107
}
27108

28109
// Create new symlink
29-
return os.Symlink(target, configPath)
110+
if err := os.Symlink(target, configPath); err != nil {
111+
return backupPath, err
112+
}
113+
114+
return backupPath, nil
30115
}
31116

32117
func GetCurrentTarget() (string, error) {

0 commit comments

Comments
 (0)