fix(client): watch only rule paths, forget vanished worktrees, ping http2
This commit is contained in:
1 parent
3b2dcffcf0
commit
4207f7e6f8
4 files changed
+165
-34
No files matched your search
@@ -4,11 +4,13 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -27,6 +29,12 @@ const (
|
||||
headFile = "HEAD"
|
||||
)
|
||||
|
||||
type tree struct {
|
||||
key string
|
||||
gitDir string
|
||||
rules Rules
|
||||
}
|
||||
|
||||
type daemon struct {
|
||||
client *Client
|
||||
watcher *fsnotify.Watcher
|
||||
@@ -34,8 +42,7 @@ type daemon struct {
|
||||
|
||||
mu sync.Mutex
|
||||
owners map[string]string
|
||||
gitDirs map[string]bool
|
||||
keys map[string]string
|
||||
trees map[string]tree
|
||||
pending map[string]*time.Timer
|
||||
}
|
||||
|
||||
@@ -56,8 +63,7 @@ func (c *Client) Daemon(ctx context.Context) error {
|
||||
watcher: watcher,
|
||||
queue: make(chan string, 256),
|
||||
owners: map[string]string{},
|
||||
gitDirs: map[string]bool{},
|
||||
keys: map[string]string{},
|
||||
trees: map[string]tree{},
|
||||
pending: map[string]*time.Timer{},
|
||||
}
|
||||
go d.listen(ctx)
|
||||
@@ -85,16 +91,32 @@ func (d *daemon) handle(event fsnotify.Event) {
|
||||
d.syncAll()
|
||||
return
|
||||
}
|
||||
dir := filepath.Dir(event.Name)
|
||||
d.mu.Lock()
|
||||
root, watched := d.owners[dir]
|
||||
irrelevant := d.gitDirs[dir] && filepath.Base(event.Name) != headFile
|
||||
if event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
|
||||
delete(d.owners, event.Name)
|
||||
}
|
||||
root, watched := d.owners[filepath.Dir(event.Name)]
|
||||
relevant := watched && d.trees[root].relevant(root, event.Name)
|
||||
d.mu.Unlock()
|
||||
if watched && !irrelevant && !strings.HasSuffix(event.Name, tempSuffix) {
|
||||
if relevant {
|
||||
d.schedule(root)
|
||||
}
|
||||
}
|
||||
|
||||
func (t tree) relevant(root, name string) bool {
|
||||
if filepath.Dir(name) == t.gitDir {
|
||||
return filepath.Base(name) == headFile
|
||||
}
|
||||
rel, err := filepath.Rel(root, name)
|
||||
if err != nil || strings.HasSuffix(name, tempSuffix) {
|
||||
return false
|
||||
}
|
||||
rel = filepath.ToSlash(rel)
|
||||
return slices.ContainsFunc(t.rules, func(rule Rule) bool {
|
||||
return within(rel, rule.Path) || within(rule.Path, rel)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *daemon) schedule(root string) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
@@ -112,10 +134,26 @@ func (d *daemon) schedule(root string) {
|
||||
|
||||
func (d *daemon) syncAll() {
|
||||
for _, root := range d.client.Worktrees() {
|
||||
d.schedule(root)
|
||||
if _, err := os.Stat(root); errors.Is(err, fs.ErrNotExist) {
|
||||
d.forget(root)
|
||||
} else {
|
||||
d.schedule(root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *daemon) forget(root string) {
|
||||
if err := d.client.Forget(root); err != nil {
|
||||
log.Printf("%s: %v", root, err)
|
||||
return
|
||||
}
|
||||
log.Printf("%s: gone, forgotten", root)
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
delete(d.trees, root)
|
||||
d.release(root, nil)
|
||||
}
|
||||
|
||||
func (d *daemon) work(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
@@ -138,21 +176,11 @@ func (d *daemon) watch(root string) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dirs := []string{repo.GitDir}
|
||||
for _, rule := range LoadRules(repo.Root) {
|
||||
rules := LoadRules(repo.Root)
|
||||
dirs := map[string]bool{repo.GitDir: true}
|
||||
for _, rule := range rules {
|
||||
target := filepath.Join(repo.Root, filepath.FromSlash(rule.Path))
|
||||
info, err := os.Stat(target)
|
||||
for err != nil && target != repo.Root {
|
||||
target = filepath.Dir(target)
|
||||
info, err = os.Stat(target)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !info.IsDir() {
|
||||
dirs = append(dirs, filepath.Dir(target))
|
||||
continue
|
||||
}
|
||||
dirs[nearestDir(filepath.Dir(target), repo.Root)] = true
|
||||
_ = filepath.WalkDir(target, func(path string, entry fs.DirEntry, err error) error {
|
||||
if err != nil || !entry.IsDir() {
|
||||
return nil
|
||||
@@ -160,21 +188,42 @@ func (d *daemon) watch(root string) {
|
||||
if entry.Name() == ".git" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
dirs = append(dirs, path)
|
||||
dirs[path] = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
d.keys[repo.Root] = repo.Key
|
||||
d.gitDirs[repo.GitDir] = true
|
||||
for _, dir := range dirs {
|
||||
if _, watching := d.owners[dir]; !watching && d.watcher.Add(dir) == nil {
|
||||
d.trees[repo.Root] = tree{key: repo.Key, gitDir: repo.GitDir, rules: rules}
|
||||
d.release(repo.Root, dirs)
|
||||
for dir := range dirs {
|
||||
if owner, taken := d.owners[dir]; taken && owner != repo.Root {
|
||||
continue
|
||||
}
|
||||
if d.watcher.Add(dir) == nil {
|
||||
d.owners[dir] = repo.Root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *daemon) release(root string, keep map[string]bool) {
|
||||
for dir, owner := range d.owners {
|
||||
if owner == root && !keep[dir] {
|
||||
_ = d.watcher.Remove(dir)
|
||||
delete(d.owners, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nearestDir(path, root string) string {
|
||||
for ; path != root; path = filepath.Dir(path) {
|
||||
if info, err := os.Stat(path); err == nil && info.IsDir() {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func (d *daemon) listen(ctx context.Context) {
|
||||
backoff := minBackoff
|
||||
for ctx.Err() == nil {
|
||||
@@ -208,8 +257,8 @@ func (d *daemon) stream(ctx context.Context) error {
|
||||
}
|
||||
d.mu.Lock()
|
||||
var roots []string
|
||||
for root, key := range d.keys {
|
||||
if key == event.Repo {
|
||||
for root, tree := range d.trees {
|
||||
if tree.key == event.Repo {
|
||||
roots = append(roots, root)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user