Skip to content

Commit 2197126

Browse files
committed
pr comments
1 parent 43c8b4f commit 2197126

File tree

2 files changed

+88
-25
lines changed

2 files changed

+88
-25
lines changed

cli/config/file.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ func (r Root) Organization() File {
2121
return File(filepath.Join(string(r), "organization"))
2222
}
2323

24+
func (r Root) DotfilesURL() File {
25+
return File(filepath.Join(string(r), "dotfilesurl"))
26+
}
27+
2428
// File provides convenience methods for interacting with *os.File.
2529
type File string
2630

cli/dotfiles.go

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package cli
22

33
import (
4+
"errors"
45
"fmt"
56
"io/fs"
67
"os"
78
"os/exec"
89
"path/filepath"
910
"strings"
11+
"time"
1012

1113
"github.com/spf13/cobra"
1214
"golang.org/x/xerrors"
@@ -17,7 +19,7 @@ import (
1719

1820
func dotfiles() *cobra.Command {
1921
var (
20-
homeDir string
22+
symlinkDir string
2123
)
2224
cmd := &cobra.Command{
2325
Use: "dotfiles [git_repo_url]",
@@ -28,11 +30,9 @@ func dotfiles() *cobra.Command {
2830
var (
2931
dotfilesRepoDir = "dotfiles"
3032
gitRepo = args[0]
31-
cfgDir = string(createConfig(cmd))
33+
cfg = createConfig(cmd)
34+
cfgDir = string(cfg)
3235
dotfilesDir = filepath.Join(cfgDir, dotfilesRepoDir)
33-
subcommands = []string{"clone", args[0], dotfilesRepoDir}
34-
gitCmdDir = cfgDir
35-
promptText = fmt.Sprintf("Cloning %s into directory %s.\n Continue?", gitRepo, dotfilesDir)
3636
// This follows the same pattern outlined by others in the market:
3737
// https://github.com/coder/coder/pull/1696#issue-1245742312
3838
installScriptSet = []string{
@@ -53,14 +53,49 @@ func dotfiles() *cobra.Command {
5353
return xerrors.Errorf("checking dir %s: %w", dotfilesDir, err)
5454
}
5555

56-
// if repo exists already do a git pull instead of clone
56+
moved := false
57+
if dotfilesExists {
58+
du, err := cfg.DotfilesURL().Read()
59+
if err != nil {
60+
return xerrors.Errorf("reading dotfiles url config: %w", err)
61+
}
62+
// if the git url has changed we create a backup and clone fresh
63+
if gitRepo != du {
64+
backupDir := fmt.Sprintf("%s_backup_%s", dotfilesDir, time.Now().Format(time.RFC3339))
65+
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
66+
Text: fmt.Sprintf("The dotfiles URL has changed from %s to %s. Coder will backup the existing repo to %s.\n\n Continue?", du, gitRepo, backupDir),
67+
IsConfirm: true,
68+
})
69+
if err != nil {
70+
return err
71+
}
72+
73+
err = os.Rename(dotfilesDir, backupDir)
74+
if err != nil {
75+
return xerrors.Errorf("renaming dir %s: %w", dotfilesDir, err)
76+
}
77+
dotfilesExists = false
78+
moved = true
79+
}
80+
}
81+
82+
var (
83+
gitCmdDir string
84+
subcommands []string
85+
promptText string
86+
)
5787
if dotfilesExists {
5888
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Found dotfiles repository at %s\n", dotfilesDir)
5989
gitCmdDir = dotfilesDir
6090
subcommands = []string{"pull", "--ff-only"}
6191
promptText = fmt.Sprintf("Pulling latest from %s into directory %s.\n Continue?", gitRepo, dotfilesDir)
6292
} else {
63-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Did not find dotfiles repository at %s\n", dotfilesDir)
93+
if !moved {
94+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Did not find dotfiles repository at %s\n", dotfilesDir)
95+
}
96+
gitCmdDir = cfgDir
97+
subcommands = []string{"clone", args[0], dotfilesRepoDir}
98+
promptText = fmt.Sprintf("Cloning %s into directory %s.\n Continue?", gitRepo, dotfilesDir)
6499
}
65100

66101
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
@@ -87,12 +122,16 @@ func dotfiles() *cobra.Command {
87122
c := exec.CommandContext(cmd.Context(), "git", subcommands...)
88123
c.Dir = gitCmdDir
89124
c.Env = append(os.Environ(), fmt.Sprintf(`GIT_SSH_COMMAND=%s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no`, gitsshCmd))
90-
out, err := c.CombinedOutput()
125+
c.Stdout = cmd.OutOrStdout()
126+
c.Stderr = cmd.ErrOrStderr()
127+
err = c.Run()
91128
if err != nil {
92-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Error.Render(string(out)))
93-
return err
129+
if !dotfilesExists {
130+
return err
131+
}
132+
// if the repo exists we soft fail the update operation and try to continue
133+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Error.Render("Failed to update repo, continuing..."))
94134
}
95-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), string(out))
96135

97136
files, err := os.ReadDir(dotfilesDir)
98137
if err != nil {
@@ -122,24 +161,29 @@ func dotfiles() *cobra.Command {
122161
return err
123162
}
124163

125-
if homeDir == "" {
126-
homeDir, err = os.UserHomeDir()
164+
if symlinkDir == "" {
165+
symlinkDir, err = os.UserHomeDir()
127166
if err != nil {
128167
return xerrors.Errorf("getting user home: %w", err)
129168
}
130169
}
131170

132171
for _, df := range dotfiles {
133172
from := filepath.Join(dotfilesDir, df)
134-
to := filepath.Join(homeDir, df)
173+
to := filepath.Join(symlinkDir, df)
135174
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Symlinking %s to %s...\n", from, to)
136-
// if file already exists at destination remove it
137-
// this behavior matches `ln -f`
138-
_, err := os.Lstat(to)
139-
if err == nil {
140-
err := os.Remove(to)
175+
176+
isRegular, err := isRegular(to)
177+
if err != nil {
178+
return xerrors.Errorf("checking symlink for %s: %w", to, err)
179+
}
180+
// move conflicting non-symlink files to file.ext.bak
181+
if isRegular {
182+
backup := fmt.Sprintf("%s.bak", to)
183+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Moving %s to %s...\n", to, backup)
184+
err = os.Rename(to, backup)
141185
if err != nil {
142-
return xerrors.Errorf("removing destination file %s: %w", to, err)
186+
return xerrors.Errorf("renaming dir %s: %w", to, err)
143187
}
144188
}
145189

@@ -167,33 +211,36 @@ func dotfiles() *cobra.Command {
167211
// nolint:gosec
168212
scriptCmd := exec.CommandContext(cmd.Context(), fmt.Sprintf("./%s", script))
169213
scriptCmd.Dir = dotfilesDir
170-
out, err = scriptCmd.CombinedOutput()
214+
scriptCmd.Stdout = cmd.OutOrStdout()
215+
scriptCmd.Stderr = cmd.ErrOrStderr()
216+
err = scriptCmd.Run()
171217
if err != nil {
172-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Error.Render(string(out)))
173218
return xerrors.Errorf("running %s: %w", script, err)
174219
}
175-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), string(out))
176220

177221
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Dotfiles installation complete.")
178222
return nil
179223
},
180224
}
181225
cliui.AllowSkipPrompt(cmd)
182-
cliflag.StringVarP(cmd.Flags(), &homeDir, "home-dir", "", "CODER_HOME_DIR", "", "Specifies the home directory for the dotfiles symlink destination. If empty will use $HOME.")
226+
cliflag.StringVarP(cmd.Flags(), &symlinkDir, "symlink-dir", "", "CODER_SYMLINK_DIR", "", "Specifies the directory for the dotfiles symlink destinations. If empty will use $HOME.")
183227

184228
return cmd
185229
}
186230

187231
// dirExists checks if the dir already exists.
188232
func dirExists(name string) (bool, error) {
189-
_, err := os.Stat(name)
233+
fi, err := os.Stat(name)
190234
if err != nil {
191235
if os.IsNotExist(err) {
192236
return false, nil
193237
}
194238

195239
return false, xerrors.Errorf("stat dir: %w", err)
196240
}
241+
if !fi.IsDir() {
242+
return false, xerrors.New("exists but not a directory")
243+
}
197244

198245
return true, nil
199246
}
@@ -210,3 +257,15 @@ func findScript(scriptSet []string, files []fs.DirEntry) string {
210257

211258
return ""
212259
}
260+
261+
func isRegular(to string) (bool, error) {
262+
fi, err := os.Lstat(to)
263+
if err != nil {
264+
if errors.Is(err, os.ErrNotExist) {
265+
return false, nil
266+
}
267+
return false, xerrors.Errorf("lstat %s: %w", to, err)
268+
}
269+
270+
return fi.Mode().IsRegular(), nil
271+
}

0 commit comments

Comments
 (0)