| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package git4idea.config; |
| |
| import com.google.common.base.Objects; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.configurations.GeneralCommandLine; |
| import com.intellij.execution.process.CapturingProcessHandler; |
| import com.intellij.execution.process.ProcessOutput; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.CharsetToolkit; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.text.ParseException; |
| import java.util.concurrent.TimeoutException; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * The version of Git. Note that the version number ignores build and commit hash. |
| * The class is able to distinct MSYS and CYGWIN Gits under Windows assuming that msysgit adds the 'msysgit' suffix to the output |
| * of the 'git version' command. |
| * This is not a very good way to distinguish msys and cygwin since in old versions of msys they didn't add the suffix. |
| * |
| * Note: this class has a natural ordering that is inconsistent with equals. |
| */ |
| public final class GitVersion implements Comparable<GitVersion> { |
| |
| /** |
| * Type indicates the type of this git distribution: is it native (unix) or msys or cygwin. |
| * Type UNDEFINED means that the type doesn't matter in certain condition. |
| */ |
| public enum Type { |
| UNIX, |
| MSYS, |
| CYGWIN, |
| /** The type doesn't matter or couldn't be detected. */ |
| UNDEFINED, |
| /** |
| * Information about Git version is unavailable because the GitVcs hasn't fully initialized yet, or because Git executable is invalid. |
| */ |
| NULL |
| } |
| |
| /** |
| * The minimal supported version |
| */ |
| public static final GitVersion MIN = new GitVersion(1, 7, 1, 1); |
| |
| /** |
| * Special version with a special Type which indicates, that Git version information is unavailable. |
| * Probably, because of invalid executable, or when GitVcs hasn't fully initialized yet. |
| */ |
| public static final GitVersion NULL = new GitVersion(0, 0, 0, 0, Type.NULL); |
| |
| private static final Pattern FORMAT = Pattern.compile( |
| "git version (\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(.*)", Pattern.CASE_INSENSITIVE); |
| |
| private static final Logger LOG = Logger.getInstance(GitVersion.class.getName()); |
| |
| private final int myMajor; |
| private final int myMinor; |
| private final int myRevision; |
| private final int myPatchLevel; |
| private final Type myType; |
| |
| private final int myHashCode; |
| |
| public GitVersion(int major, int minor, int revision, int patchLevel, Type type) { |
| myMajor = major; |
| myMinor = minor; |
| myRevision = revision; |
| myPatchLevel = patchLevel; |
| myType = type; |
| myHashCode = Objects.hashCode(myMajor, myMinor, myRevision, myPatchLevel); |
| } |
| |
| /** |
| * Creates new GitVersion with the UNDEFINED Type which actually means that the type doesn't matter for current purpose. |
| */ |
| public GitVersion(int major, int minor, int revision, int patchLevel) { |
| this(major, minor, revision, patchLevel, Type.UNDEFINED); |
| } |
| |
| /** |
| * Parses output of "git version" command. |
| */ |
| @NotNull |
| public static GitVersion parse(String output) throws ParseException { |
| if (StringUtil.isEmptyOrSpaces(output)) { |
| throw new ParseException("Empty git --version output: " + output, 0); |
| } |
| Matcher m = FORMAT.matcher(output.trim()); |
| if (!m.matches()) { |
| throw new ParseException("Unsupported format of git --version output: " + output, 0); |
| } |
| int major = getIntGroup(m, 1); |
| int minor = getIntGroup(m, 2); |
| int rev = getIntGroup(m, 3); |
| int patch = getIntGroup(m, 4); |
| boolean msys = (m.groupCount() >= 5) && m.group(5) != null && m.group(5).toLowerCase().contains("msysgit"); |
| Type type; |
| if (SystemInfo.isWindows) { |
| type = msys ? Type.MSYS : Type.CYGWIN; |
| } else { |
| type = Type.UNIX; |
| } |
| return new GitVersion(major, minor, rev, patch, type); |
| } |
| |
| // Utility method used in parsing - checks that the given capture group exists and captured something - then returns the captured value, |
| // otherwise returns 0. |
| private static int getIntGroup(Matcher matcher, int group) { |
| if (group > matcher.groupCount()+1) { |
| return 0; |
| } |
| final String match = matcher.group(group); |
| if (match == null) { |
| return 0; |
| } |
| return Integer.parseInt(match); |
| } |
| |
| @NotNull |
| public static GitVersion identifyVersion(String gitExecutable) throws TimeoutException, ExecutionException, ParseException { |
| GeneralCommandLine commandLine = new GeneralCommandLine(); |
| commandLine.setExePath(gitExecutable); |
| commandLine.addParameter("--version"); |
| CapturingProcessHandler handler = new CapturingProcessHandler(commandLine.createProcess(), CharsetToolkit.getDefaultSystemCharset()); |
| ProcessOutput result = handler.runProcess(30 * 1000); |
| if (result.isTimeout()) { |
| throw new TimeoutException("Couldn't identify the version of Git - stopped by timeout."); |
| } |
| if (result.getExitCode() != 0 || !result.getStderr().isEmpty()) { |
| LOG.info("getVersion exitCode=" + result.getExitCode() + " errors: " + result.getStderr()); |
| // anyway trying to parse |
| try { |
| parse(result.getStdout()); |
| } catch (ParseException pe) { |
| throw new ExecutionException("Errors while executing git --version. exitCode=" + result.getExitCode() + |
| " errors: " + result.getStderr()); |
| } |
| } |
| return parse(result.getStdout()); |
| } |
| |
| /** |
| * @return true if the version is supported by the plugin |
| */ |
| public boolean isSupported() { |
| return getType() != Type.NULL && compareTo(MIN) >= 0; |
| } |
| |
| /** |
| * Note: this class has a natural ordering that is inconsistent with equals. |
| * Two GitVersions are equal if their number versions are equal and if their types are equal. |
| * Types are considered equal also if one of them is undefined. Otherwise they are compared. |
| */ |
| @Override |
| public boolean equals(final Object obj) { |
| if (!(obj instanceof GitVersion)) { |
| return false; |
| } |
| GitVersion other = (GitVersion) obj; |
| if (compareTo(other) != 0) { |
| return false; |
| } |
| if (myType == Type.UNDEFINED || other.myType == Type.UNDEFINED) { |
| return true; |
| } |
| return myType == other.myType; |
| } |
| |
| /** |
| * Hashcode is computed from numbered components of the version. Thus GitVersions with the same numbered components will be compared |
| * by equals, and there the type will be taken into consideration). |
| */ |
| @Override |
| public int hashCode() { |
| return myHashCode; |
| } |
| |
| /** |
| * Note: this class has a natural ordering that is inconsistent with equals. |
| * Only numbered versions are compared, so |
| * (msys git 1.7.3).compareTo(cygwin git 1.7.3) == 0 |
| * BUT |
| * (msys git 1.7.3).equals(cygwin git 1.7.3) == false |
| * |
| * {@link GitVersion#NULL} is less than any other not-NULL version. |
| */ |
| public int compareTo(@NotNull GitVersion o) { |
| if (o.getType() == Type.NULL) { |
| return (getType() == Type.NULL ? 0 : 1); |
| } |
| int d = myMajor - o.myMajor; |
| if (d != 0) { |
| return d; |
| } |
| d = myMinor - o.myMinor; |
| if (d != 0) { |
| return d; |
| } |
| d = myRevision - o.myRevision; |
| if (d != 0) { |
| return d; |
| } |
| return myPatchLevel - o.myPatchLevel; |
| } |
| |
| @Override |
| public String toString() { |
| final String msysIndicator = (myType == Type.MSYS ? ".msysgit" : ""); |
| return myMajor + "." + myMinor + "." + myRevision + "." + myPatchLevel + msysIndicator; |
| } |
| |
| /** |
| * @return true if this version is older or the same than the given one. |
| */ |
| public boolean isOlderOrEqual(final GitVersion gitVersion) { |
| return gitVersion != null && compareTo(gitVersion) <= 0; |
| } |
| |
| /** |
| * @return true if this version is later or the same than the given one. |
| */ |
| public boolean isLaterOrEqual(GitVersion version) { |
| return version != null && compareTo(version) >= 0; |
| } |
| |
| public Type getType() { |
| return myType; |
| } |
| |
| public boolean isNull() { |
| return getType() == Type.NULL; |
| } |
| |
| } |