// PollDirectory will poll a git repo. // It will look for changes to branches and tags including creation and // deletion. func PollDirectory(l log.Logger, dirName string, repo Repo, changes chan GitChange, period time.Duration) { l.Info("Watching %s as %s\n", repo, dirName) defer l.Info("Stopped watching %s as %s\n", repo, dirName) prev := make(map[string]*GitChange) // last seen ref status // Every poll period, get the list of branches. // For those seen before, fill in previous and currect SHA in change. Remove // from prev set. // For those that are new, fill in data. // For remaining entries in prev set, these are deleted. Send them with // current as empty. for firstAttempt := true; ; firstAttempt = false { var ( next = make(map[string]*GitChange) // currently seen refs, becomes prev set branches []*GitChange // working set of branches err error ) // run cmd every period, except on the first try if !firstAttempt { time.Sleep(period) } if branches, err = repo.Branches(); err != nil { l.Critical("Cannot get branch list for %s: %s", repo, err) continue } for _, branch := range branches { if strings.HasPrefix(branch.RefName, "gitsync-") { continue } var ( old, seenBefore = prev[branch.RefName] existsAndChanged = seenBefore && (old.Current != branch.Current || old.CheckedOut != branch.CheckedOut) ) branch.RepoName = path.Base(dirName) next[branch.RefName] = branch if existsAndChanged { branch.Prev = old.Current } // share changes and new branches if !seenBefore || existsAndChanged { l.Info("sending local change for %s %+v", repo, branch) changes <- *branch } // Cleanup any branch we have seen before, and handled above if seenBefore { delete(prev, branch.RefName) } } // report remaining branches in prev as deleted // Note: Use the prev set object since we have no current one to play with for _, old := range prev { old.Prev = old.Current old.Current = "" old.CheckedOut = false l.Info("sending local delete for %s %+v", repo, old) changes <- *old } prev = next } }
// NetIO shares GitChanges on toNet with the network via a multicast group. It // will pass on GitChanges from the network via fromNet. It uniques the daemon // instance by changing the .Name member to be name@<host IP>/<original .Name) func NetIO(l log.Logger, repo Repo, addr *net.UDPAddr, fromNet, toNet chan GitChange) { var ( err error recvConn, sendConn *net.UDPConn // UDP connections to allow us to send and receive change updates ) l.Info("Joining %v multicast(%t) group", addr, addr.IP.IsMulticast()) if recvConn, sendConn, err = establishConnPair(addr); err != nil { l.Critical("Error joining listening: %s\n", addr, err) return } l.Info("Successfully joined %v multicast(%t) group", addr, addr.IP.IsMulticast()) defer recvConn.Close() defer sendConn.Close() hostIp := sendConn.LocalAddr().(*net.UDPAddr).IP.String() term := false defer func() { term = true }() rawFromNet := make(chan []byte, 128) go func() { for !term { b := make([]byte, 1024) if n, err := recvConn.Read(b); err != nil { l.Critical("Cannot read socket: %s", err) continue } else { rawFromNet <- b[:n] } } }() for { select { case req, ok := <-toNet: if !ok { return } req.User = repo.User() req.HostIp = hostIp l.Info("Sending %+v", req) buf := &bytes.Buffer{} enc := gob.NewEncoder(buf) if err := enc.Encode(req); err != nil { l.Critical("%s", err) continue } l.Fine("Sending %+v", buf.Bytes()) if _, err := sendConn.Write(buf.Bytes()); err != nil { l.Critical("%s", err) continue } case resp := <-rawFromNet: var change GitChange dec := gob.NewDecoder(bytes.NewReader(resp)) if err := dec.Decode(&change); err != nil { l.Critical("%s", err) continue } else { l.Debug("received %+v", change) } if rootCommit, err := repo.RootCommit(); err != nil { log.Critical("Error getting root commit") } else { if (repo.User() != change.User) && (rootCommit == change.RootCommit) { fromNet <- change } } } } }