go.undefinedlabs.com/scopeagent@v0.4.2/agent/git.go (about)

     1  package agent
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/google/uuid"
    16  	"go.undefinedlabs.com/scopeagent/env"
    17  	"go.undefinedlabs.com/scopeagent/tags"
    18  )
    19  
    20  type (
    21  	GitData struct {
    22  		Repository string
    23  		Commit     string
    24  		SourceRoot string
    25  		Branch     string
    26  	}
    27  	GitDiff struct {
    28  		Type    string         `json:"type" msgpack:"type"`
    29  		Version string         `json:"version" msgpack:"version"`
    30  		Uuid    string         `json:"uuid" msgpack:"uuid"`
    31  		Files   []DiffFileItem `json:"files" msgpack:"files"`
    32  	}
    33  	DiffFileItem struct {
    34  		Path         string  `json:"path" msgpack:"path"`
    35  		Added        int     `json:"added" msgpack:"added"`
    36  		Removed      int     `json:"removed" msgpack:"removed"`
    37  		Status       string  `json:"status" msgpack:"status"`
    38  		PreviousPath *string `json:"previousPath" msgpack:"previousPath"`
    39  	}
    40  )
    41  
    42  var (
    43  	refRegex    = regexp.MustCompile(`(?m)ref:[ ]*(.*)$`)
    44  	remoteRegex = regexp.MustCompile(`(?m)^\[remote[ ]*\"(.*)\"[ ]*\]$`)
    45  	branchRegex = regexp.MustCompile(`(?m)^\[branch[ ]*\"(.*)\"[ ]*\]$`)
    46  	urlRegex    = regexp.MustCompile(`(?m)url[ ]*=[ ]*(.*)$`)
    47  	mergeRegex  = regexp.MustCompile(`(?m)merge[ ]*=[ ]*(.*)$`)
    48  )
    49  
    50  // Gets the current git data
    51  func getGitData() *GitData {
    52  	gitFolder, err := getGitFolder()
    53  	if err != nil {
    54  		return nil
    55  	}
    56  
    57  	var repository, commit, sourceRoot, branch string
    58  
    59  	// Get source root
    60  	sourceRoot = filepath.Dir(gitFolder)
    61  
    62  	// Get commit hash
    63  	var mergePath string
    64  	if headFile, err := os.Open(filepath.Join(gitFolder, "HEAD")); err == nil {
    65  		defer headFile.Close()
    66  		if headBytes, err := ioutil.ReadAll(headFile); err == nil {
    67  			head := string(headBytes)
    68  			// HEAD data:  https://git-scm.com/book/en/v2/Git-Internals-Git-References
    69  			refMatch := refRegex.FindStringSubmatch(head)
    70  			if len(refMatch) == 2 {
    71  				// Symbolic reference
    72  				mergePath = strings.TrimSpace(refMatch[1])
    73  				if refFile, err := os.Open(filepath.Join(gitFolder, mergePath)); err == nil {
    74  					defer refFile.Close()
    75  					if refBytes, err := ioutil.ReadAll(refFile); err == nil {
    76  						commit = strings.TrimSpace(string(refBytes))
    77  					}
    78  				}
    79  			} else {
    80  				// Detached head (Plain hash)
    81  				commit = strings.TrimSpace(head)
    82  			}
    83  		}
    84  	}
    85  
    86  	// Get repository and branch
    87  	if configFile, err := os.Open(filepath.Join(gitFolder, "config")); err == nil {
    88  		defer configFile.Close()
    89  		reader := bufio.NewReader(configFile)
    90  		scanner := bufio.NewScanner(reader)
    91  
    92  		var tmpBranch string
    93  		var intoRemoteBlock, intoBranchBlock bool
    94  		for scanner.Scan() {
    95  			line := scanner.Text()
    96  
    97  			if repository == "" {
    98  				if !intoRemoteBlock {
    99  					remoteMatch := remoteRegex.FindStringSubmatch(line)
   100  					if len(remoteMatch) == 2 {
   101  						intoRemoteBlock = remoteMatch[1] == "origin"
   102  						continue
   103  					}
   104  				} else {
   105  					urlMatch := urlRegex.FindStringSubmatch(line)
   106  					if len(urlMatch) == 2 {
   107  						repository = strings.TrimSpace(urlMatch[1])
   108  						intoRemoteBlock = false
   109  						continue
   110  					}
   111  				}
   112  			}
   113  
   114  			if branch == "" {
   115  				if !intoBranchBlock {
   116  					branchMatch := branchRegex.FindStringSubmatch(line)
   117  					if len(branchMatch) == 2 {
   118  						tmpBranch = branchMatch[1]
   119  						intoBranchBlock = true
   120  						continue
   121  					}
   122  				} else {
   123  					mergeMatch := mergeRegex.FindStringSubmatch(line)
   124  					if len(mergeMatch) == 2 {
   125  						mergeData := strings.TrimSpace(mergeMatch[1])
   126  						intoBranchBlock = false
   127  						if mergeData == mergePath {
   128  							branch = tmpBranch
   129  							continue
   130  						}
   131  					}
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	return &GitData{
   138  		Repository: repository,
   139  		Commit:     commit,
   140  		SourceRoot: sourceRoot,
   141  		Branch:     branch,
   142  	}
   143  }
   144  
   145  func getGitFolder() (string, error) {
   146  	dir, err := os.Getwd()
   147  	if err != nil {
   148  		return "", nil
   149  	}
   150  	for {
   151  		rel, _ := filepath.Rel("/", dir)
   152  		// Exit the loop once we reach the basePath.
   153  		if rel == "." {
   154  			return "", errors.New("git folder not found")
   155  		}
   156  		gitPath := fmt.Sprintf("%v/.git", dir)
   157  		if pInfo, err := os.Stat(gitPath); err == nil && pInfo.IsDir() {
   158  			return gitPath, nil
   159  		}
   160  		// Going up!
   161  		dir += "/.."
   162  	}
   163  }
   164  
   165  func getGitDiff() *GitDiff {
   166  	var diff string
   167  	if diffBytes, err := exec.Command("git", "diff", "--numstat").Output(); err == nil {
   168  		diff = string(diffBytes)
   169  	} else {
   170  		return nil
   171  	}
   172  
   173  	reader := bufio.NewReader(strings.NewReader(diff))
   174  	var files []DiffFileItem
   175  	for {
   176  		line, err := reader.ReadString('\n')
   177  		if err != nil {
   178  			break
   179  		}
   180  		diffItem := strings.Split(line, "\t")
   181  		added, _ := strconv.Atoi(diffItem[0])
   182  		removed, _ := strconv.Atoi(diffItem[1])
   183  		path := strings.TrimSuffix(diffItem[2], "\n")
   184  
   185  		files = append(files, DiffFileItem{
   186  			Path:         path,
   187  			Added:        added,
   188  			Removed:      removed,
   189  			Status:       "Modified",
   190  			PreviousPath: nil,
   191  		})
   192  	}
   193  
   194  	id, _ := uuid.NewRandom()
   195  	gitDiff := GitDiff{
   196  		Type:    "com.undefinedlabs.ugdsf",
   197  		Version: "0.1.0",
   198  		Uuid:    id.String(),
   199  		Files:   files,
   200  	}
   201  	return &gitDiff
   202  }
   203  
   204  func getGitInfoFromGitFolder() map[string]interface{} {
   205  	gitData := getGitData()
   206  
   207  	if gitData == nil {
   208  		return nil
   209  	}
   210  
   211  	gitInfo := map[string]interface{}{}
   212  
   213  	if gitData.Repository != "" {
   214  		gitInfo[tags.Repository] = gitData.Repository
   215  	}
   216  	if gitData.Commit != "" {
   217  		gitInfo[tags.Commit] = gitData.Commit
   218  	}
   219  	if gitData.SourceRoot != "" {
   220  		gitInfo[tags.SourceRoot] = gitData.SourceRoot
   221  	}
   222  	if gitData.Branch != "" {
   223  		gitInfo[tags.Branch] = gitData.Branch
   224  	}
   225  
   226  	return gitInfo
   227  }
   228  
   229  func getGitInfoFromEnv() map[string]interface{} {
   230  	gitInfo := map[string]interface{}{}
   231  
   232  	if repository, set := env.ScopeRepository.Tuple(); set && repository != "" {
   233  		gitInfo[tags.Repository] = repository
   234  	}
   235  	if commit, set := env.ScopeCommitSha.Tuple(); set && commit != "" {
   236  		gitInfo[tags.Commit] = commit
   237  	}
   238  	if sourceRoot, set := env.ScopeSourceRoot.Tuple(); set && sourceRoot != "" {
   239  		// We check if is a valid and existing folder
   240  		if fInfo, err := os.Stat(sourceRoot); err == nil && fInfo.IsDir() {
   241  			gitInfo[tags.SourceRoot] = sourceRoot
   242  		}
   243  	}
   244  	if branch, set := env.ScopeBranch.Tuple(); set && branch != "" {
   245  		gitInfo[tags.Branch] = branch
   246  	}
   247  
   248  	return gitInfo
   249  }