blob: 74d19d2f7af38e55eff993014de7da8ce129e6bf [file] [log] [blame]
/*
* Copyright 2000-2014 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 org.zmlx.hg4idea.log;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.zmlx.hg4idea.HgFile;
import org.zmlx.hg4idea.HgFileRevision;
import org.zmlx.hg4idea.HgRevisionNumber;
import org.zmlx.hg4idea.util.HgChangesetUtil;
import org.zmlx.hg4idea.util.HgVersion;
import java.util.*;
public class HgFileRevisionLogParser extends HgBaseLogParser<HgFileRevision> {
private static final Logger LOG = Logger.getInstance(HgFileRevisionLogParser.class);
@NotNull private final HgFile myHgFile;
@NotNull private final Project myProject;
@NotNull private HgVersion myVersion;
public HgFileRevisionLogParser(@NotNull Project project, @NotNull HgFile hgFile, @NotNull HgVersion currentVersion) {
myProject = project;
myHgFile = hgFile;
myVersion = currentVersion;
}
@Override
@Nullable
protected HgFileRevision convertDetails(@NotNull String rev,
@NotNull String changeset,
@NotNull SmartList<HgRevisionNumber> parents,
@NotNull Date revisionDate,
@NotNull String author,
@NotNull String email,
@NotNull List<String> attributes) {
int numAttributes = attributes.size();
String commitMessage = parseAdditionalStringAttribute(attributes, MESSAGE_INDEX);
String branchName = parseAdditionalStringAttribute(attributes, BRANCH_INDEX);
final HgRevisionNumber vcsRevisionNumber = new HgRevisionNumber(rev, changeset, author, commitMessage, parents);
Set<String> filesAdded = Collections.emptySet();
Set<String> filesModified = Collections.emptySet();
Set<String> filesDeleted = Collections.emptySet();
Map<String, String> copies = Collections.emptyMap();
boolean shouldParseOldTemplate = !myVersion.isBuiltInFunctionSupported();
// At least in the case of the long template, it's OK that we don't have everything...for example, if there were no
// deleted or copied files, then we won't get any attributes for them...
if (numAttributes > FILES_ADDED_INDEX) {
filesAdded = parseFileList(attributes.get(FILES_ADDED_INDEX));
if (numAttributes > FILES_MODIFIED_INDEX) {
filesModified = parseFileList(attributes.get(FILES_MODIFIED_INDEX));
if (numAttributes > FILES_DELETED_INDEX) {
filesDeleted = parseFileList(attributes.get(FILES_DELETED_INDEX));
if (numAttributes > FILES_COPIED_INDEX) {
copies = shouldParseOldTemplate
? parseCopiesFileListAsOldVersion(attributes.get(FILES_COPIED_INDEX))
: parseCopiesFileList(attributes.get(FILES_COPIED_INDEX));
// Only keep renames, i.e. copies where the source file is also deleted.
Iterator<String> keys = copies.keySet().iterator();
while (keys.hasNext()) {
String s = keys.next();
if (filesAdded.contains(copies.get(s)) && filesDeleted.contains(s)) {
filesAdded.remove(copies.get(s));
filesDeleted.remove(s);
}
else if (!filesDeleted.contains(s)) {
keys.remove();
}
}
}
}
}
}
return new HgFileRevision(myProject, myHgFile, vcsRevisionNumber, branchName, revisionDate, author, commitMessage,
filesModified, filesAdded, filesDeleted, copies);
}
private static Set<String> parseFileList(@Nullable String fileListString) {
if (StringUtil.isEmpty(fileListString)) {
return Collections.emptySet();
}
else {
return new HashSet<String>(StringUtil.split(fileListString," "));
}
}
@NotNull
static Map<String, String> parseCopiesFileList(@Nullable String fileListString) {
if (StringUtil.isEmpty(fileListString)) {
return Collections.emptyMap();
}
Map<String, String> copies = new HashMap<String, String>();
List<String> filesList = StringUtil.split(fileListString, HgChangesetUtil.FILE_SEPARATOR);
for (String pairOfFiles : filesList) {
String[] files = pairOfFiles.split("\\s+\\(");
if (files.length != 2) {
LOG.info("Couldn't parse copied files: " + fileListString);
return copies;
}
copies.put(files[1].substring(0, files[1].length() - 1), files[0]);
}
return copies;
}
@NotNull
static Map<String, String> parseCopiesFileListAsOldVersion(@Nullable String fileListString) {
if (StringUtil.isEmpty(fileListString)) {
return Collections.emptyMap();
}
else {
Map<String, String> copies = new HashMap<String, String>();
//hg copied files output looks like: "target1 (source1)target2 (source2)target3 .... (target_n)"
//so we should split i-1 source from i target.
// If some sources or targets contains '(' we suppose that it has Regular Bracket sequence and perform appropriate string parsing.
//if it fails just return. (to avoid ArrayIndexOutOfBoundsException)
String[] filesList = fileListString.split("\\s+\\(");
String target = filesList[0];
for (int i = 1; i < filesList.length; ++i) {
String source = filesList[i];
int afterRightBraceIndex = findRightBracePosition(source);
if (afterRightBraceIndex == -1) {
break;
}
copies.put(source.substring(0, afterRightBraceIndex - 1), target);
if (afterRightBraceIndex >= source.length()) { //the last 'word' in str
break;
}
target = source.substring(afterRightBraceIndex);
}
return copies;
}
}
private static int findRightBracePosition(@NotNull String str) {
int len = str.length();
int depth = 1;
for (int i = 0; i < len; ++i) {
char c = str.charAt(i);
switch (c) {
case '(':
depth++;
break;
case ')':
depth--;
break;
}
if (depth == 0) {
return i + 1;
}
}
LOG.info("Unexpected output during parse copied files in log command " + str);
return -1;
}
}