@@ -18,9 +18,11 @@ package fssync
1818
1919import (
2020 "context"
21+ "encoding/json"
2122 "fmt"
2223 "io/fs"
2324 "strings"
25+ "time"
2426
2527 "github.com/google/uuid"
2628 "github.com/moby/patternmatcher"
@@ -90,7 +92,7 @@ func (f *FS) Walk(ctx context.Context, target string, fn fs.WalkDirFunc) error {
9092 cancellableCtx , cancel := context .WithCancel (ctx )
9193 defer cancel ()
9294
93- walkMeta , err := unmarshalWalkMetadata (cancellableCtx )
95+ walkMeta , err := unmarshalWalkMetadata (cancellableCtx , f . proxy . mode )
9496 if err != nil {
9597 return err
9698 }
@@ -150,11 +152,101 @@ func (f *FS) Walk(ctx context.Context, target string, fn fs.WalkDirFunc) error {
150152 defer f ._checksumMutex .Unlock ()
151153 f ._checksum = checksum
152154 return nil
155+ case ModeJSON :
156+ return receiveJSON (demux , excludeMatcher , f .proxy .dockerfile , f .proxy .dockerignore , fn )
153157 default :
154158 return fmt .Errorf ("unsupported walk mode: %q" , walkMeta .Mode )
155159 }
156160}
157161
162+ // receiveJSON handles ModeJSON walk responses. The proxy sends a single
163+ // BuildTransfer whose Data field is a JSON array of RawFileInfo. File contents
164+ // are not transferred here; they are fetched on-demand via FS.Open.
165+ func receiveJSON (demux * stream.Demultiplexer , excludeMatcher * patternmatcher.PatternMatcher , dockerfile , dockerignore []byte , fn fs.WalkDirFunc ) error {
166+ resp , err := demux .Recv ()
167+ if err != nil {
168+ return fmt .Errorf ("json walk: failed receiving response: %w" , err )
169+ }
170+ bt := resp .GetBuildTransfer ()
171+ if bt == nil {
172+ return fmt .Errorf ("json walk: expected BuildTransfer, got nil" )
173+ }
174+ if errMsg , ok := bt .Metadata ["error" ]; ok {
175+ return fmt .Errorf ("json walk: server error: %s" , errMsg )
176+ }
177+
178+ var files []RawFileInfo
179+ if err := json .Unmarshal (bt .Data , & files ); err != nil {
180+ return fmt .Errorf ("json walk: failed to unmarshal file list: %w" , err )
181+ }
182+
183+ // Staged Dockerfile/dockerignore live under DockerfileStaging (".com.apple.container").
184+ // That prefix starts with '.' which sorts before any regular path component,
185+ // so these entries must be emitted BEFORE the regular file list.
186+ if len (dockerignore ) > 0 {
187+ stagingDir := DockerfileStaging
188+ dirEntry := & fileutils.FileInfo {
189+ NameVal : stagingDir ,
190+ ModeVal : fs .ModeDir | 0755 ,
191+ IsDirVal : true ,
192+ }
193+ if err := fn (stagingDir , fs .FileInfoToDirEntry (dirEntry ), nil ); err != nil {
194+ return err
195+ }
196+ for _ , staged := range []struct {
197+ name string
198+ data []byte
199+ }{
200+ {"Dockerfile" , dockerfile },
201+ {"Dockerfile.dockerignore" , dockerignore },
202+ } {
203+ path := stagingDir + "/" + staged .name
204+ fi := & fileutils.FileInfo {
205+ NameVal : path ,
206+ SizeVal : int64 (len (staged .data )),
207+ ModeVal : 0644 ,
208+ }
209+ if err := fn (path , fs .FileInfoToDirEntry (fi ), nil ); err != nil {
210+ return err
211+ }
212+ }
213+ }
214+
215+ for _ , f := range files {
216+ excluded , err := excludeMatcher .MatchesOrParentMatches (f .Name )
217+ if err != nil {
218+ return err
219+ }
220+ if excluded {
221+ continue
222+ }
223+ modTime , err := time .Parse (time .RFC3339 , f .ModTime )
224+ if err != nil {
225+ modTime = time.Time {}
226+ }
227+ modeVal := fs .FileMode (f .Mode )
228+ if f .IsDir {
229+ modeVal |= fs .ModeDir
230+ } else if f .Target != "" {
231+ modeVal |= fs .ModeSymlink
232+ }
233+ fi := & fileutils.FileInfo {
234+ NameVal : f .Name ,
235+ SizeVal : int64 (f .Size ),
236+ ModeVal : modeVal ,
237+ ModTimeVal : modTime ,
238+ IsDirVal : f .IsDir ,
239+ Uid : f .UID ,
240+ Gid : f .GID ,
241+ LinkName : f .Target ,
242+ }
243+ if err := fn (f .Name , fs .FileInfoToDirEntry (fi ), nil ); err != nil {
244+ return err
245+ }
246+ }
247+ return nil
248+ }
249+
158250// RawFileInfo is the wire‑format for Walk (json mode).
159251type RawFileInfo struct {
160252 Name string `json:"name"`
@@ -175,23 +267,13 @@ type WalkMetadata struct {
175267 Mode TransferMode
176268}
177269
178- func unmarshalWalkMetadata (ctx context.Context ) (* WalkMetadata , error ) {
179- md := & WalkMetadata {}
270+ func unmarshalWalkMetadata (ctx context.Context , mode TransferMode ) (* WalkMetadata , error ) {
271+ md := & WalkMetadata {Mode : mode }
180272 if m , ok := metadata .FromIncomingContext (ctx ); ok {
181273 md .IncludePatterns = strings .Join (m ["include-patterns" ], "," )
182274 md .ExcludedPatterns = strings .Join (m ["exclude-patterns" ], "," )
183275 md .FollowPaths = strings .Join (m ["followpaths" ], "," )
184276 md .DirName = strings .Join (m ["dir-name" ], "," )
185- modeStr := strings .Join (m ["mode" ], "," )
186- switch modeStr {
187- case "" , "tar" :
188- modeStr = string (ModeTAR )
189- default :
190- return nil , fmt .Errorf ("invalid walk mode: %s" , modeStr )
191- }
192- md .Mode = TransferMode (modeStr )
193- } else {
194- md .Mode = ModeTAR
195277 }
196278 return md , nil
197279}
0 commit comments