Skip to content
This repository was archived by the owner on Aug 17, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 130 additions & 40 deletions agent/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,137 @@ package agent

import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/google/uuid"
"gopkg.in/src-d/go-git.v4"

"go.undefinedlabs.com/scopeagent/env"
"go.undefinedlabs.com/scopeagent/tags"
)

type GitData struct {
Repository string
Commit string
SourceRoot string
Branch string
}
type (
GitData struct {
Repository string
Commit string
SourceRoot string
Branch string
}
GitDiff struct {
Type string `json:"type" msgpack:"type"`
Version string `json:"version" msgpack:"version"`
Uuid string `json:"uuid" msgpack:"uuid"`
Files []DiffFileItem `json:"files" msgpack:"files"`
}
DiffFileItem struct {
Path string `json:"path" msgpack:"path"`
Added int `json:"added" msgpack:"added"`
Removed int `json:"removed" msgpack:"removed"`
Status string `json:"status" msgpack:"status"`
PreviousPath *string `json:"previousPath" msgpack:"previousPath"`
}
)

type GitDiff struct {
Type string `json:"type" msgpack:"type"`
Version string `json:"version" msgpack:"version"`
Uuid string `json:"uuid" msgpack:"uuid"`
Files []DiffFileItem `json:"files" msgpack:"files"`
}
type DiffFileItem struct {
Path string `json:"path" msgpack:"path"`
Added int `json:"added" msgpack:"added"`
Removed int `json:"removed" msgpack:"removed"`
Status string `json:"status" msgpack:"status"`
PreviousPath *string `json:"previousPath" msgpack:"previousPath"`
}
var (
refRegex = regexp.MustCompile(`(?m)ref:[ ]*(.*)$`)
remoteRegex = regexp.MustCompile(`(?m)^\[remote[ ]*\"(.*)\"[ ]*\]$`)
branchRegex = regexp.MustCompile(`(?m)^\[branch[ ]*\"(.*)\"[ ]*\]$`)
urlRegex = regexp.MustCompile(`(?m)url[ ]*=[ ]*(.*)$`)
mergeRegex = regexp.MustCompile(`(?m)merge[ ]*=[ ]*(.*)$`)
)

