-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Drop regex-based parsing in favor of bufio.Scanner based approach This needs at least git v2.13.2
- Loading branch information
Showing
6 changed files
with
400 additions
and
320 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,40 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
"os/exec" | ||
"strings" | ||
) | ||
|
||
const notRepoStatus string = "exit status 128" | ||
|
||
var ErrNotAGitRepo error = errors.New("not a git repo") | ||
|
||
func GetGitOutput(cwd string) (io.Reader, error) { | ||
var buf = new(bytes.Buffer) | ||
|
||
cmd := exec.Command("git", "status", "--porcelain=v2", "--branch") | ||
cmd.Stderr = buf | ||
cmd.Stdout = buf | ||
|
||
if err := cmd.Run(); err != nil { | ||
if cmd.ProcessState.String() == notRepoStatus { | ||
return nil, ErrNotAGitRepo | ||
} | ||
return nil, err | ||
} | ||
|
||
return buf, nil | ||
} | ||
|
||
func PathToGitDir(cwd string) (string, error) { | ||
cmd := exec.Command("git", "rev-parse", "--absolute-git-dir") | ||
cmd.Dir = cwd | ||
out, err := cmd.Output() | ||
if err != nil { | ||
return "", err | ||
} | ||
return strings.TrimSpace(string(out)), nil | ||
} |
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,154 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"io" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
func consumeNext(s *bufio.Scanner) string { | ||
if s.Scan() { | ||
return s.Text() | ||
} | ||
return "" | ||
} | ||
|
||
func (pi *PorcInfo) ParsePorcInfo(r io.Reader) error { | ||
var err error | ||
var s = bufio.NewScanner(r) | ||
|
||
for s.Scan() { | ||
if len(s.Text()) < 1 { | ||
continue | ||
} | ||
|
||
pi.ParseLine(s.Text()) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (pi *PorcInfo) ParseLine(line string) error { | ||
s := bufio.NewScanner(strings.NewReader(line)) | ||
// switch to a word based scanner | ||
s.Split(bufio.ScanWords) | ||
|
||
for s.Scan() { | ||
switch s.Text() { | ||
case "#": | ||
pi.parseBranchInfo(s) | ||
case "1": | ||
pi.parseTrackedFile(s) | ||
case "2": | ||
pi.parseRenamedFile(s) | ||
case "u": | ||
pi.unmerged++ | ||
case "?": | ||
pi.untracked++ | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (pi *PorcInfo) parseBranchInfo(s *bufio.Scanner) (err error) { | ||
// uses the word based scanner from ParseLine | ||
for s.Scan() { | ||
switch s.Text() { | ||
case "branch.oid": | ||
pi.commit = consumeNext(s) | ||
case "branch.head": | ||
pi.branch = consumeNext(s) | ||
case "branch.upstream": | ||
pi.upstream = consumeNext(s) | ||
case "branch.ab": | ||
err = pi.parseAheadBehind(s) | ||
} | ||
} | ||
return err | ||
} | ||
|
||
func (pi *PorcInfo) parseAheadBehind(s *bufio.Scanner) error { | ||
// uses the word based scanner from ParseLine | ||
for s.Scan() { | ||
i, err := strconv.Atoi(s.Text()[1:]) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
switch s.Text()[:1] { | ||
case "+": | ||
pi.ahead = i | ||
case "-": | ||
pi.behind = i | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// parseTrackedFile parses the porcelain v2 output for tracked entries | ||
// doc: https://git-scm.com/docs/git-status#_changed_tracked_entries | ||
// | ||
func (pi *PorcInfo) parseTrackedFile(s *bufio.Scanner) error { | ||
// uses the word based scanner from ParseLine | ||
var index int | ||
for s.Scan() { | ||
switch index { | ||
case 0: // xy | ||
pi.parseXY(s.Text()) | ||
default: | ||
continue | ||
// case 1: // sub | ||
// if s.Text() != "N..." { | ||
// log.Println("is submodule!!!") | ||
// } | ||
// case 2: // mH - octal file mode in HEAD | ||
// log.Println(index, s.Text()) | ||
// case 3: // mI - octal file mode in index | ||
// log.Println(index, s.Text()) | ||
// case 4: // mW - octal file mode in worktree | ||
// log.Println(index, s.Text()) | ||
// case 5: // hH - object name in HEAD | ||
// log.Println(index, s.Text()) | ||
// case 6: // hI - object name in index | ||
// log.Println(index, s.Text()) | ||
// case 7: // path | ||
// log.Println(index, s.Text()) | ||
} | ||
index++ | ||
} | ||
return nil | ||
} | ||
|
||
func (pi *PorcInfo) parseXY(xy string) error { | ||
switch xy[:1] { // parse staged | ||
case "M": | ||
pi.Staged.modified++ | ||
case "A": | ||
pi.Staged.added++ | ||
case "D": | ||
pi.Staged.deleted++ | ||
case "R": | ||
pi.Staged.renamed++ | ||
case "C": | ||
pi.Staged.copied++ | ||
} | ||
|
||
switch xy[1:] { // parse unstaged | ||
case "M": | ||
pi.Unstaged.modified++ | ||
case "A": | ||
pi.Unstaged.added++ | ||
case "D": | ||
pi.Unstaged.deleted++ | ||
case "R": | ||
pi.Unstaged.renamed++ | ||
case "C": | ||
pi.Unstaged.copied++ | ||
} | ||
return nil | ||
} | ||
|
||
func (pi *PorcInfo) parseRenamedFile(s *bufio.Scanner) error { | ||
return pi.parseTrackedFile(s) | ||
} |
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,61 @@ | ||
package main | ||
|
||
import ( | ||
"reflect" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
const gitoutput string = ` | ||
# branch.oid 51c9c58e2175b768137c1e38865f394c76a7d49d | ||
# branch.head master | ||
# branch.upstream origin/master | ||
# branch.ab +1 -10 | ||
1 .M N... 100644 100644 100644 3e2ceb914cf9be46bf235432781840f4145363fd 3e2ceb914cf9be46bf235432781840f4145363fd Gopkg.lock | ||
1 .M N... 100644 100644 100644 cecb683e6e626bcba909ddd36d3357d49f0cfd09 cecb683e6e626bcba909ddd36d3357d49f0cfd09 Gopkg.toml | ||
1 .M N... 100644 100644 100644 aea984b7df090ce3a5826a854f3e5364cd8f2ccd aea984b7df090ce3a5826a854f3e5364cd8f2ccd porcelain.go | ||
1 .D N... 100644 100644 000000 6d9532ba55b84ec4faf214f9cdb9ce70ec8f4f5b 6d9532ba55b84ec4faf214f9cdb9ce70ec8f4f5b porcelain_test.go | ||
2 R. N... 100644 100644 100644 44d0a25072ee3706a8015bef72bdd2c4ab6da76d 44d0a25072ee3706a8015bef72bdd2c4ab6da76d R100 hm.rb hw.rb | ||
u UU N... 100644 100644 100644 100644 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 36c06c8752c78d2aff89571132f3bf7841a7b5c3 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd hw.rb | ||
? _porcelain_test.go | ||
? git.go | ||
? git_test.go | ||
? goreleaser.yml | ||
? vendor/ | ||
` | ||
|
||
var expectedPorcInfo = PorcInfo{ | ||
branch: "master", | ||
commit: "51c9c58e2175b768137c1e38865f394c76a7d49d", | ||
remote: "", | ||
upstream: "origin/master", | ||
ahead: 1, | ||
behind: 10, | ||
untracked: 5, | ||
unmerged: 1, | ||
Unstaged: GitArea{ | ||
modified: 3, | ||
added: 0, | ||
deleted: 1, | ||
renamed: 0, | ||
copied: 0, | ||
}, | ||
Staged: GitArea{ | ||
modified: 0, | ||
added: 0, | ||
deleted: 0, | ||
renamed: 1, | ||
copied: 0, | ||
}, | ||
} | ||
|
||
func TestParsePorcInfo(t *testing.T) { | ||
var pi = new(PorcInfo) | ||
if err := pi.ParsePorcInfo(strings.NewReader(gitoutput)); err != nil { | ||
t.Fatal(err) | ||
} | ||
if !reflect.DeepEqual(&expectedPorcInfo, pi) { | ||
t.Logf("%#+v\n", pi) | ||
t.FailNow() | ||
} | ||
} |
Oops, something went wrong.