aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2018-08-21 18:46:53 +0200
committerMichael Muré <batolettre@gmail.com>2018-08-21 19:13:08 +0200
commit6d7dc465d881d0d04b01dfb6e09870346216d2d0 (patch)
treefbbce01c6b7634556303a3d917d1036767280d17
parent942178288d657202b3f7afd386b319a245afbb7e (diff)
downloadgit-bug-6d7dc465d881d0d04b01dfb6e09870346216d2d0.tar.gz
cache: lock the repo with a pid file; automatic cleaning
-rw-r--r--cache/bug_cache.go137
-rw-r--r--cache/cache.go342
-rw-r--r--cache/repo_cache.go144
-rw-r--r--commands/root.go9
-rw-r--r--commands/webui.go58
-rw-r--r--graphql/handler.go20
-rw-r--r--util/process.go23
7 files changed, 478 insertions, 255 deletions
diff --git a/cache/bug_cache.go b/cache/bug_cache.go
new file mode 100644
index 00000000..59c39f5c
--- /dev/null
+++ b/cache/bug_cache.go
@@ -0,0 +1,137 @@
+package cache
+
+import (
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/bug/operations"
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util"
+)
+
+type BugCacher interface {
+ Snapshot() *bug.Snapshot
+ ClearSnapshot()
+
+ // Mutations
+ AddComment(message string) error
+ AddCommentWithFiles(message string, files []util.Hash) error
+ ChangeLabels(added []string, removed []string) error
+ Open() error
+ Close() error
+ SetTitle(title string) error
+
+ Commit() error
+ CommitAsNeeded() error
+}
+
+type BugCache struct {
+ repo repository.Repo
+ bug *bug.Bug
+ snap *bug.Snapshot
+}
+
+func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher {
+ return &BugCache{
+ repo: repo,
+ bug: b,
+ }
+}
+
+func (c *BugCache) Snapshot() *bug.Snapshot {
+ if c.snap == nil {
+ snap := c.bug.Compile()
+ c.snap = &snap
+ }
+ return c.snap
+}
+
+func (c *BugCache) ClearSnapshot() {
+ c.snap = nil
+}
+
+func (c *BugCache) AddComment(message string) error {
+ return c.AddCommentWithFiles(message, nil)
+}
+
+func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error {
+ author, err := bug.GetUser(c.repo)
+ if err != nil {
+ return err
+ }
+
+ operations.CommentWithFiles(c.bug, author, message, files)
+
+ // TODO: perf --> the snapshot could simply be updated with the new op
+ c.ClearSnapshot()
+
+ return nil
+}
+
+func (c *BugCache) ChangeLabels(added []string, removed []string) error {
+ author, err := bug.GetUser(c.repo)
+ if err != nil {
+ return err
+ }
+
+ err = operations.ChangeLabels(nil, c.bug, author, added, removed)
+ if err != nil {
+ return err
+ }
+
+ // TODO: perf --> the snapshot could simply be updated with the new op
+ c.ClearSnapshot()
+
+ return nil
+}
+
+func (c *BugCache) Open() error {
+ author, err := bug.GetUser(c.repo)
+ if err != nil {
+ return err
+ }
+
+ operations.Open(c.bug, author)
+
+ // TODO: perf --> the snapshot could simply be updated with the new op
+ c.ClearSnapshot()
+
+ return nil
+}
+
+func (c *BugCache) Close() error {
+ author, err := bug.GetUser(c.repo)
+ if err != nil {
+ return err
+ }
+
+ operations.Close(c.bug, author)
+
+ // TODO: perf --> the snapshot could simply be updated with the new op
+ c.ClearSnapshot()
+
+ return nil
+}
+
+func (c *BugCache) SetTitle(title string) error {
+ author, err := bug.GetUser(c.repo)
+ if err != nil {
+ return err
+ }
+
+ operations.SetTitle(c.bug, author, title)
+
+ // TODO: perf --> the snapshot could simply be updated with the new op
+ c.ClearSnapshot()
+
+ return nil
+}
+
+func (c *BugCache) Commit() error {
+ return c.bug.Commit(c.repo)
+}
+
+func (c *BugCache) CommitAsNeeded() error {
+ if c.bug.HasPendingOp() {
+ return c.bug.Commit(c.repo)
+ }
+ return nil
+}
diff --git a/cache/cache.go b/cache/cache.go
index c4177f75..618ec981 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -3,56 +3,32 @@ package cache
import (
"fmt"
"io"
- "strings"
+ "io/ioutil"
+ "os"
+ "path"
+ "strconv"
- "github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/bug/operations"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util"
)
+const lockfile = "lock"
+
type Cacher interface {
- RegisterRepository(ref string, repo repository.Repo)
- RegisterDefaultRepository(repo repository.Repo)
+ // RegisterRepository register a named repository. Use this for multi-repo setup
+ RegisterRepository(ref string, repo repository.Repo) error
+ // RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
+ RegisterDefaultRepository(repo repository.Repo) error
+ // ResolveRepo retrieve a repository by name
ResolveRepo(ref string) (RepoCacher, error)
+ // DefaultRepo retrieve the default repository
DefaultRepo() (RepoCacher, error)
-}
-
-type RepoCacher interface {
- Repository() repository.Repo
- ResolveBug(id string) (BugCacher, error)
- ResolveBugPrefix(prefix string) (BugCacher, error)
- AllBugIds() ([]string, error)
- ClearAllBugs()
-
- // Mutations
- NewBug(title string, message string) (BugCacher, error)
- NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error)
- Fetch(remote string) (string, error)
- MergeAll(remote string) <-chan bug.MergeResult
- Pull(remote string, out io.Writer) error
- Push(remote string) (string, error)
-}
-
-type BugCacher interface {
- Snapshot() *bug.Snapshot
- ClearSnapshot()
- // Mutations
- AddComment(message string) error
- AddCommentWithFiles(message string, files []util.Hash) error
- ChangeLabels(added []string, removed []string) error
- Open() error
+ // Close will do anything that is needed to close the cache properly
Close() error
- SetTitle(title string) error
-
- Commit() error
- CommitAsNeeded() error
}
-// Cacher ------------------------
-
type RootCache struct {
repos map[string]RepoCacher
}
@@ -63,263 +39,139 @@ func NewCache() RootCache {
}
}
-func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) {
- c.repos[ref] = NewRepoCache(repo)
-}
-
-func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) {
- c.repos[""] = NewRepoCache(repo)
-}
-
-func (c *RootCache) DefaultRepo() (RepoCacher, error) {
- if len(c.repos) != 1 {
- return nil, fmt.Errorf("repository is not unique")
- }
-
- for _, r := range c.repos {
- return r, nil
- }
-
- panic("unreachable")
-}
-
-func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) {
- r, ok := c.repos[ref]
- if !ok {
- return nil, fmt.Errorf("unknown repo")
- }
- return r, nil
-}
-
-// Repo ------------------------
-
-type RepoCache struct {
- repo repository.Repo
- bugs map[string]BugCacher
-}
-
-func NewRepoCache(r repository.Repo) RepoCacher {
- return &RepoCache{
- repo: r,
- bugs: make(map[string]BugCacher),
- }
-}
-
-func (c *RepoCache) Repository() repository.Repo {
- return c.repo
-}
-
-func (c *RepoCache) ResolveBug(id string) (BugCacher, error) {
- cached, ok := c.bugs[id]
- if ok {
- return cached, nil
- }
-
- b, err := bug.ReadLocalBug(c.repo, id)
+// RegisterRepository register a named repository. Use this for multi-repo setup
+func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) error {
+ err := c.lockRepository(repo)
if err != nil {
- return nil, err
+ return err
}
- cached = NewBugCache(c.repo, b)
- c.bugs[id] = cached
-
- return cached, nil
+ c.repos[ref] = NewRepoCache(repo)
+ return nil
}
-func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) {
- // preallocate but empty
- matching := make([]string, 0, 5)
-
- for id := range c.bugs {
- if strings.HasPrefix(id, prefix) {
- matching = append(matching, id)
- }
- }
-
- // TODO: should check matching bug in the repo as well
-
- if len(matching) > 1 {
- return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
- }
-
- if len(matching) == 1 {
- b := c.bugs[matching[0]]
- return b, nil
- }
-
- b, err := bug.FindLocalBug(c.repo, prefix)
-
+// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
+func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) error {
+ err := c.lockRepository(repo)
if err != nil {
- return nil, err
+ return err
}
- cached := NewBugCache(c.repo, b)
- c.bugs[b.Id()] = cached
-
- return cached, nil
-}
-
-func (c *RepoCache) AllBugIds() ([]string, error) {
- return bug.ListLocalIds(c.repo)
-}
-
-func (c *RepoCache) ClearAllBugs() {
- c.bugs = make(map[string]BugCacher)
+ c.repos[""] = NewRepoCache(repo)
+ return nil
}
-func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
- return c.NewBugWithFiles(title, message, nil)
-}
+func (c *RootCache) lockRepository(repo repository.Repo) error {
+ lockPath := repoLockFilePath(repo)
-func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) {
- author, err := bug.GetUser(c.repo)
+ err := RepoIsAvailable(repo)
if err != nil {
- return nil, err
+ return err
}
- b, err := operations.CreateWithFiles(author, title, message, files)
+ f, err := os.Create(lockPath)
if err != nil {
- return nil, err
+ return err
}
- err = b.Commit(c.repo)
+ pid := fmt.Sprintf("%d", os.Getpid())
+ _, err = f.WriteString(pid)
if err != nil {
- return nil, err
+ return err
}
- cached := NewBugCache(c.repo, b)
- c.bugs[b.Id()] = cached
-
- return cached, nil
-}
-
-func (c *RepoCache) Fetch(remote string) (string, error) {
- return bug.Fetch(c.repo, remote)
-}
-
-func (c *RepoCache) MergeAll(remote string) <-chan bug.MergeResult {
- return bug.MergeAll(c.repo, remote)
-}
-
-func (c *RepoCache) Pull(remote string, out io.Writer) error {
- return bug.Pull(c.repo, out, remote)
+ return f.Close()
}
-func (c *RepoCache) Push(remote string) (string, error) {
- return bug.Push(c.repo, remote)
-}
-
-// Bug ------------------------
-
-type BugCache struct {
- repo repository.Repo
- bug *bug.Bug
- snap *bug.Snapshot
-}
-
-func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher {
- return &BugCache{
- repo: repo,
- bug: b,
+// ResolveRepo retrieve a repository by name
+func (c *RootCache) DefaultRepo() (RepoCacher, error) {
+ if len(c.repos) != 1 {
+ return nil, fmt.Errorf("repository is not unique")
}
-}
-func (c *BugCache) Snapshot() *bug.Snapshot {
- if c.snap == nil {
- snap := c.bug.Compile()
- c.snap = &snap
+ for _, r := range c.repos {
+ return r, nil
}
- return c.snap
-}
-func (c *BugCache) ClearSnapshot() {
- c.snap = nil
-}
-
-func (c *BugCache) AddComment(message string) error {
- return c.AddCommentWithFiles(message, nil)
+ panic("unreachable")
}
-func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error {
- author, err := bug.GetUser(c.repo)
- if err != nil {
- return err
+// DefaultRepo retrieve the default repository
+func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) {
+ r, ok := c.repos[ref]
+ if !ok {
+ return nil, fmt.Errorf("unknown repo")
}
-
- operations.CommentWithFiles(c.bug, author, message, files)
-
- // TODO: perf --> the snapshot could simply be updated with the new op
- c.ClearSnapshot()
-
- return nil
+ return r, nil
}
-func (c *BugCache) ChangeLabels(added []string, removed []string) error {
- author, err := bug.GetUser(c.repo)
- if err != nil {
- return err
- }
-
- err = operations.ChangeLabels(nil, c.bug, author, added, removed)
- if err != nil {
- return err
+func (c *RootCache) Close() error {
+ for _, cachedRepo := range c.repos {
+ lockPath := repoLockFilePath(cachedRepo.Repository())
+ err := os.Remove(lockPath)
+ if err != nil {
+ return err
+ }
}
-
- // TODO: perf --> the snapshot could simply be updated with the new op
- c.ClearSnapshot()
-
return nil
}
-func (c *BugCache) Open() error {
- author, err := bug.GetUser(c.repo)
- if err != nil {
- return err
- }
+func RepoIsAvailable(repo repository.Repo) error {
+ lockPath := repoLockFilePath(repo)
- operations.Open(c.bug, author)
+ // Todo: this leave way for a racey access to the repo between the test
+ // if the file exist and the actual write. It's probably not a problem in
+ // practice because using a repository will be done from user interaction
+ // or in a context where a single instance of git-bug is already guaranteed
+ // (say, a server with the web UI running). But still, that might be nice to
+ // have a mutex or something to guard that.
- // TODO: perf --> the snapshot could simply be updated with the new op
- c.ClearSnapshot()
+ // Todo: this will fail if somehow the filesystem is shared with another
+ // computer. Should add a configuration that prevent the cleaning of the
+ // lock file
- return nil
-}
+ f, err := os.Open(lockPath)
-func (c *BugCache) Close() error {
- author, err := bug.GetUser(c.repo)
- if err != nil {
+ if err != nil && !os.IsNotExist(err) {
return err
}
- operations.Close(c.bug, author)
+ if err == nil {
+ // lock file already exist
+ buf, err := ioutil.ReadAll(io.LimitReader(f, 10))
+ if err != nil {
+ return err
+ }
+ if len(buf) == 10 {
+ return fmt.Errorf("The lock file should be < 10 bytes")
+ }
- // TODO: perf --> the snapshot could simply be updated with the new op
- c.ClearSnapshot()
+ pid, err := strconv.Atoi(string(buf))
+ if err != nil {
+ return err
+ }
- return nil
-}
+ if util.ProcessIsRunning(pid) {
+ return fmt.Errorf("The repository you want to access is already locked by the process pid %d", pid)
+ }
-func (c *BugCache) SetTitle(title string) error {
- author, err := bug.GetUser(c.repo)
- if err != nil {
- return err
- }
+ // The lock file is just laying there after a crash, clean it
- operations.SetTitle(c.bug, author, title)
+ fmt.Println("A lock file is present but the corresponding process is not, removing it.")
+ err = f.Close()
+ if err != nil {
+ return err
+ }
- // TODO: perf --> the snapshot could simply be updated with the new op
- c.ClearSnapshot()
+ os.Remove(lockPath)
+ if err != nil {
+ return err
+ }
+ }
return nil
}
-func (c *BugCache) Commit() error {
- return c.bug.Commit(c.repo)
-}
-
-func (c *BugCache) CommitAsNeeded() error {
- if c.bug.HasPendingOp() {
- return c.bug.Commit(c.repo)
- }
- return nil
+func repoLockFilePath(repo repository.Repo) string {
+ return path.Join(repo.GetPath(), ".git", "git-bug", lockfile)
}
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
new file mode 100644
index 00000000..e58165d2
--- /dev/null
+++ b/cache/repo_cache.go
@@ -0,0 +1,144 @@
+package cache
+
+import (
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/bug/operations"
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util"
+)
+
+type RepoCacher interface {
+ Repository() repository.Repo
+ ResolveBug(id string) (BugCacher, error)
+ ResolveBugPrefix(prefix string) (BugCacher, error)
+ AllBugIds() ([]string, error)
+ ClearAllBugs()
+
+ // Mutations
+ NewBug(title string, message string) (BugCacher, error)
+ NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error)
+ Fetch(remote string) (string, error)
+ MergeAll(remote string) <-chan bug.MergeResult
+ Pull(remote string, out io.Writer) error
+ Push(remote string) (string, error)
+}
+
+type RepoCache struct {
+ repo repository.Repo
+ bugs map[string]BugCacher
+}
+
+func NewRepoCache(r repository.Repo) RepoCacher {
+ return &RepoCache{
+ repo: r,
+ bugs: make(map[string]BugCacher),
+ }
+}
+
+func (c *RepoCache) Repository() repository.Repo {
+ return c.repo
+}
+
+func (c *RepoCache) ResolveBug(id string) (BugCacher, error) {
+ cached, ok := c.bugs[id]
+ if ok {
+ return cached, nil
+ }
+
+ b, err := bug.ReadLocalBug(c.repo, id)
+ if err != nil {
+ return nil, err
+ }
+
+ cached = NewBugCache(c.repo, b)
+ c.bugs[id] = cached
+
+ return cached, nil
+}
+
+func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) {
+ // preallocate but empty
+ matching := make([]string, 0, 5)
+
+ for id := range c.bugs {
+ if strings.HasPrefix(id, prefix) {
+ matching = append(matching, id)
+ }
+ }
+
+ // TODO: should check matching bug in the repo as well
+
+ if len(matching) > 1 {
+ return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
+ }
+
+ if len(matching) == 1 {
+ b := c.bugs[matching[0]]
+ return b, nil
+ }
+
+ b, err := bug.FindLocalBug(c.repo, prefix)
+
+ if err != nil {
+ return nil, err
+ }
+
+ cached := NewBugCache(c.repo, b)
+ c.bugs[b.Id()] = cached
+
+ return cached, nil
+}
+
+func (c *RepoCache) AllBugIds() ([]string, error) {
+ return bug.ListLocalIds(c.repo)
+}
+
+func (c *RepoCache) ClearAllBugs() {
+ c.bugs = make(map[string]BugCacher)
+}
+
+func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
+ return c.NewBugWithFiles(title, message, nil)
+}
+
+func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) {
+ author, err := bug.GetUser(c.repo)
+ if err != nil {
+ return nil, err
+ }
+
+ b, err := operations.CreateWithFiles(author, title, message, files)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.Commit(c.repo)
+ if err != nil {
+ return nil, err
+ }
+
+ cached := NewBugCache(c.repo, b)
+ c.bugs[b.Id()] = cached
+
+ return cached, nil
+}
+
+func (c *RepoCache) Fetch(remote string) (string, error) {
+ return bug.Fetch(c.repo, remote)
+}
+
+func (c *RepoCache) MergeAll(remote string) <-chan bug.MergeResult {
+ return bug.MergeAll(c.repo, remote)
+}
+
+func (c *RepoCache) Pull(remote string, out io.Writer) error {
+ return bug.Pull(c.repo, out, remote)
+}
+
+func (c *RepoCache) Push(remote string) (string, error) {
+ return bug.Push(c.repo, remote)
+}
diff --git a/commands/root.go b/commands/root.go
index bbf7d6de..9435ce64 100644
--- a/commands/root.go
+++ b/commands/root.go
@@ -5,6 +5,7 @@ import (
"os"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/repository"
"github.com/spf13/cobra"
)
@@ -68,5 +69,13 @@ func loadRepo(cmd *cobra.Command, args []string) error {
return err
}
+ // Prevent the command from running when the cache has locked the repo
+ // Todo: make it more fine-grained at first
+ // Todo: make the running cache available for other processes
+ err = cache.RepoIsAvailable(repo)
+ if err != nil {
+ return err
+ }
+
return nil
}
diff --git a/commands/webui.go b/commands/webui.go
index 9ebced09..f3f9c184 100644
--- a/commands/webui.go
+++ b/commands/webui.go
@@ -2,11 +2,14 @@ package commands
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
+ "os"
+ "os/signal"
"time"
"github.com/MichaelMure/git-bug/graphql"
@@ -34,23 +37,66 @@ func runWebUI(cmd *cobra.Command, args []string) error {
addr := fmt.Sprintf("127.0.0.1:%d", port)
webUiAddr := fmt.Sprintf("http://%s", addr)
- fmt.Printf("Web UI: %s\n", webUiAddr)
- fmt.Printf("Graphql API: http://%s/graphql\n", addr)
- fmt.Printf("Graphql Playground: http://%s/playground\n", addr)
-
router := mux.NewRouter()
+ graphqlHandler, err := graphql.NewHandler(repo)
+ if err != nil {
+ return err
+ }
+
// Routes
router.Path("/playground").Handler(handler.Playground("git-bug", "/graphql"))
- router.Path("/graphql").Handler(graphql.NewHandler(repo))
+ router.Path("/graphql").Handler(graphqlHandler)
router.Path("/gitfile/{hash}").Handler(newGitFileHandler(repo))
router.Path("/upload").Methods("POST").Handler(newGitUploadFileHandler(repo))
router.PathPrefix("/").Handler(http.FileServer(webui.WebUIAssets))
+ srv := &http.Server{
+ Addr: addr,
+ Handler: router,
+ }
+
+ done := make(chan bool)
+ quit := make(chan os.Signal, 1)
+
+ // register as handler of the interrupt signal to trigger the teardown
+ signal.Notify(quit, os.Interrupt)
+
+ go func() {
+ <-quit
+ fmt.Println("WebUI is shutting down...")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ srv.SetKeepAlivesEnabled(false)
+ if err := srv.Shutdown(ctx); err != nil {
+ log.Fatalf("Could not gracefully shutdown the WebUI: %v\n", err)
+ }
+
+ // Teardown
+ err := graphqlHandler.Close()
+ if err != nil {
+ fmt.Println(err)
+ }
+
+ close(done)
+ }()
+
+ fmt.Printf("Web UI: %s\n", webUiAddr)
+ fmt.Printf("Graphql API: http://%s/graphql\n", addr)
+ fmt.Printf("Graphql Playground: http://%s/playground\n", addr)
+
open.Run(webUiAddr)
- log.Fatal(http.ListenAndServe(addr, router))
+ err = srv.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ return err
+ }
+
+ <-done
+ fmt.Println("WebUI stopped")
return nil
}
diff --git a/graphql/handler.go b/graphql/handler.go
index 507cb508..b452d828 100644
--- a/graphql/handler.go
+++ b/graphql/handler.go
@@ -10,10 +10,22 @@ import (
"net/http"
)
-func NewHandler(repo repository.Repo) http.Handler {
- backend := resolvers.NewBackend()
+type Handler struct {
+ http.HandlerFunc
+ *resolvers.Backend
+}
+
+func NewHandler(repo repository.Repo) (Handler, error) {
+ h := Handler{
+ Backend: resolvers.NewBackend(),
+ }
+
+ err := h.Backend.RegisterDefaultRepository(repo)
+ if err != nil {
+ return Handler{}, err
+ }
- backend.RegisterDefaultRepository(repo)
+ h.HandlerFunc = handler.GraphQL(graph.NewExecutableSchema(h.Backend))
- return handler.GraphQL(graph.NewExecutableSchema(backend))
+ return h, nil
}
diff --git a/util/process.go b/util/process.go
new file mode 100644
index 00000000..ddd3f704
--- /dev/null
+++ b/util/process.go
@@ -0,0 +1,23 @@
+package util
+
+import (
+ "os"
+ "syscall"
+)
+
+// ProcessIsRunning tell is a process is running
+func ProcessIsRunning(pid int) bool {
+ // never return no error in a unix system
+ process, err := os.FindProcess(pid)
+
+ if err != nil {
+ return false
+ }
+
+ // Signal 0 doesn't do anything but allow testing the process
+ err = process.Signal(syscall.Signal(0))
+
+ // Todo: distinguish "you don't have access" and "process doesn't exist"
+
+ return err == nil
+}