// Gets the current git data
func getGitData() *GitData {
var repository, commit, sourceRoot, branch string

wd, err := os.Getwd()
if err != nil {
return nil
}
repo, err := git.PlainOpenWithOptions(wd, &git.PlainOpenOptions{DetectDotGit: true})
gitFolder, err := getGitFolder()
if err != nil {
return nil
}

if remote, err := repo.Remote("origin"); err == nil {
urls := remote.Config().URLs
if len(urls) > 0 {
repository = urls[0]
}
}
var repository, commit, sourceRoot, branch string

if head, err := repo.Head(); err == nil {
commit = head.Hash().String()
branch = head.Name().Short()
// Get source root
sourceRoot = filepath.Dir(gitFolder)

// Get commit hash
var mergePath string
if headFile, err := os.Open(filepath.Join(gitFolder, "HEAD")); err == nil {
defer headFile.Close()
if headBytes, err := ioutil.ReadAll(headFile); err == nil {
head := string(headBytes)
// HEAD data: https://git-scm.com/book/en/v2/Git-Internals-Git-References
refMatch := refRegex.FindStringSubmatch(head)
if len(refMatch) == 2 {
// Symbolic reference
mergePath = strings.TrimSpace(refMatch[1])
if refFile, err := os.Open(filepath.Join(gitFolder, mergePath)); err == nil {
defer refFile.Close()
if refBytes, err := ioutil.ReadAll(refFile); err == nil {
commit = strings.TrimSpace(string(refBytes))
}
}
} else {
// Detached head (Plain hash)
commit = strings.TrimSpace(head)
branch = "HEAD"
}
}
}

if tree, err := repo.Worktree(); err == nil {
sourceRoot = tree.Filesystem.Root()
// Get repository and branch
if configFile, err := os.Open(filepath.Join(gitFolder, "config")); err == nil {
defer configFile.Close()
reader := bufio.NewReader(configFile)
scanner := bufio.NewScanner(reader)

var tmpBranch string
var intoRemoteBlock, intoBranchBlock bool
for scanner.Scan() {
line := scanner.Text()

if repository == "" {
if !intoRemoteBlock {
remoteMatch := remoteRegex.FindStringSubmatch(line)
if len(remoteMatch) == 2 {
intoRemoteBlock = remoteMatch[1] == "origin"
continue
}
} else {
urlMatch := urlRegex.FindStringSubmatch(line)
if len(urlMatch) == 2 {
repository = strings.TrimSpace(urlMatch[1])
intoRemoteBlock = false
continue
}
}
}

if branch == "" {
if !intoBranchBlock {
branchMatch := branchRegex.FindStringSubmatch(line)
if len(branchMatch) == 2 {
tmpBranch = branchMatch[1]
intoBranchBlock = true
continue
}
} else {
mergeMatch := mergeRegex.FindStringSubmatch(line)
if len(mergeMatch) == 2 {
mergeData := strings.TrimSpace(mergeMatch[1])
intoBranchBlock = false
if mergeData == mergePath {
branch = tmpBranch
continue
}
}
}
}
}
}

return &GitData{
Expand All @@ -72,9 +143,28 @@ func getGitData() *GitData {
}
}

func getGitFolder() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", nil
}
for {
rel, _ := filepath.Rel("/", dir)
// Exit the loop once we reach the basePath.
if rel == "." {
return "", errors.New("git folder not found")
}
gitPath := fmt.Sprintf("%v/.git", dir)
if pInfo, err := os.Stat(gitPath); err == nil && pInfo.IsDir() {
return gitPath, nil
}
// Going up!
dir += "/.."
}
}

func getGitDiff() *GitDiff {
var diff string
// Git diff with numstat is not supported by "gopkg.in/src-d/go-git.v4"
if diffBytes, err := exec.Command("git", "diff", "--numstat").Output(); err == nil {
diff = string(diffBytes)
} else {
Expand Down
110 changes: 110 additions & 0 deletions agent/git_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package agent

import "testing"

func TestRefRegex(t *testing.T) {
tests := map[string]string{
"ref: refs/heads/git-alternative": "refs/heads/git-alternative",
"ref: refs/heads/git-alternative": "refs/heads/git-alternative",
" ref: refs/heads/git-alternative": "refs/heads/git-alternative",
"ref: a.b_c:d|e-f%g$h(i)/j\\k": "a.b_c:d|e-f%g$h(i)/j\\k",
}
for k, v := range tests {
match := refRegex.FindStringSubmatch(k)
if match == nil {
t.Fatalf("match for '%s' is nil", k)
}
if match[1] != v {
t.Fatalf("match for '%s' is '%s', but '%s' was expected", k, match[1], v)
}
}
}

func TestRemoteRegex(t *testing.T) {
tests := map[string]string{
"[remote \"origin\"]": "origin",
"[remote \"upstream\"]": "upstream",
"[remote \"a-b-c\"]": "a-b-c",
"[remote \"a.b.c\"]": "a.b.c",
"[remote \"a_b_c\"]": "a_b_c",
"[remote \"a\\b\\c\"]": "a\\b\\c",
"[remote \"a$b$c\"]": "a$b$c",
"[remote \"a$b$c\"]": "a$b$c",
"[remote \"a/b/c\"]": "a/b/c",
"[remote \"a b c\"]": "a b c",
"[remote \"a b c\" ]": "a b c",
}
for k, v := range tests {
match := remoteRegex.FindStringSubmatch(k)
if match == nil {
t.Fatalf("match for '%s' is nil", k)
}
if match[1] != v {
t.Fatalf("match for '%s' is '%s', but '%s' was expected", k, match[1], v)
}
}
}

func TestBranchRegex(t *testing.T) {
tests := map[string]string{
"[branch \"origin\"]": "origin",
"[branch \"upstream\"]": "upstream",
"[branch \"a-b-c\"]": "a-b-c",
"[branch \"a.b.c\"]": "a.b.c",
"[branch \"a_b_c\"]": "a_b_c",
"[branch \"a\\b\\c\"]": "a\\b\\c",
"[branch \"a$b$c\"]": "a$b$c",
"[branch \"a$b$c\"]": "a$b$c",
"[branch \"a/b/c\"]": "a/b/c",
"[branch \"a b c\"]": "a b c",
"[branch \"a b c\" ]": "a b c",
}
for k, v := range tests {
match := branchRegex.FindStringSubmatch(k)
if match == nil {
t.Fatalf("match for '%s' is nil", k)
}
if match[1] != v {
t.Fatalf("match for '%s' is '%s', but '%s' was expected", k, match[1], v)
}
}
}

func TestUrlRegex(t *testing.T) {
tests := map[string]string{
" url = git@github.com:undefinedlabs/scope-go-agent.git": "git@github.com:undefinedlabs/scope-go-agent.git",
"url = git@github.com:undefinedlabs/scope-go-agent.git": "git@github.com:undefinedlabs/scope-go-agent.git",
"url = git@github.com:undefinedlabs/scope-go-agent.git": "git@github.com:undefinedlabs/scope-go-agent.git",
" url = git@github.com:undefinedlabs/scope-go-agent.git": "git@github.com:undefinedlabs/scope-go-agent.git",
"url=git@github.com:undefinedlabs/scope-go-agent.git": "git@github.com:undefinedlabs/scope-go-agent.git",
"url =git@github.com:undefinedlabs/scope-go-agent.git": "git@github.com:undefinedlabs/scope-go-agent.git",
" url = a.b_c:d|e-f%g$h(i)/j\\k": "a.b_c:d|e-f%g$h(i)/j\\k",
}
for k, v := range tests {
match := urlRegex.FindStringSubmatch(k)
if match == nil {
t.Fatalf("match for '%s' is nil", k)
}
if match[1] != v {
t.Fatalf("match for '%s' is '%s', but '%s' was expected", k, match[1], v)
}
}
}

func TestMergeRegex(t *testing.T) {
tests := map[string]string{
" merge = refs/heads/no-module-package-name": "refs/heads/no-module-package-name",
"merge = refs/heads/no-module-package-name": "refs/heads/no-module-package-name",
"merge = refs/heads/no-module-package-name": "refs/heads/no-module-package-name",
" merge = a.b_c:d|e-f%g$h(i)/j\\k": "a.b_c:d|e-f%g$h(i)/j\\k",
}
for k, v := range tests {
match := mergeRegex.FindStringSubmatch(k)
if match == nil {
t.Fatalf("match for '%s' is nil", k)
}
if match[1] != v {
t.Fatalf("match for '%s' is '%s', but '%s' was expected", k, match[1], v)
}
}
}
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ require (
github.com/go-errors/errors v1.0.1
github.com/gogo/protobuf v1.3.1
github.com/google/uuid v1.1.1
github.com/kr/pretty v0.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/opentracing/basictracer-go v1.0.0
github.com/opentracing/opentracing-go v1.1.0
github.com/sergi/go-diff v1.1.0 // indirect
github.com/stretchr/testify v1.5.1
github.com/undefinedlabs/go-mpatch v0.0.0-20200122175732-0044123dbb98
github.com/vmihailenco/msgpack v4.0.4+incompatible
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
google.golang.org/appengine v1.6.5 // indirect
google.golang.org/grpc v1.27.1
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
)
Loading