| /* |
| * 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; |
| } |
| } |