From 1147d8d1bfe69e2209fc5b3e1133984056c2a854 Mon Sep 17 00:00:00 2001 From: Aswin Karthik Date: Mon, 14 Oct 2019 23:52:42 +0530 Subject: [PATCH] Avoid multiple git diff staged commands and parse output from single command --- gitrepo/gitrepo.go | 73 ++++++++++++++++++++++++++++++++++------- gitrepo/gitrepo_test.go | 48 +++++++++++++++------------ 2 files changed, 89 insertions(+), 32 deletions(-) diff --git a/gitrepo/gitrepo.go b/gitrepo/gitrepo.go index fbe32b65..e92510f8 100644 --- a/gitrepo/gitrepo.go +++ b/gitrepo/gitrepo.go @@ -2,14 +2,14 @@ package gitrepo import ( "fmt" + log "github.com/Sirupsen/logrus" "io/ioutil" "os" "os/exec" "path" "path/filepath" + "regexp" "strings" - - log "github.com/Sirupsen/logrus" ) //FilePath represents the absolute path of an added file @@ -38,13 +38,63 @@ func RepoLocatedAt(path string) GitRepo { return GitRepo{absoluteRoot} } -//GetDiffForStagedFiles gets all the staged files and collects the diff section in each file +// GetDiffForStagedFiles gets all the staged files and collects the diff section in each file func (repo GitRepo) GetDiffForStagedFiles() []Addition { - files := repo.stagedFiles() - result := make([]Addition, len(files)) - for i, file := range files { - data := repo.fetchStagedDiff(file) - result[i] = NewAddition(file, data) + stagedContent := repo.executeRepoCommand("git", "diff", "--staged") + content := strings.TrimSpace(string(stagedContent)) + lines := strings.Split(content, "\n") + result := make([]Addition, 0) + + if len(lines) < 1 { + return result + } + + // Standard git diff header pattern + // ref: https://git-scm.com/docs/diff-format#_generating_patches_with_p + headerRegex := regexp.MustCompile("^diff --git a/([^ ]+) ") + + lineNumberOfFirstHeader := 0 + var additionFilename string + for ; lineNumberOfFirstHeader < len(lines); lineNumberOfFirstHeader++ { + if headerRegex.MatchString(lines[lineNumberOfFirstHeader]) { + matches := headerRegex.FindStringSubmatch(lines[lineNumberOfFirstHeader]) + additionFilename = matches[1] + break + } + } + + additionContentBuffer := &strings.Builder{} + for i := lineNumberOfFirstHeader + 1; i < len(lines); i++ { + if headerRegex.MatchString(lines[i]) { + // It is a new diff header + // which means we have reached the next file's header + + // capture content written to buffer so far as addition content + stagedChanges := repo.extractAdditions(additionContentBuffer.String()) + if stagedChanges != nil { + addition := NewAddition(additionFilename, stagedChanges) + result = append( + result, addition, + ) + } + + // get next file name and reset buffer for next iteration + matches := headerRegex.FindStringSubmatch(lines[i]) + additionFilename = matches[1] + additionContentBuffer.Reset() + } else { + additionContentBuffer.WriteString(lines[i]) + additionContentBuffer.WriteRune('\n') + } + } + + // Save last file's diff content + stagedChanges := repo.extractAdditions(additionContentBuffer.String()) + if stagedChanges != nil { + addition := NewAddition(additionFilename, stagedChanges) + result = append( + result, addition, + ) } log.WithFields(log.Fields{ @@ -228,10 +278,11 @@ func (repo *GitRepo) fetchStagedChanges() string { return string(repo.executeRepoCommand("git", "diff", "--cached", "--name-status", "--diff-filter=ACM")) } -//Fetches the currently staged diff and filters the command output to get only the modified sections of the file -func (repo *GitRepo) fetchStagedDiff(fileName string) []byte { +// extractAdditions will accept git diff --staged {file} output and filters the command output +// to get only the modified sections of the file +func (repo *GitRepo) extractAdditions(diffContent string) []byte { var result []byte - changes := strings.Split(string(repo.executeRepoCommand("git", "diff", "--staged", fileName)), "\n") + changes := strings.Split(diffContent, "\n") for _, c := range changes { if !strings.HasPrefix(c, "+++") && !strings.HasPrefix(c, "---") && strings.HasPrefix(c, "+") { diff --git a/gitrepo/gitrepo_test.go b/gitrepo/gitrepo_test.go index e7ddc103..cc891b48 100644 --- a/gitrepo/gitrepo_test.go +++ b/gitrepo/gitrepo_test.go @@ -40,29 +40,35 @@ func TestGetDiffForStagedFiles(t *testing.T) { git.Add("new.txt") additions := repo.GetDiffForStagedFiles() - assert.Len(t, additions, 2) - modifiedAddition := additions[0] - createdAddition := additions[1] - - aTxtFileContents, err := ioutil.ReadFile(path.Join(cloneLocation, "a.txt")) - assert.NoError(t, err) - newTxtFileContents, err := ioutil.ReadFile(path.Join(cloneLocation, "new.txt")) - assert.NoError(t, err) - - expectedModifiedAddition := Addition{ - Path: FilePath("a.txt"), - Name: FileName("a.txt"), - Data: []byte(fmt.Sprintf("%s\n", string(aTxtFileContents))), - } - - expectedCreatedAddition := Addition{ - Path: FilePath("new.txt"), - Name: FileName("new.txt"), - Data: []byte(fmt.Sprintf("%s\n", string(newTxtFileContents))), + if assert.Len(t, additions, 2) { + modifiedAddition := additions[0] + createdAddition := additions[1] + + aTxtFileContents, err := ioutil.ReadFile(path.Join(cloneLocation, "a.txt")) + assert.NoError(t, err) + newTxtFileContents, err := ioutil.ReadFile(path.Join(cloneLocation, "new.txt")) + assert.NoError(t, err) + + expectedModifiedAddition := Addition{ + Path: FilePath("a.txt"), + Name: FileName("a.txt"), + Data: []byte(fmt.Sprintf("%s\n", string(aTxtFileContents))), + } + + expectedCreatedAddition := Addition{ + Path: FilePath("new.txt"), + Name: FileName("new.txt"), + Data: []byte(fmt.Sprintf("%s\n", string(newTxtFileContents))), + } + + // For human-readable comparison + assert.Equal(t, string(expectedModifiedAddition.Data), string(modifiedAddition.Data)) + assert.Equal(t, string(expectedCreatedAddition.Data), string(createdAddition.Data)) + + assert.Equal(t, expectedModifiedAddition, modifiedAddition) + assert.Equal(t, expectedCreatedAddition, createdAddition) } - assert.Equal(t, expectedModifiedAddition, modifiedAddition) - assert.Equal(t, expectedCreatedAddition, createdAddition) } func TestAdditionsReturnsEditsAndAdds(t *testing.T) {