@@ -13,6 +13,7 @@ import (
1313 "github.com/dstackai/dstack/runner/internal/common"
1414 "github.com/dstackai/dstack/runner/internal/log"
1515 "github.com/dstackai/dstack/runner/internal/repo"
16+ "github.com/dstackai/dstack/runner/internal/schemas"
1617)
1718
1819// setupRepo must be called from Run
@@ -36,13 +37,27 @@ func (ex *RunExecutor) setupRepo(ctx context.Context) error {
3637 }
3738 log .Trace (ctx , "Job repo dir" , "path" , ex .repoDir )
3839
39- shouldCheckout , err := ex .shouldCheckout (ctx )
40+ repoDirIsEmpty , err := ex .prepareRepoDir (ctx )
4041 if err != nil {
41- return fmt .Errorf ("check if checkout needed: %w" , err )
42- }
43- if ! shouldCheckout {
44- log .Info (ctx , "skipping repo checkout: repo dir is not empty" )
45- return nil
42+ return fmt .Errorf ("prepare repo dir: %w" , err )
43+ }
44+ if ! repoDirIsEmpty {
45+ var repoExistsAction schemas.RepoExistsAction
46+ if ex .jobSpec .RepoExistsAction != nil {
47+ repoExistsAction = * ex .jobSpec .RepoExistsAction
48+ } else {
49+ log .Debug (ctx , "repo_exists_action is not set, using legacy 'skip' action" )
50+ repoExistsAction = schemas .RepoExistsActionSkip
51+ }
52+ switch repoExistsAction {
53+ case schemas .RepoExistsActionError :
54+ return fmt .Errorf ("setup repo: repo dir is not empty: %s" , ex .repoDir )
55+ case schemas .RepoExistsActionSkip :
56+ log .Info (ctx , "Skipping repo checkout: repo dir is not empty" , "path" , ex .repoDir )
57+ return nil
58+ default :
59+ return fmt .Errorf ("setup repo: unsupported action: %s" , repoExistsAction )
60+ }
4661 }
4762 // Move existing repo files from the repo dir and back to be able to git clone.
4863 // Currently, only needed for volumes mounted inside repo with lost+found present.
@@ -143,11 +158,11 @@ func (ex *RunExecutor) prepareArchive(ctx context.Context) error {
143158 return nil
144159}
145160
146- func (ex * RunExecutor ) shouldCheckout (ctx context.Context ) (bool , error ) {
147- log .Trace (ctx , "checking if repo checkout is needed " )
161+ func (ex * RunExecutor ) prepareRepoDir (ctx context.Context ) (bool , error ) {
162+ log .Trace (ctx , "Preparing repo dir " )
148163 info , err := os .Stat (ex .repoDir )
149164 if err != nil {
150- if os . IsNotExist (err ) {
165+ if errors . Is (err , os . ErrNotExist ) {
151166 if err = common .MkdirAll (ctx , ex .repoDir , ex .jobUid , ex .jobGid ); err != nil {
152167 return false , fmt .Errorf ("create repo dir: %w" , err )
153168 }
@@ -157,24 +172,22 @@ func (ex *RunExecutor) shouldCheckout(ctx context.Context) (bool, error) {
157172 return false , fmt .Errorf ("stat repo dir: %w" , err )
158173 }
159174 if ! info .IsDir () {
160- return false , fmt .Errorf ("failed to set up repo dir: %s is not a dir" , ex .repoDir )
175+ return false , fmt .Errorf ("stat repo dir: %s is not a dir" , ex .repoDir )
161176 }
162177 entries , err := os .ReadDir (ex .repoDir )
163178 if err != nil {
164179 return false , fmt .Errorf ("read repo dir: %w" , err )
165180 }
166181 if len (entries ) == 0 {
167- // Repo dir existed but was empty, e.g. a volume without repo
182+ // Repo dir is empty
168183 return true , nil
169184 }
170- if len (entries ) > 1 {
171- // Repo already checked out, e.g. a volume with repo
172- return false , nil
173- }
174- if entries [0 ].Name () == "lost+found" {
185+ if len (entries ) == 1 && entries [0 ].Name () == "lost+found" {
175186 // lost+found may be present on a newly created volume
187+ // We (but not Git, see `{move,restore}RepoDir`) consider such a dir "empty"
176188 return true , nil
177189 }
190+ // Repo dir is not empty
178191 return false , nil
179192}
180193
0 commit comments