2017-10-26 02:23:43 +00:00
|
|
|
package git
|
|
|
|
|
|
|
|
import (
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/git-lfs/git-lfs/subprocess"
|
|
|
|
"github.com/rubyist/tracerx"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
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 23:42:32 +00:00
|
|
|
gitVersionOnce sync.Once
|
|
|
|
gitVersion string
|
|
|
|
gitVersionErr error
|
2017-10-26 02:23:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func Version() (string, error) {
|
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 23:42:32 +00:00
|
|
|
gitVersionOnce.Do(func() {
|
|
|
|
gitVersion, gitVersionErr =
|
|
|
|
subprocess.SimpleExec("git", "version")
|
|
|
|
})
|
|
|
|
return gitVersion, gitVersionErr
|
2017-10-26 02:23:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|