git-lfs/git/version.go
Taylor Blau 4a992015e5 git/version.go: replace sync.Mutex usage with sync.Once
We use a sync.Mutex to synchronize access to the string pointer
`gitVersion`, which indicates the version of Git used by the system
running Git LFS.

Our basic usage of the sync.Mutex is not incorrect, but we can improve
the readability by instead using a sync.Once. A sync.Once determines
very quickly (and in an atomic, goroutine-safe fashion) whether or not
_any_ function has been run, and if it hasn't, run it.

By doing this, we can--at the first request--produce a value for the
result of running 'git version', and then return it later to the caller.
This has the following benefits:

  - If 'git version' has already been run, we do not need to hold the
    lock for the entire duration of the function.

  - If 'git version' has not already been run, we only run it once,
    retaining the existing behavior.

Only one change, which is the introduction of the `gitVersionErr`
variable. This is a consequence of executing the 'git version' call in a
closure: since we're in a new stack frame, we can't return from our
parent.

Instead, we retain the value for all time, and return _it_, along with
whatever value we got from running 'git version' in the first place.
2019-01-23 15:42:32 -08:00

78 lines
2.1 KiB
Go

package git
import (
"regexp"
"strconv"
"sync"
"github.com/git-lfs/git-lfs/subprocess"
"github.com/rubyist/tracerx"
)
var (
gitVersionOnce sync.Once
gitVersion string
gitVersionErr error
)
func Version() (string, error) {
gitVersionOnce.Do(func() {
gitVersion, gitVersionErr =
subprocess.SimpleExec("git", "version")
})
return gitVersion, gitVersionErr
}
// IsVersionAtLeast returns whether the git version is the one specified or higher
// argument is plain version string separated by '.' e.g. "2.3.1" but can omit minor/patch
func IsGitVersionAtLeast(ver string) bool {
gitver, err := Version()
if err != nil {
tracerx.Printf("Error getting git version: %v", err)
return false
}
return IsVersionAtLeast(gitver, ver)
}
// IsVersionAtLeast compares 2 version strings (ok to be prefixed with 'git version', ignores)
func IsVersionAtLeast(actualVersion, desiredVersion string) bool {
// Capture 1-3 version digits, optionally prefixed with 'git version' and possibly
// with suffixes which we'll ignore (e.g. unstable builds, MinGW versions)
verregex := regexp.MustCompile(`(?:git version\s+)?(\d+)(?:.(\d+))?(?:.(\d+))?.*`)
var atleast uint64
// Support up to 1000 in major/minor/patch digits
const majorscale = 1000 * 1000
const minorscale = 1000
if match := verregex.FindStringSubmatch(desiredVersion); match != nil {
// Ignore errors as regex won't match anything other than digits
major, _ := strconv.Atoi(match[1])
atleast += uint64(major * majorscale)
if len(match) > 2 {
minor, _ := strconv.Atoi(match[2])
atleast += uint64(minor * minorscale)
}
if len(match) > 3 {
patch, _ := strconv.Atoi(match[3])
atleast += uint64(patch)
}
}
var actual uint64
if match := verregex.FindStringSubmatch(actualVersion); match != nil {
major, _ := strconv.Atoi(match[1])
actual += uint64(major * majorscale)
if len(match) > 2 {
minor, _ := strconv.Atoi(match[2])
actual += uint64(minor * minorscale)
}
if len(match) > 3 {
patch, _ := strconv.Atoi(match[3])
actual += uint64(patch)
}
}
return actual >= atleast
}