-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Count lines changed for current file
- Loading branch information
Showing
8 changed files
with
460 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package git | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"regexp" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/wakatime/wakatime-cli/pkg/log" | ||
) | ||
|
||
const defaultCountLinesChangedTimeoutSecs = 2 | ||
|
||
var gitLinesChangedRegex = regexp.MustCompile(`^(?P<added>\d+)\s*(?P<removed>\d+)\s*(?s).*$`) | ||
|
||
type ( | ||
// Git is an interface to git. | ||
Git interface { | ||
CountLinesChanged() (*int, *int, error) | ||
} | ||
|
||
// Client is a git client. | ||
Client struct { | ||
filepath string | ||
GitCmd func(args ...string) (string, error) | ||
} | ||
) | ||
|
||
// New creates a new git client. | ||
func New(filepath string) *Client { | ||
return &Client{ | ||
filepath: filepath, | ||
GitCmd: gitCmdFn, | ||
} | ||
} | ||
|
||
// gitCmdFn runs a git command with the specified env vars and returns its output or errors. | ||
func gitCmdFn(args ...string) (string, error) { | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultCountLinesChangedTimeoutSecs*time.Second) | ||
defer cancel() | ||
|
||
cmd := exec.CommandContext(ctx, "git", args...) | ||
cmd.Stderr = os.Stderr | ||
|
||
out, err := cmd.Output() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to execute git command: %s", err) | ||
} | ||
|
||
return string(out), nil | ||
} | ||
|
||
// CountLinesChanged counts the number of lines added and removed in a file. | ||
func (c *Client) CountLinesChanged() (*int, *int, error) { | ||
if !fileExists(c.filepath) { | ||
return nil, nil, nil | ||
} | ||
|
||
out, err := c.GitCmd("diff", "--numstat", c.filepath) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("failed to count lines changed: %s", err) | ||
} | ||
|
||
if out == "" { | ||
// Maybe it's staged, try with --cached. | ||
out, err = c.GitCmd("diff", "--numstat", "--cached", c.filepath) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("failed to count lines changed: %s", err) | ||
} | ||
} | ||
|
||
if out == "" { | ||
return nil, nil, nil | ||
} | ||
|
||
match := gitLinesChangedRegex.FindStringSubmatch(out) | ||
paramsMap := make(map[string]string) | ||
|
||
for i, name := range gitLinesChangedRegex.SubexpNames() { | ||
if i > 0 && i <= len(match) { | ||
paramsMap[name] = match[i] | ||
} | ||
} | ||
|
||
if len(paramsMap) == 0 { | ||
log.Debugf("failed to parse git diff output: %s", out) | ||
|
||
return nil, nil, nil | ||
} | ||
|
||
var added, removed *int | ||
|
||
if val, ok := paramsMap["added"]; ok { | ||
if v, err := strconv.Atoi(val); err == nil { | ||
added = &v | ||
} | ||
} | ||
|
||
if val, ok := paramsMap["removed"]; ok { | ||
if v, err := strconv.Atoi(val); err == nil { | ||
removed = &v | ||
} | ||
} | ||
|
||
return added, removed, nil | ||
} | ||
|
||
// fileExists checks if a file or directory exist. | ||
func fileExists(fp string) bool { | ||
_, err := os.Stat(fp) | ||
return err == nil || os.IsExist(err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package git_test | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/wakatime/wakatime-cli/pkg/git" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCountLinesChanged(t *testing.T) { | ||
gc := git.New("testdata/main.go") | ||
gc.GitCmd = func(args ...string) (string, error) { | ||
assert.Equal(t, args, []string{"diff", "--numstat", "testdata/main.go"}) | ||
|
||
return "4 1 testdata/main.go", nil | ||
} | ||
|
||
added, removed, err := gc.CountLinesChanged() | ||
require.NoError(t, err) | ||
|
||
require.NotNil(t, added) | ||
require.NotNil(t, removed) | ||
|
||
assert.Equal(t, 4, *added) | ||
assert.Equal(t, 1, *removed) | ||
} | ||
|
||
func TestCountLinesChanged_Err(t *testing.T) { | ||
gc := git.New("testdata/main.go") | ||
gc.GitCmd = func(args ...string) (string, error) { | ||
assert.Equal(t, args, []string{"diff", "--numstat", "testdata/main.go"}) | ||
|
||
return "", errors.New("some error") | ||
} | ||
|
||
added, removed, err := gc.CountLinesChanged() | ||
assert.EqualError(t, err, "failed to count lines changed: some error") | ||
|
||
assert.Nil(t, added) | ||
assert.Nil(t, removed) | ||
} | ||
|
||
func TestCountLinesChanged_Staged(t *testing.T) { | ||
gc := git.New("testdata/main.go") | ||
|
||
var numCalls int | ||
|
||
gc.GitCmd = func(args ...string) (string, error) { | ||
numCalls++ | ||
|
||
switch numCalls { | ||
case 1: | ||
assert.Equal(t, args, []string{"diff", "--numstat", "testdata/main.go"}) | ||
case 2: | ||
assert.Equal(t, args, []string{"diff", "--numstat", "--cached", "testdata/main.go"}) | ||
|
||
return "4 1 testdata/main.go", nil | ||
} | ||
|
||
return "", nil | ||
} | ||
|
||
added, removed, err := gc.CountLinesChanged() | ||
assert.NoError(t, err) | ||
|
||
require.NotNil(t, added) | ||
require.NotNil(t, removed) | ||
|
||
assert.Equal(t, 4, *added) | ||
assert.Equal(t, 1, *removed) | ||
} | ||
|
||
func TestCountLinesChanged_MissingFile(t *testing.T) { | ||
gc := git.New("/tmp/missing-file") | ||
|
||
added, removed, err := gc.CountLinesChanged() | ||
assert.NoError(t, err) | ||
|
||
assert.Nil(t, added) | ||
assert.Nil(t, removed) | ||
} | ||
|
||
func TestCountLinesChanged_NoOutput(t *testing.T) { | ||
gc := git.New("testdata/main.go") | ||
|
||
var numCalls int | ||
|
||
gc.GitCmd = func(args ...string) (string, error) { | ||
numCalls++ | ||
|
||
switch numCalls { | ||
case 1: | ||
assert.Equal(t, args, []string{"diff", "--numstat", "testdata/main.go"}) | ||
case 2: | ||
assert.Equal(t, args, []string{"diff", "--numstat", "--cached", "testdata/main.go"}) | ||
} | ||
|
||
return "", nil | ||
} | ||
|
||
added, removed, err := gc.CountLinesChanged() | ||
assert.NoError(t, err) | ||
|
||
assert.Nil(t, added) | ||
assert.Nil(t, removed) | ||
} | ||
|
||
func TestCountLinesChanged_MalformedOutput(t *testing.T) { | ||
gc := git.New("testdata/main.go") | ||
gc.GitCmd = func(args ...string) (string, error) { | ||
assert.Equal(t, args, []string{"diff", "--numstat", "testdata/main.go"}) | ||
|
||
return "malformed output", nil | ||
} | ||
|
||
added, removed, err := gc.CountLinesChanged() | ||
assert.NoError(t, err) | ||
|
||
assert.Nil(t, added) | ||
assert.Nil(t, removed) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package main | ||
|
||
import "fmt" | ||
|
||
func main() { | ||
sum := 1 + 2 | ||
|
||
fmt.Printf("sum: %d\n", sum) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.