Skip to content

Commit

Permalink
Merge pull request #17 from timeglass/0.5
Browse files Browse the repository at this point in the history
Version 0.5
  • Loading branch information
advanderveer committed May 22, 2015
2 parents 32e4fc9 + 2d56765 commit d2192a0
Show file tree
Hide file tree
Showing 18 changed files with 547 additions and 18 deletions.
10 changes: 8 additions & 2 deletions README.md
Expand Up @@ -8,8 +8,10 @@ __Features:__

- The timer __automatically starts__ when you switch to a (new) branch using `git checkout`
- The timer __automatically pauses__ when it doesn't detect any file activity for a while
- The time you spent is automatically added to the next `git commit` message
- The time you spent is automatically added to the next `git commit` message by default
- The timer increments in discreet steps: the _minimal billable unit_ (MBU), by default this is 1m.
- Spent time is stored as commit metadata using [git-notes](git-scm.com/docs/git-notes) and pushed automatically to your remote by default


__Currently Supported: (see roadmap)__

Expand All @@ -26,7 +28,7 @@ __Currently Supported: (see roadmap)__
glass init
```

_NOTE: you'll only have to run this once per clone_
_NOTE: you'll have to run this once per clone_

3. Start the timer by creating a new branch:

Expand All @@ -47,6 +49,10 @@ __Currently Supported: (see roadmap)__
git log -n 1
```

## Documentation
More documentation will be available in the future, for now we offer the following:

- [Configuration Reference](/docs/config.md)

## Roadmap, input welcome!

Expand Down
2 changes: 1 addition & 1 deletion VERSION
@@ -1 +1 @@
0.4.0
0.5.0
6 changes: 3 additions & 3 deletions command/init.go
Expand Up @@ -44,16 +44,16 @@ func (c *Init) Run(ctx *cli.Context) error {
return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err)
}

vcs, err := vcs.GetVCS(dir)
vc, err := vcs.GetVCS(dir)
if err != nil {
return errwrap.Wrapf("Failed to setup VCS: {{err}}", err)
}

err = vcs.Hook()
err = vc.Hook()
if err != nil {
return errwrap.Wrapf("Failed to write hooks: {{err}}", err)
}

fmt.Println("Timeglass: hooks written")
return nil
return NewPull().Run(ctx)
}
29 changes: 26 additions & 3 deletions command/lap.go
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/hashicorp/errwrap"

"github.com/timeglass/glass/model"
"github.com/timeglass/glass/vcs"
)

type Lap struct {
Expand All @@ -23,15 +24,20 @@ func (c *Lap) Name() string {
}

func (c *Lap) Description() string {
return fmt.Sprintf("<description>")
return fmt.Sprintf("Resets the running timer, report spent time and punch as time spent on last commit")
}

func (c *Lap) Usage() string {
return "Show the measured time and reset the timer to 0s"
return "Register time spent on last commit and reset the timer to 0s"
}

func (c *Lap) Flags() []cli.Flag {
return []cli.Flag{}
return []cli.Flag{
cli.BoolFlag{
Name: "from-hook",
Usage: "Indicate it is called from a git, now expects refs on stdin",
},
}
}

func (c *Lap) Action() func(ctx *cli.Context) {
Expand All @@ -50,16 +56,33 @@ func (c *Lap) Run(ctx *cli.Context) error {
return errwrap.Wrapf(fmt.Sprintf("Failed to get Daemon address: {{err}}"), err)
}

//get time and reset
client := NewClient(info)
t, err := client.Lap()
if err != nil {
if err == ErrDaemonDown {
//if were calling this from a hook, supress errors
if ctx.Bool("from-hook") {
return nil
}

return errwrap.Wrapf(fmt.Sprintf("No timer appears to be running for '%s': {{err}}", dir), err)
} else {
return err
}
}

//write the vcs
vc, err := vcs.GetVCS(dir)
if err != nil {
return errwrap.Wrapf("Failed to setup VCS: {{err}}", err)
}

err = vc.Log(t)
if err != nil {
return errwrap.Wrapf("Failed to log time into VCS: {{err}}", err)
}

fmt.Println(t)
return nil
}
69 changes: 69 additions & 0 deletions command/pull.go
@@ -0,0 +1,69 @@
package command

import (
"fmt"
"os"

"github.com/codegangsta/cli"
"github.com/hashicorp/errwrap"

"github.com/timeglass/glass/vcs"
)

type Pull struct {
*command
}

func NewPull() *Pull {
return &Pull{newCommand()}
}

func (c *Pull) Name() string {
return "pull"
}

func (c *Pull) Description() string {
return fmt.Sprintf("Pull the Timeglass notes branch from the remote repository. Provide the remote's name as the first argument, if no argument is provided it tries to pull from to the VCS default remote")
}

func (c *Pull) Usage() string {
return "Pull time data from the remote repository"
}

func (c *Pull) Flags() []cli.Flag {
return []cli.Flag{}
}

func (c *Pull) Action() func(ctx *cli.Context) {
return c.command.Action(c.Run)
}

func (c *Pull) Run(ctx *cli.Context) error {
dir, err := os.Getwd()
if err != nil {
return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err)
}

vc, err := vcs.GetVCS(dir)
if err != nil {
return errwrap.Wrapf("Failed to setup VCS: {{err}}", err)
}

remote := ctx.Args().First()
if remote == "" {
remote = vc.DefaultRemote()
}

err = vc.Fetch(remote)
if err != nil {
if err == vcs.ErrNoRemoteTimeData {
fmt.Printf("Timeglass: remote '%s' has no time data (yet), nothing to pull\n", remote)
return nil
}

return errwrap.Wrapf("Failed to pull time data: {{err}}", err)
}

fmt.Println("Timeglass: time data fetched successfully")
return nil
}
69 changes: 69 additions & 0 deletions command/punch.go
@@ -0,0 +1,69 @@
package command

