diff --git a/internal/git/repository.go b/internal/git/repository.go index 506563f1..4d3ad70c 100644 --- a/internal/git/repository.go +++ b/internal/git/repository.go @@ -1,6 +1,7 @@ package git import ( + "errors" "fmt" "os" "path/filepath" @@ -10,9 +11,11 @@ import ( "github.com/spf13/afero" "github.com/evilmartians/lefthook/internal/log" + "github.com/evilmartians/lefthook/internal/version" ) const ( + minGitVersion = "2.31.0" stashMessage = "lefthook auto backup" unstagedPatchName = "lefthook-unstaged.patch" infoDirMode = 0o775 @@ -20,7 +23,8 @@ const ( ) var ( - headBranchRegexp = regexp.MustCompile(`HEAD -> (?P.*)$`) + reHeadBranch = regexp.MustCompile(`HEAD -> (?P.*)$`) + reVersion = regexp.MustCompile(`\d+\.\d+\.\d+`) cmdPushFilesBase = []string{"git", "diff", "--name-only", "HEAD", "@{push}"} cmdPushFilesHead = []string{"git", "diff", "--name-only", "HEAD"} cmdStagedFiles = []string{"git", "diff", "--name-only", "--cached", "--diff-filter=ACMR"} @@ -36,6 +40,7 @@ var ( cmdRemotes = []string{"git", "branch", "--remotes"} cmdHideUnstaged = []string{"git", "checkout", "--force", "--"} cmdEmptyTreeSHA = []string{"git", "hash-object", "-t", "tree", "/dev/null"} + cmdGitVersion = []string{"git", "version"} ) // Repository represents a git repository. @@ -53,6 +58,18 @@ type Repository struct { // NewRepository returns a Repository or an error, if git repository it not initialized. func NewRepository(fs afero.Fs, git *CommandExecutor) (*Repository, error) { + gitVersionOut, err := git.Cmd(cmdGitVersion) + if err == nil { + gitVersion := reVersion.FindString(gitVersionOut) + if err = version.Check(minGitVersion, gitVersion); err != nil { + log.Debugf("[lefthook] version check warning: %s %s", gitVersion, err) + + if errors.Is(err, version.ErrUncoveredVersion) { + log.Warn("Git version is too old. Minimum supported version is " + minGitVersion) + } + } + } + rootPath, err := git.Cmd(cmdRootPath) if err != nil { return nil, err @@ -127,12 +144,12 @@ func (r *Repository) PushFiles() ([]string, error) { } for _, branch := range branches { - if !headBranchRegexp.MatchString(branch) { + if !reHeadBranch.MatchString(branch) { continue } - matches := headBranchRegexp.FindStringSubmatch(branch) - r.headBranch = matches[headBranchRegexp.SubexpIndex("name")] + matches := reHeadBranch.FindStringSubmatch(branch) + r.headBranch = matches[reHeadBranch.SubexpIndex("name")] break } } diff --git a/internal/version/version.go b/internal/version/version.go index 675163f8..7f52687b 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -18,7 +18,10 @@ var ( `^(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+))?)?$`, ) - errIncorrectVersion = errors.New("format of 'min_version' setting is incorrect") + ErrInvalidVersion = errors.New("invalid version format") + ErrUncoveredVersion = errors.New("version is lower than required") + + errInvalidMinVersion = errors.New("format of 'min_version' setting is incorrect") ) func Version(verbose bool) string { @@ -29,47 +32,58 @@ func Version(verbose bool) string { return version } -// CheckCovered returns true if given version is less or equal than current -// and false otherwise. -func CheckCovered(targetVersion string) error { - if len(targetVersion) == 0 { - return nil - } - - if !versionRegexp.MatchString(targetVersion) { - return errIncorrectVersion - } - - major, minor, patch, err := parseVersion(version) - if err != nil { - return err +func Check(wanted, given string) error { + if !versionRegexp.MatchString(given) { + return ErrInvalidVersion } - tMajor, tMinor, tPatch, err := parseVersion(targetVersion) + major, minor, patch, err := parseVersion(given) if err != nil { - return err + return ErrInvalidVersion } - execPath, err := os.Executable() + wantMajor, wantMinor, wantPatch, err := parseVersion(wanted) if err != nil { - execPath = "" + return ErrInvalidVersion } - errUncovered := fmt.Errorf("required lefthook version (%s) is higher than current (%s) at %s", targetVersion, version, execPath) switch { - case major > tMajor: + case major > wantMajor: return nil - case major < tMajor: - return errUncovered - case minor > tMinor: + case major < wantMajor: + return ErrUncoveredVersion + case minor > wantMinor: return nil - case minor < tMinor: - return errUncovered - case patch >= tPatch: + case minor < wantMinor: + return ErrUncoveredVersion + case patch >= wantPatch: return nil default: - return errUncovered + return ErrUncoveredVersion + } +} + +// CheckCovered returns true if given version is less or equal than current +// and false otherwise. +func CheckCovered(targetVersion string) error { + if len(targetVersion) == 0 { + return nil + } + + err := Check(targetVersion, version) + + if errors.Is(err, ErrUncoveredVersion) { + execPath, oserr := os.Executable() + if oserr != nil { + execPath = "" + } + + return fmt.Errorf("required lefthook version (%s) is higher than current (%s) at %s", targetVersion, version, execPath) + } else if errors.Is(err, ErrInvalidVersion) { + return errInvalidMinVersion } + + return err } // parseVersion parses the version string of "1.2.3", "1.2", or just "1" and