Skip to content

Commit af55f0e

Browse files
committed
Add wait flag
1 parent e1b354c commit af55f0e

3 files changed

Lines changed: 94 additions & 30 deletions

File tree

config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ type Config struct {
7070
// running any command. When enabled, no command should be specified.
7171
PrintChanges bool `json:"printChanges,omitempty"`
7272

73+
// Wait specifies whether to wait for a single change and then exit without
74+
// running any command. When enabled, no command should be specified.
75+
Wait bool `json:"wait,omitempty"`
76+
7377
// TODO: FollowSymlinks
7478

7579
// Private fields below

godemon.go

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Advanced options:
3939
-s, --signal restart signal name or number (default SIGINT)
4040
-v, --verbose log more info; set twice to log lower level debug info
4141
-p, --print-changes print change events to stdout without running a command
42+
--wait wait for one change, then exit without running a command
4243
`)
4344
}
4445

@@ -116,6 +117,10 @@ func parseConfig(args []string) (*Config, error) {
116117
cfg.PrintChanges = true
117118
continue
118119
}
120+
if arg == "--wait" {
121+
cfg.Wait = true
122+
continue
123+
}
119124

120125
// Parse string flags
121126
for _, spec := range []struct {
@@ -264,8 +269,11 @@ func parseConfig(args []string) (*Config, error) {
264269
}
265270
cfg.notifySignal = s
266271
}
267-
if cfg.PrintChanges {
272+
if cfg.PrintChanges || cfg.Wait {
268273
if len(cfg.Command) > 0 {
274+
if cfg.Wait {
275+
return nil, fmt.Errorf("--wait cannot be used with a command")
276+
}
269277
return nil, fmt.Errorf("--print-changes cannot be used with a command")
270278
}
271279
} else {
@@ -667,7 +675,9 @@ func (g *godemon) Start() error {
667675
defer func() {
668676
debugf("Removing file watchers")
669677
w.Close()
670-
select {} // Wait for shutdown to finish.
678+
if !g.cfg.Wait {
679+
select {} // Wait for shutdown to finish.
680+
}
671681
}()
672682
g.w = w
673683

@@ -695,7 +705,9 @@ func (g *godemon) Start() error {
695705
notifyf("Exiting due to --dry-run flag.")
696706
}
697707

698-
if g.cfg.PrintChanges {
708+
if g.cfg.Wait {
709+
g.waitForChange(addCh)
710+
} else if g.cfg.PrintChanges {
699711
// In print-changes mode, just print events to stdout without running a command.
700712
g.handlePrintChanges(addCh, shutdownCh)
701713
} else {
@@ -852,30 +864,36 @@ func (g *godemon) handleAdds(addCh chan string) {
852864
}
853865
}
854866

867+
func (g *godemon) handleChange(addCh chan<- string, event fsEvent) bool {
868+
if g.shouldIgnore(event.Path) {
869+
infof("Ignoring event: %s %q", event.Op, event.Path)
870+
return false
871+
}
872+
infof("Got event: %s %q", event.Op, event.Path)
873+
874+
if g.cfg.Lockfile != nil && *g.cfg.Lockfile != "" {
875+
if err := waitForLockfileRemoval(*g.cfg.Lockfile); err != nil {
876+
warnf("waitForLockfileRemoval failed: %s", err)
877+
}
878+
}
879+
880+
// When creating new dirs, add them to the watch list (recursive watchers
881+
// cover newly created dirs automatically).
882+
if !g.w.Recursive() && event.Op&opCreate != 0 && isDir(event.Path) {
883+
addCh <- event.Path
884+
}
885+
return true
886+
}
887+
855888
func (g *godemon) handleEvents(addCh chan<- string, restartCh chan<- struct{}, shutdownCh <-chan struct{}) {
856889
for {
857890
select {
858891
case err := <-g.w.Errors():
859892
warnf("%s", err)
860893
case event := <-g.w.Events():
861-
if g.shouldIgnore(event.Path) {
862-
infof("Ignoring event: %s %q", event.Op, event.Path)
863-
continue
864-
}
865-
infof("Got event: %s %q", event.Op, event.Path)
866-
867-
if g.cfg.Lockfile != nil && *g.cfg.Lockfile != "" {
868-
if err := waitForLockfileRemoval(*g.cfg.Lockfile); err != nil {
869-
warnf("waitForLockfileRemoval failed: %s", err)
870-
}
894+
if g.handleChange(addCh, event) {
895+
restartCh <- struct{}{}
871896
}
872-
873-
// When creating new dirs, add them to the watch list (recursive
874-
// watchers cover newly created dirs automatically).
875-
if !g.w.Recursive() && event.Op&opCreate != 0 && isDir(event.Path) {
876-
addCh <- event.Path
877-
}
878-
restartCh <- struct{}{}
879897
case <-shutdownCh:
880898
debugf("handleEvents: got shutdown signal")
881899
return
@@ -897,26 +915,33 @@ func (g *godemon) handlePrintChanges(addCh chan<- string, shutdownCh <-chan stru
897915
case err := <-g.w.Errors():
898916
warnf("%s", err)
899917
case event := <-g.w.Events():
900-
if g.shouldIgnore(event.Path) {
901-
infof("Ignoring event: %s %q", event.Op, event.Path)
902-
continue
903-
}
904-
if !isQuiet() {
918+
if g.handleChange(addCh, event) && !isQuiet() {
905919
// Print the change event to stdout
906920
fmt.Printf("%s %s\n", event.Op, event.Path)
907921
}
908-
// When creating new dirs, add them to the watch list (recursive
909-
// watchers cover newly created dirs automatically).
910-
if !g.w.Recursive() && event.Op&opCreate != 0 && isDir(event.Path) {
911-
addCh <- event.Path
912-
}
913922
case <-shutdownCh:
914923
debugf("handlePrintChanges: got shutdown signal")
915924
return
916925
}
917926
}
918927
}
919928

929+
func (g *godemon) waitForChange(addCh chan<- string) {
930+
for {
931+
select {
932+
case err := <-g.w.Errors():
933+
warnf("%s", err)
934+
case event := <-g.w.Events():
935+
if g.handleChange(addCh, event) {
936+
if g.cfg.PrintChanges && !isQuiet() {
937+
fmt.Printf("%s %s\n", event.Op, event.Path)
938+
}
939+
return
940+
}
941+
}
942+
}
943+
}
944+
920945
func waitForLockfileRemoval(path string) error {
921946
debugf("Waiting for removal of lockfile %s", path)
922947

godemon_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,41 @@ func TestParseThrottleFlag(t *testing.T) {
238238
}
239239
}
240240

241+
func TestWaitExitsOnWatchedChange(t *testing.T) {
242+
ws := t.TempDir()
243+
writeFile(t, ws, "watched.go", "")
244+
245+
cfg, err := parseConfig([]string{"godemon", "--wait", "--watch", ws, "--only", "*.go", "--ignore", "ignored.go", "--no-gitignore"})
246+
if err != nil {
247+
t.Fatal(err)
248+
}
249+
250+
done := make(chan error, 1)
251+
go func() {
252+
done <- (&godemon{cfg: cfg}).Start()
253+
}()
254+
255+
// An ignored change should leave wait mode running.
256+
time.Sleep(500 * time.Millisecond)
257+
writeFile(t, ws, "ignored.go", "")
258+
select {
259+
case err := <-done:
260+
t.Fatalf("wait exited on ignored change: %s", err)
261+
case <-time.After(250 * time.Millisecond):
262+
}
263+
264+
// A watched change should make wait mode return successfully.
265+
writeFile(t, ws, "watched.go", "changed")
266+
select {
267+
case err := <-done:
268+
if err != nil {
269+
t.Fatal(err)
270+
}
271+
case <-time.After(3 * time.Second):
272+
t.Fatal("timed out waiting for watched change")
273+
}
274+
}
275+
241276
func TestRestartOnCreate(t *testing.T) {
242277
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
243278
defer cancel()

0 commit comments

Comments
 (0)