Skip to content

Commit

Permalink
Merge pull request #29 from hmiyado/fast-for-local-repository
Browse files Browse the repository at this point in the history
fast for local repository
  • Loading branch information
hmiyado authored Jul 28, 2022
2 parents 399583a + 49759f4 commit 14243a1
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 77 deletions.
15 changes: 8 additions & 7 deletions internal/cli/command_releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,14 @@ func (c *CliContextWrapper) Option() (*core.Option, error) {
}

return &core.Option{
Since: c.Since(),
Until: c.Until(),
IgnorePattern: ignorePattern,
FixCommitPattern: fixCommitPattern,
StartTimerFunc: c.StartTimer,
StopTimerFunc: c.StopTimer,
DebuglnFunc: c.Debugln,
Since: c.Since(),
Until: c.Until(),
IgnorePattern: ignorePattern,
IsLocalRepository: c.context.String("repository") == "",
FixCommitPattern: fixCommitPattern,
StartTimerFunc: c.StartTimer,
StopTimerFunc: c.StopTimer,
DebuglnFunc: c.Debugln,
}, nil
}

Expand Down
113 changes: 91 additions & 22 deletions internal/core/query_releases.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package core

import (
"bufio"
"bytes"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"time"

Expand All @@ -21,12 +25,13 @@ type Option struct {
// inclucive
Since time.Time `json:"since"`
// inclucive
Until time.Time `json:"until"`
IgnorePattern *regexp.Regexp `json:"-"`
FixCommitPattern *regexp.Regexp `json:"-"`
StartTimerFunc func(string) `json:"-"`
StopTimerFunc func(string) `json:"-"`
DebuglnFunc func(...any) `json:"-"`
Until time.Time `json:"until"`
IgnorePattern *regexp.Regexp `json:"-"`
FixCommitPattern *regexp.Regexp `json:"-"`
IsLocalRepository bool `json:"-"`
StartTimerFunc func(string) `json:"-"`
StopTimerFunc func(string) `json:"-"`
DebuglnFunc func(...any) `json:"-"`
}

func (r *Release) String() string {
Expand Down Expand Up @@ -114,23 +119,11 @@ func QueryReleases(repository *git.Repository, option *Option) []*Release {
nextSuccessReleaseIndex = len(releases)
}

var lastCommit *object.Commit
var preReleaseCommit *object.Commit
if i < len(sources)-1 {
preReleaseCommit = sources[i+1].commit
}
restoresPreRelease := false
err := traverseCommits(repository, preReleaseCommit, source.commit, func(c *object.Commit) error {
if option.isFixedCommit(c.Message) {
restoresPreRelease = true
}
lastCommit = c
return nil
})
isRestored = restoresPreRelease
leadTimeForChanges := time.Duration(0)
if err == nil && lastCommit != nil {
leadTimeForChanges = source.commit.Committer.When.Sub(lastCommit.Committer.When)
if option != nil && option.IsLocalRepository {
isRestored, leadTimeForChanges = getIsRestoredAndLeadTimeForChangesByLocalGit(sources, i, option)
} else {
isRestored, leadTimeForChanges = getIsRestoredAndLeadTimeForChangesByGoGit(sources, i, option, repository)
}
option.StopTimer(timerKeyReleaseMetrics)

Expand All @@ -145,3 +138,79 @@ func QueryReleases(repository *git.Repository, option *Option) []*Release {
}
return releases
}

// getIsRestoredAndLeadTimeForChangesByLocalGit gets isRestored and leadTimeForChanges by using local git command.
// Local git command is about 10 times faster than go-git.
func getIsRestoredAndLeadTimeForChangesByLocalGit(
sources []ReleaseSource,
i int,
option *Option,
) (isRestored bool, leadTimeForChanges time.Duration) {
source := sources[i]
since := "1900-01-01"
if i < len(sources)-1 {
preReleaseCommit := sources[i+1].commit
since = preReleaseCommit.Committer.When.Format("2006-01-02")
}
restoresPreRelease := false
output, cmdErr := exec.Command("git", "log",
"--since", since,
`--format="%ct %s"`,
"--date-order",
source.commit.Hash.String(),
).Output()
lastLine := ""
if cmdErr == nil {
scanner := bufio.NewScanner(bytes.NewReader(output))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
if option.isFixedCommit(line) {
restoresPreRelease = true
}
lastLine = line
}
}
isRestored = restoresPreRelease
leadTimeForChanges = time.Duration(0)
if cmdErr == nil && lastLine != "" {
unixtimeString := strings.Split(lastLine, " ")[0]
unixtimeInt, err := strconv.ParseInt(unixtimeString, 10, 64)
if err == nil {
lastCommitWhen := time.Unix(unixtimeInt, 0)
leadTimeForChanges = source.commit.Committer.When.Sub(lastCommitWhen)
}
}
return isRestored, leadTimeForChanges
}

// getIsRestoredAndLeadTimeForChangesByGoGit gets isRestored and leadTimeForChanges by using go-git.
// go-git is slow but it can use in-memory repository.
// When repository is specified by url, repository is in-memory so that go-git is used.
func getIsRestoredAndLeadTimeForChangesByGoGit(
sources []ReleaseSource,
i int,
option *Option,
repository *git.Repository,
) (isRestored bool, leadTimeForChanges time.Duration) {
source := sources[i]
var lastCommit *object.Commit
var preReleaseCommit *object.Commit
if i < len(sources)-1 {
preReleaseCommit = sources[i+1].commit
}
restoresPreRelease := false
err := traverseCommits(repository, preReleaseCommit, source.commit, func(c *object.Commit) error {
if option.isFixedCommit(c.Message) {
restoresPreRelease = true
}
lastCommit = c
return nil
})
isRestored = restoresPreRelease
leadTimeForChanges = time.Duration(0)
if err == nil && lastCommit != nil {
leadTimeForChanges = source.commit.Committer.When.Sub(lastCommit.Committer.When)
}
return isRestored, leadTimeForChanges
}
88 changes: 40 additions & 48 deletions internal/core/query_releases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,30 +85,7 @@ func TestQueryReleasesShouldReturnReleasesWithSpecifiedTimeRange(t *testing.T) {
tag5_2_0 := &Release{Tag: "v5.2.0", Date: time.Date(2020, 10, 9, 11, 49, 30, 0, time.FixedZone("+0200", 2*60*60)), Result: ReleaseResult{IsSuccess: true}}
expectedTags := []*Release{tag5_2_0, tag5_1_0, tag5_0_0}

if len(releases) != len(expectedTags) {
t.Errorf("releases does not have expected tag num. expected: %v. actual: %v", len(expectedTags), len(releases))
return
}

unmatchedRelease := make([]int, 0)
for i, actual := range releases {
expected := expectedTags[i]
if actual.Equal(expected) {
continue
}
unmatchedRelease = append(unmatchedRelease, i)
}

if len(unmatchedRelease) == 0 {
return
}

for i := range unmatchedRelease {
actual := releases[i]
expected := expectedTags[i]
t.Logf("releases[%d] = %s. expected: %v", i, actual, expected)
}
t.Errorf("releases does not have specified")
assertReleasesAreEqual(t, expectedTags, releases)
}

func TestQueryReleasesShouldHaveReleaseResult(t *testing.T) {
Expand Down Expand Up @@ -145,30 +122,7 @@ func TestQueryReleasesShouldHaveReleaseResult(t *testing.T) {
}
expectedTags := []*Release{tag2_1_2, tag2_1_1, tag2_1_0}

if len(releases) != len(expectedTags) {
t.Errorf("releases does not have expected tag num. expected: %v. actual: %v", len(expectedTags), len(releases))
return
}

unmatchedRelease := make([]int, 0)
for i, actual := range releases {
expected := expectedTags[i]
if actual.Equal(expected) {
continue
}
unmatchedRelease = append(unmatchedRelease, i)
}

if len(unmatchedRelease) == 0 {
return
}

for i := range unmatchedRelease {
actual := releases[i]
expected := expectedTags[i]
t.Logf("releases[%d] = %s. expected: %v", i, actual, expected)
}
t.Errorf("releases does not have specified")
assertReleasesAreEqual(t, expectedTags, releases)
}

func TestQueryReleasesShouldReturnReleasesWithIgnorePattern(t *testing.T) {
Expand All @@ -194,6 +148,17 @@ func TestQueryReleasesShouldReturnReleasesWithIgnorePattern(t *testing.T) {
t.Errorf("releases does not have specified")
}

func TestQueryReleasesShouldReturnSameReleasesRepositoryIsLocalOrNot(t *testing.T) {
fourKeysRepository, _ := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{})
releasesOfLocalRepository := QueryReleases(fourKeysRepository, &Option{
IsLocalRepository: true,
})
releasesOfNotLocalRepository := QueryReleases(fourKeysRepository, &Option{
IsLocalRepository: false,
})
assertReleasesAreEqual(t, releasesOfLocalRepository, releasesOfNotLocalRepository)
}

func parseDurationOrZero(str string) time.Duration {
d, err := time.ParseDuration(str)
if err != nil {
Expand All @@ -209,3 +174,30 @@ func parseDurationOrNil(str string) *time.Duration {
}
return &d
}

func assertReleasesAreEqual(t *testing.T, releasesExpected []*Release, releasesActual []*Release) {
if len(releasesActual) != len(releasesExpected) {
t.Errorf("releases does not have same length. expected: %v. actual: %v", len(releasesExpected), len(releasesActual))
return
}

unmatchedRelease := make([]int, 0)
for i, actual := range releasesActual {
expected := releasesExpected[i]
if actual.Equal(expected) {
continue
}
unmatchedRelease = append(unmatchedRelease, i)
}

if len(unmatchedRelease) == 0 {
return
}

for i := range unmatchedRelease {
actual := releasesActual[i]
expected := releasesExpected[i]
t.Logf("releases[%d] = %s. expected: %v", i, actual, expected)
}
t.Errorf("releases does not have specified")
}

0 comments on commit 14243a1

Please sign in to comment.