diff --git a/README.md b/README.md index 4fb453f..de41147 100644 --- a/README.md +++ b/README.md @@ -26,31 +26,31 @@ Installation ------------- 0. Setup your personal note directory with Git. Make the master branch, commit, add `origin`, and `git push origin master -u`. -1. Clone `https://github.com/tanin47/git-notes` to `$GOPATH/src/github.com/tanin47/git-notes`. If your `GOPATH` is empty, maybe you might want to use `~/go`. +1. Install with `go install https://github.com/tanin47/git-notes@latest` 2. Make the config file that contains the paths that will be synced automatically by Git Notes. See the example: `git-notes.json.example` -3. Build the binary with `go build *.go` + It can be found in `~/go/pkg/mod/github.com/tanin47/git-notes@{version with hash will be here}/` -The binary will be built as `git-notes` in the root dir. +The binary can be found as `git-notes` in the `~/go/bin`. (Or more accurate to say, in `$(go env GOBIN)` or even `$(go env GOPATH)/bin`) You can run it by: `git-notes [your-config-file]`. +Probably you need to add next string to you `~/.profile`. +``` sh +export PATH=$(go env GOBIN):$PATH +``` +And set GOBIN with `go env -w GOBIN=$(go env GOPATH)/bin` To make Git Notes run at the startup and in the background, please follow the specific platform instruction below: -### Ubuntu +### Linux -Move `./service_conf/linux.git-notes.service` to `/etc/systemd/system/git-notes.service` +Launch `git-notes --service` +This will add service file `~/.config/systemd/user/git-notes.service` and default config file in `~/.config/git-notes/git-notes.json` -Modify `/etc/systemd/user/git-notes.service` to use the binary that you built above with and your config file. +Enable Git Notes to start at boot: `systemctl --user enable git-notes.service` -Reload service file after moving: `systemctl daemon-reload` +Run: `systemctl --user start git-notes.service` -Enable Git Notes to start at boot: `systemctl enable git-notes.service` - -Run: `systemctl start git-notes.service` - -Read logs: `journalctl -u git-notes.service --follow` - -Start after booting: `systemctl enable git-notes.service` +Read logs: `journalctl --user -u git-notes.service --follow` ### Mac diff --git a/config_reader.go b/config_reader.go index 4bfe4f2..e40a6b5 100644 --- a/config_reader.go +++ b/config_reader.go @@ -6,24 +6,28 @@ import ( ) type Config struct { - Repos []string `json:"Repos"` + Repos []string `json:"repos"` } type ConfigReader interface { Read(path string) (*Config, error) } -type JsonConfigReader struct {} +type JsonConfigReader struct{} func (c *JsonConfigReader) Read(path string) (*Config, error) { file, err := os.Open(path) - if err != nil { return nil, err } + if err != nil { + return nil, err + } decoder := json.NewDecoder(file) var config Config err = decoder.Decode(&config) - if err != nil { return nil, err } + if err != nil { + return nil, err + } return &config, nil } diff --git a/config_reader_test.go b/config_reader_test.go index fafc646..74244c1 100644 --- a/config_reader_test.go +++ b/config_reader_test.go @@ -1,8 +1,9 @@ package main import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestJsonConfigReader_Read(t *testing.T) { diff --git a/git.go b/git.go index 2f41d1d..d67689d 100644 --- a/git.go +++ b/git.go @@ -11,8 +11,8 @@ import ( ) const ( - Error State = "error" - Dirty State = "dirty" + Error State = "error" + Dirty State = "dirty" Ahead State = "ahead" OutOfSync State = "out-of-sync" Sync State = "sync" @@ -27,8 +27,7 @@ type Git interface { Update(path string) error } -type GitCmd struct { -} +type GitCmd struct{} func (g *GitCmd) Sync(path string) error { state, err := g.GetState(path) @@ -60,7 +59,7 @@ func (g *GitCmd) Sync(path string) error { } } -func runCmd(path string, command string, args... string) (string, error) { +func runCmd(path string, command string, args ...string) (string, error) { cmd := exec.Command(command, args...) cmd.Dir = path @@ -159,9 +158,8 @@ func GetStateAgainstRemote(path string) (State, error) { func (g *GitCmd) Update(path string) error { state, err := g.GetState(path) - if err != nil { - return err + return err } switch state { diff --git a/git_test.go b/git_test.go index 3d37dcb..1be1531 100644 --- a/git_test.go +++ b/git_test.go @@ -2,13 +2,13 @@ package main import ( "fmt" - "github.com/stretchr/testify/assert" - "github.com/tanin47/git-notes/internal/test_helpers" "log" "os" "testing" -) + "github.com/stretchr/testify/assert" + "github.com/tanin47/git-notes/internal/test_helpers" +) func assertState(t *testing.T, path string, expectedState State) { gogit := GitCmd{} @@ -293,4 +293,3 @@ func TestGoGit_SyncFixConflict(t *testing.T) { performSync(t, repos.Local) assertState(t, repos.Local, Sync) } - diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..35d5f7c --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/tanin47/git-notes + +go 1.18 + +require github.com/stretchr/testify v1.8.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5164829 --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..c69de71 --- /dev/null +++ b/helpers.go @@ -0,0 +1,13 @@ +//go:build !linux + +package main + +import ( + "log" +) + +func makeService() { + log.Println("Sorry, we have only linux implementation for now") +} + +func makeConfig() {} diff --git a/helpers_linux.go b/helpers_linux.go new file mode 100644 index 0000000..4a66a49 --- /dev/null +++ b/helpers_linux.go @@ -0,0 +1,107 @@ +//go:build linux + +package main + +import ( + "encoding/json" + "log" + "os" + "os/exec" + "path" + + _ "embed" +) + +//go:embed service_conf/linux.git-notes.service +var serviceData []byte + +// makeService installs a service file in $XDG_DATA_HOME/systemd/user or $HOME/.local/share/systemd/user +// More information can be found on https://www.freedesktop.org/software/systemd/man/systemd.unit.html +func makeService() { + var err error + home, err := os.UserHomeDir() + + var pathToService string + if os.Getenv("XDG_CONFIG_HOME") != "" { + log.Println("Creating $XDG_CONFIG_HOME/systemd/user/git-notes.service") + pathToService = path.Join(os.Getenv("XDG_CONFIG_HOME"), "systemd/user") + } else { + log.Println("Creating ~/.config/systemd/user/git-notes.service") + pathToService = path.Join(home, ".config/systemd/user") + } + + if err = os.MkdirAll(pathToService, os.ModePerm); err != nil { + log.Fatalln("Failed making directory for service files", err) + return + } + + pathToService = path.Join(pathToService, "git-notes.service") + if err = writeService(pathToService, serviceData); err != nil { + if os.IsExist(err) { + log.Println("Config already exist, delete it manually if you want to create new one") + } else { + log.Fatal("Error writing service file", err) + } + } + + prc := exec.Command("systemctl", "--user", "daemon-reload") + if _, err = prc.CombinedOutput(); err != nil { + log.Println(prc.String()) + log.Println(err) + } +} + +func makeConfig() { + var err error + var home string + var configData []byte + + home, err = os.UserHomeDir() + + configData, err = json.MarshalIndent( + Config{ + make([]string, 0), + }, + "", "\t", + ) + + var pathToConfig string + if os.Getenv("XDG_CONFIG_HOME") != "" { + log.Println("Creating $XDG_CONFIG_HOME/git-notes/git-notes.json") + pathToConfig = path.Join(os.Getenv("XDG_CONFIG_HOME"), "git-notes") + } else { + log.Println("Creating ~/.config/git-notes/git-notes.json") + pathToConfig = path.Join(home, ".config/git-notes") + } + + if err = os.MkdirAll(pathToConfig, os.ModePerm); err != nil { + log.Fatalln("Failed making directory for config files", err) + return + } + + pathToConfig = path.Join(pathToConfig, "git-notes.json") + if err = writeService(pathToConfig, configData); err != nil { + if os.IsExist(err) { + log.Println("Config already exist, delete it manually if you want to create new one") + } else { + log.Fatal("Error writing service file", err) + } + } +} + +func writeService(path string, data []byte) error { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) + if err != nil { + return err + } + + if _, err = f.Write(data); err != nil { + return err + } + + if err = f.Sync(); err != nil { + return err + } + + return f.Close() +} diff --git a/internal/test_helpers/repo.go b/internal/test_helpers/repo.go index 221d5e8..7759f4a 100644 --- a/internal/test_helpers/repo.go +++ b/internal/test_helpers/repo.go @@ -2,13 +2,14 @@ package test_helpers import ( "fmt" - "github.com/stretchr/testify/assert" "io/ioutil" "log" "os" "os/exec" "strings" "testing" + + "github.com/stretchr/testify/assert" ) type Repos struct { @@ -76,11 +77,10 @@ func SetupRemote(local string, remote string) { func WriteFile(t *testing.T, repoPath string, filePath string, content string) { fullPath := fmt.Sprintf("%s/%s", repoPath, filePath) log.Printf("Write file: %v, content: %v", fullPath, content) - assert.NoError(t, ioutil.WriteFile(fullPath, []byte(content), 0644)) + assert.NoError(t, ioutil.WriteFile(fullPath, []byte(content), 0o644)) } - -func PerformCmd(t *testing.T, path string, cmd string, args... string) { +func PerformCmd(t *testing.T, path string, cmd string, args ...string) { log.Printf("Run cmd: %v", strings.Join(append([]string{cmd}, args...), " ")) c := exec.Command(cmd, args...) c.Dir = path diff --git a/main.go b/main.go index 3bdff0c..3fa81cc 100644 --- a/main.go +++ b/main.go @@ -10,18 +10,25 @@ import ( var Running = true func main() { + if len(os.Args) == 2 && os.Args[1] == "--service" { + log.Println("Git Notes start creating service...") + makeService() + makeConfig() + os.Exit(0) + } + log.Println("Git Notes is starting...") - var git = NewGoGit() - var watcher = GitWatcher{ - git: &git, - running: false, - checkInterval: 10 * time.Second, + git := NewGoGit() + watcher := GitWatcher{ + git: &git, + running: false, + checkInterval: 10 * time.Second, delayBeforeFiringEvent: 2 * time.Second, - delayAfterFiringEvent: 5 * time.Second, + delayAfterFiringEvent: 5 * time.Second, } - var configReader = JsonConfigReader{} - var gitRepoMonitor = GitRepoMonitor{ + configReader := JsonConfigReader{} + gitRepoMonitor := GitRepoMonitor{ scheduledUpdateInterval: 5 * time.Minute, } @@ -38,7 +45,6 @@ func Run(git Git, watcher Watcher, configReader ConfigReader, monitor PathMonito } configPath := os.Args[1] config, err := configReader.Read(configPath) - if err != nil { log.Fatalf("Unable to read the config file. Err: %v", err) } @@ -48,4 +54,3 @@ func Run(git Git, watcher Watcher, configReader ConfigReader, monitor PathMonito monitor.StartMonitoring(repoPath, watcher, git) } } - diff --git a/main_test.go b/main_test.go index c228b5c..de01d6c 100644 --- a/main_test.go +++ b/main_test.go @@ -2,18 +2,19 @@ package main import ( "fmt" - "github.com/stretchr/testify/assert" - "github.com/tanin47/git-notes/internal/test_helpers" "io/ioutil" "os" "testing" "time" + + "github.com/stretchr/testify/assert" + "github.com/tanin47/git-notes/internal/test_helpers" ) func TestMainFunc(t *testing.T) { Running = true - var git = NewGoGit() + git := NewGoGit() repos := test_helpers.SetupRepos() defer test_helpers.CleanupRepos(repos) @@ -41,7 +42,7 @@ func TestMainFunc(t *testing.T) { state, err := git.GetState(repos.Local) assert.NoError(t, err) return state == Sync - }, 15 * time.Second, 1 * time.Second) + }, 15*time.Second, 1*time.Second) test_helpers.WriteFile(t, repos.Local, "test.md", "TestContent2") @@ -53,16 +54,16 @@ func TestMainFunc(t *testing.T) { state, err := git.GetState(repos.Local) assert.NoError(t, err) return state == Sync - }, 15 * time.Second, 1 * time.Second) + }, 15*time.Second, 1*time.Second) Running = false } func TestRun(t *testing.T) { - var git = MockGit{} - var watcher = MockWatcher{} - var configReader = MockConfigReader{} - var monitor = MockMonitor{} + git := MockGit{} + watcher := MockWatcher{} + configReader := MockConfigReader{} + monitor := MockMonitor{} oldArgs := os.Args os.Args = []string{"app", "some-git-notes.json"} @@ -80,7 +81,7 @@ type MockConfigReader struct { func (m *MockConfigReader) Read(path string) (*Config, error) { m.readPath = path - var config = &Config{ + config := &Config{ Repos: []string{"some-path", "some-path-2"}, } return config, nil diff --git a/path_monitor.go b/path_monitor.go index 42d2823..44a496f 100644 --- a/path_monitor.go +++ b/path_monitor.go @@ -22,7 +22,7 @@ func (g *GitRepoMonitor) scheduleUpdate(repoPath string, channel chan string) { } func (g *GitRepoMonitor) StartMonitoring(repoPath string, watcher Watcher, git Git) { - var channel = make(chan string) + channel := make(chan string) err := git.Sync(repoPath) if err != nil { log.Printf("Syncing failed. Err: %v", err) diff --git a/path_monitor_test.go b/path_monitor_test.go index 3b41552..0591e6f 100644 --- a/path_monitor_test.go +++ b/path_monitor_test.go @@ -1,17 +1,18 @@ package main import ( - "github.com/stretchr/testify/assert" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestGitRepoMonitor_StartMonitoring(t *testing.T) { - var gitRepoMonitor = GitRepoMonitor{ + gitRepoMonitor := GitRepoMonitor{ scheduledUpdateInterval: time.Minute, } - var watcher = MockWatcher{} - var git = MockGit{} + watcher := MockWatcher{} + git := MockGit{} gitRepoMonitor.StartMonitoring("some-path", &watcher, &git) @@ -25,25 +26,25 @@ func TestGitRepoMonitor_StartMonitoring(t *testing.T) { } func TestGitRepoMonitor_StartMonitoringAutomaticScheduleUpdate(t *testing.T) { - var gitRepoMonitor = GitRepoMonitor{ + gitRepoMonitor := GitRepoMonitor{ scheduledUpdateInterval: 100 * time.Millisecond, } - var watcher = MockWatcher{} - var git = MockGit{} + watcher := MockWatcher{} + git := MockGit{} gitRepoMonitor.StartMonitoring("some-path", &watcher, &git) assert.Eventually(t, func() bool { return git.Count >= 2 - }, 1 * time.Second, 10 * time.Millisecond) + }, 1*time.Second, 10*time.Millisecond) } func TestGitRepoMonitor_ScheduleUpdate(t *testing.T) { - var gitRepoMonitor = GitRepoMonitor{ + gitRepoMonitor := GitRepoMonitor{ scheduledUpdateInterval: 100 * time.Millisecond, } - var channel = make(chan string) + channel := make(chan string) var path string go func() { @@ -54,7 +55,7 @@ func TestGitRepoMonitor_ScheduleUpdate(t *testing.T) { assert.Eventually(t, func() bool { return path == "some-path" - }, 1 * time.Second, 10 * time.Millisecond) + }, 1*time.Second, 10*time.Millisecond) } type MockWatcher struct { diff --git a/service_conf/linux.git-notes.service b/service_conf/linux.git-notes.service index b4197a0..cbce23f 100644 --- a/service_conf/linux.git-notes.service +++ b/service_conf/linux.git-notes.service @@ -1,10 +1,12 @@ [Unit] Description=Git Notes +After=network.target [Service] Type=simple -User=tanin -ExecStart=/home/tanin/go/src/github.com/tanin47/git-notes/git-notes /home/tanin/go/src/github.com/tanin47/git-notes/git-notes.json + +ExecStart=%h/go/bin/git-notes %h/.config/git-notes/git-notes.json + Restart=always RestartSec=60 diff --git a/watcher.go b/watcher.go index 0a63263..cdb433c 100644 --- a/watcher.go +++ b/watcher.go @@ -10,11 +10,11 @@ type Watcher interface { } type GitWatcher struct { - git Git - running bool - checkInterval time.Duration + git Git + running bool + checkInterval time.Duration delayBeforeFiringEvent time.Duration - delayAfterFiringEvent time.Duration + delayAfterFiringEvent time.Duration } func (f *GitWatcher) Stop() { @@ -23,7 +23,6 @@ func (f *GitWatcher) Stop() { func (f *GitWatcher) Check(path string, channel chan string) { dirty, err := f.git.IsDirty(path) - if err != nil { log.Printf("Failed to get state. Error: %v", err) } @@ -44,5 +43,4 @@ func (f *GitWatcher) Watch(path string, channel chan string) { f.Check(path, channel) } }() - } diff --git a/watcher_test.go b/watcher_test.go index d21771c..139036d 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -1,12 +1,13 @@ package main import ( - "github.com/stretchr/testify/assert" - "github.com/tanin47/git-notes/internal/test_helpers" "log" "os" "testing" "time" + + "github.com/stretchr/testify/assert" + "github.com/tanin47/git-notes/internal/test_helpers" ) type listener struct { @@ -16,21 +17,21 @@ type listener struct { func setup() (*GitWatcher, *listener, string, chan string) { var channel chan string = make(chan string) - var watcher = GitWatcher { - git: &GitCmd{}, - running: false, - checkInterval: 10 * time.Millisecond, + watcher := GitWatcher{ + git: &GitCmd{}, + running: false, + checkInterval: 10 * time.Millisecond, delayBeforeFiringEvent: 0, - delayAfterFiringEvent: 1 * time.Second, + delayAfterFiringEvent: 1 * time.Second, } - var path = test_helpers.SetupGitRepo("watcher", false) + path := test_helpers.SetupGitRepo("watcher", false) var listener listener go func() { for { - path = <- channel + path = <-channel listener.paths = append(listener.paths, path) } }() @@ -53,7 +54,7 @@ func commit(t *testing.T, path string) { } func TestGitWatcher_Watch(t *testing.T) { - var watcher, listener, path, channel = setup() + watcher, listener, path, channel := setup() defer cleanup(watcher, path) watcher.Watch(path, channel) @@ -67,7 +68,7 @@ func TestGitWatcher_Watch(t *testing.T) { } func TestGitWatcher_CreateAndModify(t *testing.T) { - var watcher, listener, path, channel = setup() + watcher, listener, path, channel := setup() defer cleanup(watcher, path) watcher.Check(path, channel)