github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/filemon/changes.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package filemon 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "sort" 24 "strings" 25 "time" 26 27 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 28 ) 29 30 // FileMap is a map of filename to modification times. 31 type FileMap map[string]time.Time 32 33 // Stat returns the modification times for a list of files. 34 func Stat(deps func() ([]string, error)) (FileMap, error) { 35 state := FileMap{} 36 paths, err := deps() 37 if err != nil { 38 return state, fmt.Errorf("listing files: %w", err) 39 } 40 for _, path := range paths { 41 stat, err := os.Stat(path) 42 if err != nil { 43 if os.IsNotExist(err) { 44 log.Entry(context.TODO()).Debugf("could not stat dependency: %s", err) 45 continue // Ignore files that don't exist 46 } 47 return nil, fmt.Errorf("unable to stat file %q: %w", path, err) 48 } 49 state[path] = stat.ModTime() 50 } 51 52 return state, nil 53 } 54 55 type Events struct { 56 Added []string 57 Modified []string 58 Deleted []string 59 } 60 61 func (e Events) HasChanged() bool { 62 return len(e.Added) != 0 || len(e.Deleted) != 0 || len(e.Modified) != 0 63 } 64 65 func (e *Events) String() string { 66 added, deleted, modified := len(e.Added), len(e.Deleted), len(e.Modified) 67 68 var sb strings.Builder 69 if added > 0 { 70 sb.WriteString(fmt.Sprintf("[watch event] added: %s\n", e.Added)) 71 } 72 if deleted > 0 { 73 sb.WriteString(fmt.Sprintf("[watch event] deleted: %s\n", e.Deleted)) 74 } 75 if modified > 0 { 76 sb.WriteString(fmt.Sprintf("[watch event] modified: %s\n", e.Modified)) 77 } 78 return sb.String() 79 } 80 81 func events(prev, curr FileMap) Events { 82 e := Events{} 83 for f, t := range prev { 84 modtime, ok := curr[f] 85 if !ok { 86 // file in prev but not in curr -> file deleted 87 e.Deleted = append(e.Deleted, f) 88 continue 89 } 90 if !modtime.Equal(t) { 91 // file in both prev and curr 92 // time not equal -> file modified 93 e.Modified = append(e.Modified, f) 94 continue 95 } 96 } 97 98 for f := range curr { 99 // don't need to check case where file is in both curr and prev 100 // covered above 101 _, ok := prev[f] 102 if !ok { 103 // file in curr but not in prev -> file added 104 e.Added = append(e.Added, f) 105 } 106 } 107 108 sortEvents(e) 109 logEvents(e) 110 return e 111 } 112 113 func sortEvents(e Events) { 114 sort.Strings(e.Added) 115 sort.Strings(e.Modified) 116 sort.Strings(e.Deleted) 117 } 118 119 func logEvents(e Events) { 120 if e.Added != nil && len(e.Added) > 0 { 121 log.Entry(context.TODO()).Infof("files added: %v", e.Added) 122 } 123 if e.Modified != nil && len(e.Modified) > 0 { 124 log.Entry(context.TODO()).Infof("files modified: %v", e.Modified) 125 } 126 if e.Deleted != nil && len(e.Deleted) > 0 { 127 log.Entry(context.TODO()).Infof("files deleted: %v", e.Deleted) 128 } 129 }