import (
"fmt"
"os"
"time"

"github.com/codegangsta/cli"
"github.com/hashicorp/errwrap"

"github.com/timeglass/glass/vcs"
)

type Punch struct {
*command
}

func NewPunch() *Punch {
return &Punch{newCommand()}
}

func (c *Punch) Name() string {
return "punch"
}

func (c *Punch) Description() string {
return fmt.Sprintf("")
}

func (c *Punch) Usage() string {
return "Manually register time spent on the last commit"
}

func (c *Punch) Flags() []cli.Flag {
return []cli.Flag{}
}

func (c *Punch) Action() func(ctx *cli.Context) {
return c.command.Action(c.Run)
}

func (c *Punch) Run(ctx *cli.Context) error {
dir, err := os.Getwd()
if err != nil {
return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err)
}

if ctx.Args().First() == "" {
return fmt.Errorf("Please provide the time you spent as the first argument")
}

t, err := time.ParseDuration(ctx.Args().First())
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Failed to parse provided argument '%s' as a valid duration (e.g 1h2m10s): {{err}}", ctx.Args().First()), err)
}

//write the vcs
vc, err := vcs.GetVCS(dir)
if err != nil {
return errwrap.Wrapf("Failed to setup VCS: {{err}}", err)
}

err = vc.Log(t)
if err != nil {
return errwrap.Wrapf("Failed to log time into VCS: {{err}}", err)
}

return nil
}
106 changes: 106 additions & 0 deletions command/push.go
@@ -0,0 +1,106 @@
package command

import (
"fmt"
"io/ioutil"
"os"

"github.com/codegangsta/cli"
"github.com/hashicorp/errwrap"

"github.com/timeglass/glass/model"
"github.com/timeglass/glass/vcs"
)

type Push struct {
*command
}

func NewPush() *Push {
return &Push{newCommand()}
}

func (c *Push) Name() string {
return "push"
}

func (c *Push) Description() string {
return fmt.Sprintf("Pushes the Timeglass notes branch to the remote repository. Provide the remote's name as the first argument, if no argument is provided it tries to push to the VCS default remote")
}

func (c *Push) Usage() string {
return "Push time data to the remote repository"
}

func (c *Push) Flags() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: "from-hook",
Usage: "Indicate it is called from a git, now expects refs on stdin",
},
}
}

func (c *Push) Action() func(ctx *cli.Context) {
return c.command.Action(c.Run)
}

func (c *Push) Run(ctx *cli.Context) error {
dir, err := os.Getwd()
if err != nil {
return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err)
}

m := model.New(dir)
conf, err := m.ReadConfig()
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Failed to read configuration: {{err}}"), err)
}

//hooks require us require us to check the refs that are pushed over stdin
//to prevent inifinte push loop
refs := ""
if ctx.Bool("from-hook") {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return errwrap.Wrapf("Failed to read from stdin: {{err}}", err)
}

refs = string(bytes)
//when `glass push` triggers the pre-push hook it will not
//provide any refs on stdin
//this probalby means means there is nothing left to push and
//we return here to prevent recursive push
if refs == "" {
return nil
}

//configuration can explicitly request not to push time data automatically
//on hook usage
if !conf.AutoPush {
return nil
}
}

vc, err := vcs.GetVCS(dir)
if err != nil {
return errwrap.Wrapf("Failed to setup VCS: {{err}}", err)
}

remote := ctx.Args().First()
if remote == "" {
remote = vc.DefaultRemote()
}

err = vc.Push(remote, refs)
if err != nil {
if err == vcs.ErrNoLocalTimeData {
fmt.Printf("Timeglass: local clone has no time data (yet), nothing to push to '%s'. Start a timer and commit changes to record local time data.\n", remote)
return nil
}

return errwrap.Wrapf("Failed to push time data: {{err}}", err)
}

return nil
}
7 changes: 6 additions & 1 deletion command/start.go
Expand Up @@ -51,14 +51,19 @@ func (c *Start) Run(ctx *cli.Context) error {
return errwrap.Wrapf(fmt.Sprintf("Failed to get Daemon address: {{err}}"), err)
}

conf, err := m.ReadConfig()
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Failed to read configuration: {{err}}"), err)
}

client := NewClient(info)
err = client.Call("timer.start")
if err != nil {
if err != ErrDaemonDown {
return err
}

cmd := exec.Command("glass-daemon")
cmd := exec.Command("glass-daemon", fmt.Sprintf("--mbu=%s", conf.MBU))
err := cmd.Start()
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Failed to start Daemon: {{err}}"), err)
Expand Down

0 comments on commit d2192a0

Please sign in to comment.