Skip to content

Commit ce8f497

Browse files
authored
Protect existing solutions from being overwritten by 'download' (#979)
Add an optional `--force` flag to the download command to overwrite an existing exercise directory.
1 parent aa9dcfa commit ce8f497

2 files changed

Lines changed: 114 additions & 2 deletions

File tree

cmd/download.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func runDownload(cfg config.Config, flags *pflag.FlagSet, args []string) error {
6464
metadata := download.payload.metadata()
6565
dir := metadata.Exercise(usrCfg.GetString("workspace")).MetadataDir()
6666

67+
if _, err = os.Stat(dir); !download.forceoverwrite && err == nil {
68+
return fmt.Errorf("directory '%s' already exists, use --force to overwrite", dir)
69+
}
70+
6771
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
6872
return err
6973
}
@@ -103,7 +107,6 @@ func runDownload(cfg config.Config, flags *pflag.FlagSet, args []string) error {
103107
continue
104108
}
105109

106-
// TODO: handle collisions
107110
path := sf.relativePath()
108111
dir := filepath.Join(metadata.Dir, filepath.Dir(path))
109112
if err = os.MkdirAll(dir, os.FileMode(0755)); err != nil {
@@ -133,7 +136,8 @@ type download struct {
133136
token, apibaseurl, workspace string
134137

135138
// optional
136-
track, team string
139+
track, team string
140+
forceoverwrite bool
137141

138142
payload *downloadPayload
139143
}
@@ -158,6 +162,11 @@ func newDownload(flags *pflag.FlagSet, usrCfg *viper.Viper) (*download, error) {
158162
return nil, err
159163
}
160164

165+
d.forceoverwrite, err = flags.GetBool("force")
166+
if err != nil {
167+
return nil, err
168+
}
169+
161170
d.token = usrCfg.GetString("token")
162171
d.apibaseurl = usrCfg.GetString("apibaseurl")
163172
d.workspace = usrCfg.GetString("workspace")
@@ -354,6 +363,7 @@ func setupDownloadFlags(flags *pflag.FlagSet) {
354363
flags.StringP("track", "t", "", "the track ID")
355364
flags.StringP("exercise", "e", "", "the exercise slug")
356365
flags.StringP("team", "T", "", "the team slug")
366+
flags.BoolP("force", "F", false, "overwrite existing exercise directory")
357367
}
358368

359369
func init() {

cmd/download_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,108 @@ func TestDownload(t *testing.T) {
209209
}
210210
}
211211

212+
func TestDownloadToExistingDirectory(t *testing.T) {
213+
co := newCapturedOutput()
214+
co.override()
215+
defer co.reset()
216+
217+
testCases := []struct {
218+
exerciseDir string
219+
flags map[string]string
220+
}{
221+
{
222+
exerciseDir: filepath.Join("bogus-track", "bogus-exercise"),
223+
flags: map[string]string{"exercise": "bogus-exercise", "track": "bogus-track"},
224+
},
225+
{
226+
exerciseDir: filepath.Join("teams", "bogus-team", "bogus-track", "bogus-exercise"),
227+
flags: map[string]string{"exercise": "bogus-exercise", "track": "bogus-track", "team": "bogus-team"},
228+
},
229+
}
230+
231+
for _, tc := range testCases {
232+
tmpDir, err := ioutil.TempDir("", "download-cmd")
233+
defer os.RemoveAll(tmpDir)
234+
assert.NoError(t, err)
235+
236+
err = os.MkdirAll(filepath.Join(tmpDir, tc.exerciseDir), os.FileMode(0755))
237+
assert.NoError(t, err)
238+
239+
ts := fakeDownloadServer("true", "")
240+
defer ts.Close()
241+
242+
v := viper.New()
243+
v.Set("workspace", tmpDir)
244+
v.Set("apibaseurl", ts.URL)
245+
v.Set("token", "abc123")
246+
247+
cfg := config.Config{
248+
UserViperConfig: v,
249+
}
250+
flags := pflag.NewFlagSet("fake", pflag.PanicOnError)
251+
setupDownloadFlags(flags)
252+
for name, value := range tc.flags {
253+
flags.Set(name, value)
254+
}
255+
256+
err = runDownload(cfg, flags, []string{})
257+
258+
if assert.Error(t, err) {
259+
assert.Regexp(t, "directory '.+' already exists", err.Error())
260+
}
261+
}
262+
}
263+
264+
func TestDownloadToExistingDirectoryWithForce(t *testing.T) {
265+
co := newCapturedOutput()
266+
co.override()
267+
defer co.reset()
268+
269+
testCases := []struct {
270+
exerciseDir string
271+
flags map[string]string
272+
}{
273+
{
274+
exerciseDir: filepath.Join("bogus-track", "bogus-exercise"),
275+
flags: map[string]string{"exercise": "bogus-exercise", "track": "bogus-track"},
276+
},
277+
{
278+
exerciseDir: filepath.Join("teams", "bogus-team", "bogus-track", "bogus-exercise"),
279+
flags: map[string]string{"exercise": "bogus-exercise", "track": "bogus-track", "team": "bogus-team"},
280+
},
281+
}
282+
283+
for _, tc := range testCases {
284+
tmpDir, err := ioutil.TempDir("", "download-cmd")
285+
defer os.RemoveAll(tmpDir)
286+
assert.NoError(t, err)
287+
288+
err = os.MkdirAll(filepath.Join(tmpDir, tc.exerciseDir), os.FileMode(0755))
289+
assert.NoError(t, err)
290+
291+
ts := fakeDownloadServer("true", "")
292+
defer ts.Close()
293+
294+
v := viper.New()
295+
v.Set("workspace", tmpDir)
296+
v.Set("apibaseurl", ts.URL)
297+
v.Set("token", "abc123")
298+
299+
cfg := config.Config{
300+
UserViperConfig: v,
301+
}
302+
flags := pflag.NewFlagSet("fake", pflag.PanicOnError)
303+
setupDownloadFlags(flags)
304+
for name, value := range tc.flags {
305+
flags.Set(name, value)
306+
}
307+
flags.Set("force", "true")
308+
309+
err = runDownload(cfg, flags, []string{})
310+
assert.NoError(t, err)
311+
}
312+
}
313+
212314
func fakeDownloadServer(requestor, teamSlug string) *httptest.Server {
213315
mux := http.NewServeMux()
214316
server := httptest.NewServer(mux)

0 commit comments

Comments
 (0)