blob: 28eca505ba2aeb7557b37c510895c92c2dd200d2 [file] [log] [blame]
/*
* 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];
}
}