| /* |
| * Copyright 2000-2011 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.commands; |
| |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.util.Key; |
| import gnu.trove.TObjectDoubleHashMap; |
| import gnu.trove.TObjectDoubleProcedure; |
| |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Captures standard remote operation notifications: "counting objects", "compressing objects", "receiving objects", "resolving deltas" and returns the fraction based on the stage of the command execution. |
| * Some operations or some progress indications may be skipped - this will be handled properly. |
| * @author Kirill Likhodedov |
| */ |
| public class GitStandardProgressAnalyzer implements GitProgressAnalyzer { |
| |
| // progress of each operation is stored here. this is an overhead since operations go one by one, |
| // but it looks simpler than storing current operation, checking that ther was no skipped, etc. |
| private TObjectDoubleHashMap<Operation> myOperationsProgress = new TObjectDoubleHashMap<Operation>(4); |
| |
| public static GitLineHandlerListener createListener(final ProgressIndicator indicator) { |
| final GitStandardProgressAnalyzer progressAnalyzer = new GitStandardProgressAnalyzer(); |
| return new GitLineHandlerAdapter() { |
| @Override |
| public void onLineAvailable(String line, Key outputType) { |
| final double fraction = progressAnalyzer.analyzeProgress(line); |
| if (fraction >= 0) { |
| indicator.setFraction(fraction); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * A long git command usually consists of the operations in this enum. |
| * A pattern is used to match the operation from the git output. fraction is used to indicate which part of the total git command |
| * this operation takes (these numbers are not strict, we can't predict how much it will take in reality): |
| * counting objects - 5% |
| * compressing objects - 10 % |
| * receiving objects - 80 % |
| * resolving deltas - 5 % |
| */ |
| private enum Operation { |
| COUNTING_OBJECTS(".*Counting objects: +(\\d+).*", 0.05) { |
| @Override |
| double getProgress(int outputNumber) { // no percentage is given. +20% on each thousand objects. |
| if (outputNumber > 5000) { |
| return 1; |
| } |
| return outputNumber / 5000.0; |
| } |
| }, |
| COMPRESSING_OBJECTS(".*Compressing objects: +(\\d{1,3})%.*", 0.1), |
| RECEIVING_OR_WRITING_OBJECTS(".*(?:Receiving|Writing) objects: +(\\d{1,3})%.*", 0.8), // receiving on fetch, writing on push |
| RESOLVING_DELTAS(".*Resolving deltas: +(\\d{1,3})%.*", 0.05); |
| |
| private Pattern myPattern; |
| private double myFractionInTotal; |
| |
| Operation(String pattern, double fractionInTotal) { |
| myPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); |
| myFractionInTotal = fractionInTotal; |
| } |
| |
| /** |
| * Returns the progress of the operation - the completed fraction (between 0 and 1.0) - basing on the integer given by the git |
| * command output. This output number usually is a percent, but it may vary (see implementations of the method). |
| */ |
| double getProgress(int outputNumber) { |
| return outputNumber / 100.0; |
| } |
| } |
| |
| @Override |
| public double analyzeProgress(String output) { |
| for (Operation operation : Operation.values()) { |
| final Matcher matcher = operation.myPattern.matcher(output); |
| if (matcher.matches()) { |
| try { |
| double operationProgress = operation.getProgress(Integer.parseInt(matcher.group(1))); // progress of this operation |
| myOperationsProgress.put(operation, operationProgress); |
| } catch (NumberFormatException e) { |
| return -1; |
| } |
| return updateTotalProgress(operation); |
| } |
| } |
| return -1; |
| } |
| |
| private double updateTotalProgress(Operation currentOperation) { |
| // marking all operations before this one as completed |
| for (Operation op : Operation.values()) { |
| if (currentOperation.compareTo(op) > 0) { |
| myOperationsProgress.put(op, 1.0); |
| } |
| } |
| // counting progress |
| final double[] totalProgress = new double[1]; |
| myOperationsProgress.forEachEntry(new TObjectDoubleProcedure<Operation>() { |
| @Override |
| public boolean execute(Operation operation, double progress) { |
| totalProgress[0] += operation.myFractionInTotal * progress; |
| return true; |
| } |
| }); |
| return totalProgress[0]; |
| } |
| |
| } |