From d783d869a3ce2010e907cfa15addeddc3d294be7 Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Tue, 1 Apr 2014 20:46:03 +0400 Subject: [PATCH] IDEA-122670 Hg log: request only necessary log information *common hg parser for log command created; *construct appropriate VcsCommit type using this HgBaseLogParser requesting only needed arguments from hg; *HgLogCommand changed; *annotations added; *String.split method calls changed to StringUtil.split if possible; *several parsing methods became package private and appropriate test moved to appropriate test package for getting access --- .../zmlx/hg4idea/command/HgLogCommand.java | 246 +------------ .../org/zmlx/hg4idea/log/HgBaseLogParser.java | 175 +++++++++ .../hg4idea/log/HgFileRevisionLogParser.java | 176 +++++++++ .../org/zmlx/hg4idea/log/HgHistoryUtil.java | 334 ++++++++++++++++++ .../org/zmlx/hg4idea/log/HgLogProvider.java | 4 +- .../org/zmlx/hg4idea/util/HgHistoryUtil.java | 257 -------------- .../test/history/HgLogHistoryTest.java | 9 +- .../zmlx/hg4idea/log}/HgLogParseTest.java | 11 +- 8 files changed, 703 insertions(+), 509 deletions(-) create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/log/HgBaseLogParser.java create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/log/HgFileRevisionLogParser.java create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java delete mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/util/HgHistoryUtil.java rename plugins/hg4idea/testSrc/{hg4idea/test/history => org/zmlx/hg4idea/log}/HgLogParseTest.java (86%) diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java index 7a7b2d26ac58..639b86eedf0d 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java @@ -14,45 +14,31 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ArrayUtil; 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.HgVcs; import org.zmlx.hg4idea.execution.HgCommandException; import org.zmlx.hg4idea.execution.HgCommandExecutor; import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.log.HgBaseLogParser; +import org.zmlx.hg4idea.log.HgFileRevisionLogParser; +import org.zmlx.hg4idea.log.HgHistoryUtil; import org.zmlx.hg4idea.util.HgChangesetUtil; import org.zmlx.hg4idea.util.HgUtil; import org.zmlx.hg4idea.util.HgVersion; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; public class HgLogCommand { private static final Logger LOG = Logger.getInstance(HgLogCommand.class.getName()); - private static final int REVISION_INDEX = 0; - private static final int CHANGESET_INDEX = 1; - private static final int PARENTS_INDEX = 2; - - private static final int DATE_INDEX = 3; - private static final int AUTHOR_INDEX = 4; - private static final int BRANCH_INDEX = 5; - private static final int MESSAGE_INDEX = 6; - private static final int FILES_ADDED_INDEX = 7; - private static final int FILES_MODIFIED_INDEX = 8; - private static final int FILES_DELETED_INDEX = 9; - private static final int FILES_COPIED_INDEX = 10; - - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); @NotNull private final Project myProject; @NotNull private HgVersion myVersion; @@ -100,107 +86,14 @@ public class HgLogCommand { return Collections.emptyList(); } - String[] templates = constructTemplateArgument(includeFiles, myVersion); + String[] templates = HgBaseLogParser.constructFullTemplateArgument(includeFiles, myVersion); String template = HgChangesetUtil.makeTemplate(templates); - int expectedItemCount = templates.length; - boolean shouldParseOldTemplate = !myVersion.isBuiltInFunctionSupported(); - FilePath originalFileName = HgUtil.getOriginalFileName(hgFile.toFilePath(), ChangeListManager.getInstance(myProject)); HgFile originalHgFile = new HgFile(hgFile.getRepo(), originalFileName); HgCommandResult result = execute(hgFile.getRepo(), template, limit, originalHgFile, argsForCmd); - final List revisions = new LinkedList(); - if (result == null) { - return revisions; - } - - List errors = result.getErrorLines(); - if (errors != null && !errors.isEmpty()) { - if (result.getExitValue() != 0) { - throw new HgCommandException(errors.toString()); - } - LOG.warn(errors.toString()); - } - String output = result.getRawOutput(); - String[] changeSets = output.split(HgChangesetUtil.CHANGESET_SEPARATOR); - for (String line : changeSets) { - try { - // we need to get all attributes, include empty trailing strings, so use non-positive limit as second argument - String[] attributes = line.split(HgChangesetUtil.ITEM_SEPARATOR, -1); - // 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... - int numAttributes = attributes.length; - if (!includeFiles && (numAttributes < expectedItemCount)) { //there are may be empty string as last split attributes - LOG.debug("Wrong format. Skipping line " + line); - continue; - } - else if (includeFiles && (numAttributes < FILES_ADDED_INDEX)) { - LOG.debug("Wrong format for long template. Skipping line " + line); - continue; - } - - String revisionString = attributes[REVISION_INDEX]; - String changeset = attributes[CHANGESET_INDEX]; - String parentsString = attributes[PARENTS_INDEX]; - - List parents = parseParentRevisions(parentsString, revisionString); - - Date revisionDate = DATE_FORMAT.parse(attributes[DATE_INDEX]); - String author = attributes[AUTHOR_INDEX]; - String branchName = attributes[BRANCH_INDEX]; - String commitMessage = attributes[MESSAGE_INDEX]; - - final HgRevisionNumber vcsRevisionNumber = new HgRevisionNumber(revisionString, changeset, author, commitMessage, parents); - - Set filesAdded = Collections.emptySet(); - Set filesModified = Collections.emptySet(); - Set filesDeleted = Collections.emptySet(); - Map copies = Collections.emptyMap(); - - if (numAttributes > FILES_ADDED_INDEX) { - filesAdded = parseFileList(attributes[FILES_ADDED_INDEX]); - - if (numAttributes > FILES_MODIFIED_INDEX) { - filesModified = parseFileList(attributes[FILES_MODIFIED_INDEX]); - - if (numAttributes > FILES_DELETED_INDEX) { - filesDeleted = parseFileList(attributes[FILES_DELETED_INDEX]); - - if (numAttributes > FILES_COPIED_INDEX) { - copies = shouldParseOldTemplate - ? parseCopiesFileListAsOldVersion(attributes[FILES_COPIED_INDEX]) - : parseCopiesFileList(attributes[FILES_COPIED_INDEX]); - // Only keep renames, i.e. copies where the source file is also deleted. - Iterator 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(); - } - } - } - } - } - } - - revisions.add( - new HgFileRevision(myProject, hgFile, vcsRevisionNumber, branchName, revisionDate, author, commitMessage, - filesModified, - filesAdded, - filesDeleted, copies)); - } - catch (NumberFormatException e) { - LOG.warn("Error parsing rev in line " + line); - } - catch (ParseException e) { - LOG.warn("Error parsing date in line " + line); - } - } - return revisions; + return HgHistoryUtil.getCommitRecords(myProject, result, + new HgFileRevisionLogParser(myProject, originalHgFile, myVersion)); } @Nullable @@ -242,125 +135,4 @@ public class HgLogCommand { return commandExecutor.executeInCurrentThread(repo, "log", arguments); } - private static Set parseFileList(String fileListString) { - if (StringUtil.isEmpty(fileListString)) { - return Collections.emptySet(); - } - else { - return new HashSet(Arrays.asList(fileListString.split(" "))); - } - } - - @NotNull - public static Map parseCopiesFileList(@Nullable String fileListString) { - if (StringUtil.isEmpty(fileListString)) { - return Collections.emptyMap(); - } - Map copies = new HashMap(); - String[] filesList = fileListString.split(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 - public static Map parseCopiesFileListAsOldVersion(@Nullable String fileListString) { - if (StringUtil.isEmpty(fileListString)) { - return Collections.emptyMap(); - } - else { - Map copies = new HashMap(); - //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; - } - - @NotNull - private static String[] constructTemplateArgument(boolean includeFiles, @NotNull HgVersion currentVersion) { - List templates = new ArrayList(); - templates.add("{rev}"); - templates.add("{node}"); - if (currentVersion.isParentRevisionTemplateSupported()) { - templates.add("{p1rev}:{p1node} {p2rev}:{p2node}"); - } - else { - templates.add("{parents}"); - } - templates.addAll(Arrays.asList("{date|isodatesec}", "{author}", "{branch}", "{desc}")); - if (!includeFiles) { - return ArrayUtil.toStringArray(templates); - } - templates.addAll(Arrays.asList("{file_adds}", "{file_mods}", "{file_dels}")); - templates - .add(currentVersion.isBuiltInFunctionSupported() ? "{join(file_copies,'" + HgChangesetUtil.FILE_SEPARATOR + "')}" : "{file_copies}"); - return ArrayUtil.toStringArray(templates); - } - - @NotNull - private static List parseParentRevisions(@NotNull String parentsString, @NotNull String currentRevisionString) { - List parents = new ArrayList(2); - if (StringUtil.isEmptyOrSpaces(parentsString)) { - // parents shouldn't be empty only if not supported - Long revision = Long.valueOf(currentRevisionString); - HgRevisionNumber parentRevision = HgRevisionNumber.getLocalInstance(String.valueOf(revision - 1)); - parents.add(parentRevision); - return parents; - } - //hg returns parents in the format 'rev:node rev:node ' (note the trailing space) - String[] parentStrings = parentsString.trim().split(" "); - for (String parentString : parentStrings) { - String[] parentParts = parentString.split(":"); - //if revision has only 1 parent and "--debug" argument were added or if appropriate parent template were used, its second parent has revision number -1 - if (Integer.valueOf(parentParts[0]) >= 0) { - parents.add(HgRevisionNumber.getInstance(parentParts[0], parentParts[1])); - } - } - return parents; - } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgBaseLogParser.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgBaseLogParser.java new file mode 100644 index 000000000000..3982e2f2ef6b --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgBaseLogParser.java @@ -0,0 +1,175 @@ +/* + * 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.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; +import com.intellij.util.SmartList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.util.HgChangesetUtil; +import org.zmlx.hg4idea.util.HgUtil; +import org.zmlx.hg4idea.util.HgVersion; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * Parse one log command line and create appropriate type of commit info or revision info. + * see {@link HgHistoryUtil} + * + * @param + */ +public abstract class HgBaseLogParser implements Function { + private static final Logger LOG = Logger.getInstance(HgBaseLogParser.class); + + private static final int REVISION_INDEX = 0; + private static final int CHANGESET_INDEX = 1; + private static final int PARENTS_INDEX = 2; + private static final int DATE_INDEX = 3; + private static final int AUTHOR_INDEX = 4; + + protected static final int MESSAGE_INDEX = 5; + protected static final int BRANCH_INDEX = 6; + + protected static final int FILES_ADDED_INDEX = 7; + protected static final int FILES_MODIFIED_INDEX = 8; + protected static final int FILES_DELETED_INDEX = 9; + protected static final int FILES_COPIED_INDEX = 10; + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); + + + @Nullable + public CommitT convert(@NotNull String line) { + + // we need to get all attributes, include empty trailing strings, so use non-positive limit as second argument + List attributes = StringUtil.split(line, HgChangesetUtil.ITEM_SEPARATOR, true, false); + int numAttributes = attributes.size(); + if (numAttributes <= AUTHOR_INDEX) { + LOG.info("Hg Log Command was cancelled or failed"); + return null; + } + try { + String revisionString = attributes.get(REVISION_INDEX); + String changeset = attributes.get(CHANGESET_INDEX); + String parentsString = attributes.get(PARENTS_INDEX); + + SmartList parents = parseParentRevisions(parentsString, revisionString); + + Date revisionDate = DATE_FORMAT.parse(attributes.get(DATE_INDEX)); + Pair authorAndEmail = HgUtil.parseUserNameAndEmail(attributes.get(AUTHOR_INDEX)); + return convertDetails(revisionString, changeset, parents, revisionDate, authorAndEmail.first, authorAndEmail.second, attributes); + } + catch (ParseException e) { + LOG.warn("Error parsing date in line " + line); + return null; + } + } + + @Override + public CommitT fun(String s) { + return convert(s); + } + + @Nullable + protected abstract CommitT convertDetails(@NotNull String rev, + @NotNull String changeset, + @NotNull SmartList parents, + @NotNull Date revisionDate, + @NotNull String author, + @NotNull String email, + @NotNull List attributes); + + @NotNull + public static List constructDefaultTemplate(HgVersion currentVersion) { + List templates = new ArrayList(); + templates.add("{rev}"); + templates.add("{node}"); + if (currentVersion.isParentRevisionTemplateSupported()) { + templates.add("{p1rev}:{p1node} {p2rev}:{p2node}"); + } + else { + templates.add("{parents}"); + } + templates.addAll(Arrays.asList("{date|isodatesec}", "{author}")); + return templates; + } + + @NotNull + public static String[] constructFullTemplateArgument(boolean includeFiles, @NotNull HgVersion currentVersion) { + List templates = new ArrayList(); + templates.add("{rev}"); + templates.add("{node}"); + if (currentVersion.isParentRevisionTemplateSupported()) { + templates.add("{p1rev}:{p1node} {p2rev}:{p2node}"); + } + else { + templates.add("{parents}"); + } + templates.addAll(Arrays.asList("{date|isodatesec}", "{author}", "{desc}", "{branch}")); + if (!includeFiles) { + return ArrayUtil.toStringArray(templates); + } + templates.addAll(Arrays.asList("{file_adds}", "{file_mods}", "{file_dels}")); + templates + .add(currentVersion.isBuiltInFunctionSupported() ? "{join(file_copies,'" + HgChangesetUtil.FILE_SEPARATOR + "')}" : "{file_copies}"); + return ArrayUtil.toStringArray(templates); + } + + @NotNull + protected static SmartList parseParentRevisions(@NotNull String parentsString, @NotNull String currentRevisionString) { + SmartList parents = new SmartList(); + if (StringUtil.isEmptyOrSpaces(parentsString)) { + // parents shouldn't be empty only if not supported + Long revision = Long.valueOf(currentRevisionString); + HgRevisionNumber parentRevision = HgRevisionNumber.getLocalInstance(String.valueOf(revision - 1)); + parents.add(parentRevision); + return parents; + } + //hg returns parents in the format 'rev:node rev:node ' (note the trailing space) + List parentStrings = StringUtil.split(parentsString.trim(), " "); + for (String parentString : parentStrings) { + List parentParts = StringUtil.split(parentString, ":"); + //if revision has only 1 parent and "--debug" argument were added or if appropriate parent template were used, + // its second parent has revision number -1 + if (Integer.valueOf(parentParts.get(0)) >= 0) { + parents.add(HgRevisionNumber.getInstance(parentParts.get(0), parentParts.get(1))); + } + } + return parents; + } + + @NotNull + protected static String parseAdditionalStringAttribute(final List attributes, int index) { + int numAttributes = attributes.size(); + if (numAttributes > index) { + return attributes.get(index); + } + LOG.warn("Couldn't parse hg log commit info attribute " + index); + return ""; + } +} + diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgFileRevisionLogParser.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgFileRevisionLogParser.java new file mode 100644 index 000000000000..74d19d2f7af3 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgFileRevisionLogParser.java @@ -0,0 +1,176 @@ +/* + * 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 { + 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 parents, + @NotNull Date revisionDate, + @NotNull String author, + @NotNull String email, + @NotNull List 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 filesAdded = Collections.emptySet(); + Set filesModified = Collections.emptySet(); + Set filesDeleted = Collections.emptySet(); + Map 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 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 parseFileList(@Nullable String fileListString) { + if (StringUtil.isEmpty(fileListString)) { + return Collections.emptySet(); + } + else { + return new HashSet(StringUtil.split(fileListString," ")); + } + } + + @NotNull + static Map parseCopiesFileList(@Nullable String fileListString) { + if (StringUtil.isEmpty(fileListString)) { + return Collections.emptyMap(); + } + Map copies = new HashMap(); + List 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 parseCopiesFileListAsOldVersion(@Nullable String fileListString) { + if (StringUtil.isEmpty(fileListString)) { + return Collections.emptyMap(); + } + else { + Map copies = new HashMap(); + //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; + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java new file mode 100644 index 000000000000..dfb576f98ab3 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java @@ -0,0 +1,334 @@ +/* + * 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.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ThrowableComputable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.VcsNotifier; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Consumer; +import com.intellij.util.Function; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.vcs.log.*; +import com.intellij.vcsUtil.VcsUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.*; +import org.zmlx.hg4idea.command.HgLogCommand; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.util.HgChangesetUtil; +import org.zmlx.hg4idea.util.HgUtil; +import org.zmlx.hg4idea.util.HgVersion; + +import java.io.File; +import java.util.*; + +public class HgHistoryUtil { + + private static final Logger LOG = Logger.getInstance(HgHistoryUtil.class); + + private HgHistoryUtil() { + } + + @NotNull + public static List loadMetadata(@NotNull final Project project, + @NotNull final VirtualFile root, int limit, + @NotNull List parameters) throws VcsException { + + final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + if (factory == null) { + return Collections.emptyList(); + } + HgVcs hgvcs = HgVcs.getInstance(project); + assert hgvcs != null; + HgVersion version = hgvcs.getVersion(); + List templateList = HgBaseLogParser.constructDefaultTemplate(version); + templateList.add("{desc}"); + String[] templates = ArrayUtil.toStringArray(templateList); + HgCommandResult result = getLogResult(project, root, version, limit, parameters, HgChangesetUtil.makeTemplate(templates)); + HgBaseLogParser baseParser = new HgBaseLogParser() { + + @Override + protected VcsCommitMetadata convertDetails(@NotNull String rev, + @NotNull String changeset, + @NotNull SmartList parents, + @NotNull Date revisionDate, + @NotNull String author, + @NotNull String email, + @NotNull List attributes) { + String message = parseAdditionalStringAttribute(attributes, MESSAGE_INDEX); + int subjectIndex = message.indexOf('\n'); + String subject = subjectIndex == -1 ? message : message.substring(0, subjectIndex); + List parentsHash = new SmartList(); + for (HgRevisionNumber parent : parents) { + parentsHash.add(factory.createHash(parent.getChangeset())); + } + return factory.createCommitMetadata(factory.createHash(changeset), parentsHash, revisionDate.getTime(), root, + subject, author, email, message, author, email, revisionDate.getTime()); + } + }; + return getCommitRecords(project, result, baseParser); + } + + /** + *

Get & parse hg log detailed output with commits, their parents and their changes.

+ *

+ *

Warning: this is method is efficient by speed, but don't query too much, because the whole log output is retrieved at once, + * and it can occupy too much memory. The estimate is ~600Kb for 1000 commits.

+ */ + @NotNull + public static List history(@NotNull final Project project, @NotNull final VirtualFile root, int limit, + @NotNull List parameters) throws VcsException { + final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + if (factory == null) { + return Collections.emptyList(); + } + HgVcs hgvcs = HgVcs.getInstance(project); + assert hgvcs != null; + final HgVersion version = hgvcs.getVersion(); + String[] templates = HgBaseLogParser.constructFullTemplateArgument(true, version); + HgCommandResult result = getLogResult(project, root, version, limit, parameters, HgChangesetUtil.makeTemplate(templates)); + List hgRevisions = + getCommitRecords(project, result, new HgFileRevisionLogParser(project, getOriginalHgFile(project, root), version)); + List vcsFullCommitDetailsList = new ArrayList(); + for (HgFileRevision revision : hgRevisions) { + + HgRevisionNumber vcsRevisionNumber = revision.getRevisionNumber(); + List parents = vcsRevisionNumber.getParents(); + HgRevisionNumber firstParent = parents.isEmpty() ? null : parents.get(0); // can have no parents if it is a root + List parentsHash = new SmartList(); + for (HgRevisionNumber parent : parents) { + parentsHash.add(factory.createHash(parent.getChangeset())); + } + + final Collection changes = new ArrayList(); + for (String file : revision.getModifiedFiles()) { + changes.add(createChange(project, root, file, firstParent, file, vcsRevisionNumber, FileStatus.MODIFIED)); + } + for (String file : revision.getAddedFiles()) { + changes.add(createChange(project, root, null, null, file, vcsRevisionNumber, FileStatus.ADDED)); + } + for (String file : revision.getDeletedFiles()) { + changes.add(createChange(project, root, file, firstParent, null, vcsRevisionNumber, FileStatus.DELETED)); + } + for (Map.Entry copiedFile : revision.getCopiedFiles().entrySet()) { + changes + .add(createChange(project, root, copiedFile.getKey(), firstParent, copiedFile.getValue(), vcsRevisionNumber, FileStatus.ADDED)); + } + + vcsFullCommitDetailsList.add(factory.createFullDetails(factory.createHash(vcsRevisionNumber.getChangeset()), parentsHash, + revision.getRevisionDate().getTime(), root, + vcsRevisionNumber.getSubject(), + vcsRevisionNumber.getAuthor(), vcsRevisionNumber.getEmail(), + vcsRevisionNumber.getCommitMessage(), vcsRevisionNumber.getAuthor(), + vcsRevisionNumber.getEmail(), revision.getRevisionDate().getTime(), + new ThrowableComputable, Exception>() { + @Override + public Collection compute() throws Exception { + return changes; + } + } + )); + } + return vcsFullCommitDetailsList; + } + + + @Nullable + private static HgCommandResult getLogResult(@NotNull final Project project, + @NotNull final VirtualFile root, @NotNull HgVersion version, int limit, + @NotNull List parameters, @NotNull String template) { + HgFile originalHgFile = getOriginalHgFile(project, root); + HgLogCommand hgLogCommand = new HgLogCommand(project); + List args = new ArrayList(parameters); + hgLogCommand.setLogFile(false); + if (!version.isParentRevisionTemplateSupported()) { + args.add("--debug"); + } + return hgLogCommand.execute(root, template, limit, originalHgFile, args); + } + + public static HgFile getOriginalHgFile(Project project, VirtualFile root) { + HgFile hgFile = new HgFile(root, VcsUtil.getFilePath(root.getPath())); + if (project.isDisposed()) { + return hgFile; + } + FilePath originalFileName = HgUtil.getOriginalFileName(hgFile.toFilePath(), ChangeListManager.getInstance(project)); + return new HgFile(hgFile.getRepo(), originalFileName); + } + + @NotNull + public static List getCommitRecords(@NotNull Project project, + @Nullable HgCommandResult result, + @NotNull Function converter) { + final List revisions = new LinkedList(); + if (result == null) { + return revisions; + } + + List errors = result.getErrorLines(); + if (errors != null && !errors.isEmpty()) { + if (result.getExitValue() != 0) { + VcsNotifier.getInstance(project).notifyError(HgVcsMessages.message("hg4idea.error.log.command.execution"), errors.toString()); + return Collections.emptyList(); + } + LOG.warn(errors.toString()); + } + String output = result.getRawOutput(); + List changeSets = StringUtil.split(output, HgChangesetUtil.CHANGESET_SEPARATOR); + return ContainerUtil.mapNotNull(changeSets, converter); + } + + @NotNull + public static List readMiniDetails(@NotNull Project project, + @NotNull final VirtualFile root, + @NotNull List hashes) + throws VcsException { + final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + if (factory == null) { + return Collections.emptyList(); + } + HgVcs hgvcs = HgVcs.getInstance(project); + assert hgvcs != null; + HgVersion version = hgvcs.getVersion(); + List templateList = HgBaseLogParser.constructDefaultTemplate(version); + templateList.add("{desc}"); + String[] templates = ArrayUtil.toStringArray(templateList); + HgCommandResult result = getLogResult(project, root, version, -1, prepareHashes(hashes), HgChangesetUtil.makeTemplate(templates)); + return getCommitRecords(project, result, new HgBaseLogParser() { + @Override + protected VcsShortCommitDetails convertDetails(@NotNull String rev, + @NotNull String changeset, + @NotNull SmartList parents, + @NotNull Date revisionDate, + @NotNull String author, + @NotNull String email, + @NotNull List attributes) { + String message = parseAdditionalStringAttribute(attributes, MESSAGE_INDEX); + int subjectIndex = message.indexOf('\n'); + String subject = subjectIndex == -1 ? message : message.substring(0, subjectIndex); + List parentsHash = new SmartList(); + for (HgRevisionNumber parent : parents) { + parentsHash.add(factory.createHash(parent.getChangeset())); + } + return factory.createShortDetails(factory.createHash(changeset), parentsHash, + revisionDate.getTime(), root, + subject, author, email); + } + }); + } + + @NotNull + public static List readAllHashes(@NotNull Project project, @NotNull VirtualFile root, + @NotNull final Consumer userRegistry, @NotNull List params) + throws VcsException { + + final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + if (factory == null) { + return Collections.emptyList(); + } + HgVcs hgvcs = HgVcs.getInstance(project); + assert hgvcs != null; + HgVersion version = hgvcs.getVersion(); + String[] templates = ArrayUtil.toStringArray(HgBaseLogParser.constructDefaultTemplate(version)); + HgCommandResult result = getLogResult(project, root, version, -1, params, HgChangesetUtil.makeTemplate(templates)); + return getCommitRecords(project, result, new HgBaseLogParser() { + + @Override + protected TimedVcsCommit convertDetails(@NotNull String rev, + @NotNull String changeset, + @NotNull SmartList parents, + @NotNull Date revisionDate, + @NotNull String author, + @NotNull String email, + @NotNull List attributes) { + List parentsHash = new SmartList(); + for (HgRevisionNumber parent : parents) { + parentsHash.add(factory.createHash(parent.getChangeset())); + } + userRegistry.consume(factory.createUser(author, email)); + return factory.createTimedCommit(factory.createHash(changeset), + parentsHash, revisionDate.getTime()); + } + }); + } + + private static VcsLogObjectsFactory getObjectsFactoryWithDisposeCheck(Project project) { + if (!project.isDisposed()) { + return ServiceManager.getService(project, VcsLogObjectsFactory.class); + } + return null; + } + + @NotNull + public static Change createChange(@NotNull Project project, @NotNull VirtualFile root, + @Nullable String fileBefore, + @Nullable HgRevisionNumber revisionBefore, + @Nullable String fileAfter, + HgRevisionNumber revisionAfter, + FileStatus aStatus) { + + HgContentRevision beforeRevision = + fileBefore == null ? null : new HgContentRevision(project, new HgFile(root, new File(root.getPath(), fileBefore)), revisionBefore); + HgContentRevision afterRevision = + fileAfter == null ? null : new HgContentRevision(project, new HgFile(root, new File(root.getPath(), fileAfter)), revisionAfter); + return new Change(beforeRevision, afterRevision, aStatus); + } + + @NotNull + public static List prepareHashes(@NotNull List hashes) { + List hashArgs = new ArrayList(); + for (String hash : hashes) { + hashArgs.add("-r"); + hashArgs.add(hash); + } + return hashArgs; + } + + @NotNull + public static Collection getDescendingHeadsOfBranches(@NotNull Project project, @NotNull VirtualFile root, @NotNull Hash hash) + throws VcsException { + //hg log -r "descendants(659db54c1b6865c97c4497fa867194bcd759ca76) and head()" --template "{branch}{bookmarks}" + Set branchHeads = new HashSet(); + List params = new ArrayList(); + params.add("-r"); + params.add("descendants(" + hash.asString() + ") and head()"); + HgLogCommand hgLogCommand = new HgLogCommand(project); + hgLogCommand.setLogFile(false); + String template = HgChangesetUtil.makeTemplate("{branch}", "{bookmarks}"); + HgCommandResult logResult = hgLogCommand.execute(root, template, -1, null, params); + if (logResult == null || logResult.getExitValue() != 0) { + throw new VcsException("Couldn't get commit details: log command execution error."); + } + String output = logResult.getRawOutput(); + List changeSets = StringUtil.split(output, HgChangesetUtil.CHANGESET_SEPARATOR); + for (String line : changeSets) { + List attributes = StringUtil.split(line, HgChangesetUtil.ITEM_SEPARATOR); + branchHeads.addAll(attributes); + } + return branchHeads; + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java index a937932e4bcf..84b070ea35c6 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java @@ -33,7 +33,6 @@ import org.zmlx.hg4idea.HgVcs; import org.zmlx.hg4idea.repo.HgConfig; import org.zmlx.hg4idea.repo.HgRepository; import org.zmlx.hg4idea.repo.HgRepositoryManager; -import org.zmlx.hg4idea.util.HgHistoryUtil; import org.zmlx.hg4idea.util.HgUtil; import java.text.SimpleDateFormat; @@ -88,6 +87,9 @@ public class HgLogProvider implements VcsLogProvider { @Override public Collection readAllRefs(@NotNull VirtualFile root) throws VcsException { myRepositoryManager.waitUntilInitialized(); + if (myProject.isDisposed()) { + return Collections.emptyList(); + } HgRepository repository = myRepositoryManager.getRepositoryForRoot(root); if (repository == null) { LOG.error("Repository not found for root " + root); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgHistoryUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgHistoryUtil.java deleted file mode 100644 index 48d0f6ea1fac..000000000000 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgHistoryUtil.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2000-2013 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.util; - -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.ThrowableComputable; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.Consumer; -import com.intellij.util.Function; -import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.vcs.log.*; -import com.intellij.vcsUtil.VcsUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.zmlx.hg4idea.*; -import org.zmlx.hg4idea.action.HgCommandResultNotifier; -import org.zmlx.hg4idea.command.HgLogCommand; -import org.zmlx.hg4idea.execution.HgCommandException; -import org.zmlx.hg4idea.execution.HgCommandResult; -import org.zmlx.hg4idea.provider.HgCommittedChangeList; - -import java.io.File; -import java.util.*; - -public class HgHistoryUtil { - - private HgHistoryUtil() { - } - - @NotNull - public static List loadMetadata(@NotNull final Project project, - @NotNull final VirtualFile root, int limit, - List parameters) throws VcsException { - - final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class); - List result = getCommittedChangeList(project, root, limit, false, parameters); - return ContainerUtil.mapNotNull(result, new Function() { - @Override - public VcsCommitMetadata fun(HgCommittedChangeList record) { - HgRevisionNumber revNumber = (HgRevisionNumber)record.getRevisionNumber(); - return factory.createCommitMetadata(factory.createHash(revNumber.getChangeset()), getParentHashes(factory, revNumber), - record.getCommitDate().getTime(), root, - revNumber.getSubject(), - revNumber.getAuthor(), revNumber.getEmail(), revNumber.getCommitMessage(), - record.getCommitterName(), - "", record.getCommitDate().getTime()); - } - }); - } - - /** - *

Get & parse hg log detailed output with commits, their parents and their changes.

- *

- *

Warning: this is method is efficient by speed, but don't query too much, because the whole log output is retrieved at once, - * and it can occupy too much memory. The estimate is ~600Kb for 1000 commits.

- */ - @NotNull - public static List history(@NotNull final Project project, @NotNull final VirtualFile root, int limit, - List parameters) throws VcsException { - final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class); - List result = getCommittedChangeList(project, root, limit, true, parameters); - return ContainerUtil.mapNotNull(result, new Function() { - @Override - public VcsFullCommitDetails fun(HgCommittedChangeList record) { - return createCommit(root, record, factory); - } - }); - } - - @NotNull - private static List getCommittedChangeList(@NotNull final Project project, - @NotNull final VirtualFile root, int limit, boolean withFiles, - List parameters) { - HgFile hgFile = new HgFile(root, VcsUtil.getFilePath(root.getPath())); - List result = new LinkedList(); - final List localRevisions; - HgLogCommand hgLogCommand = new HgLogCommand(project); - List args = new ArrayList(parameters); - hgLogCommand.setLogFile(false); - HgVcs hgvcs = HgVcs.getInstance(project); - assert hgvcs != null; - try { - if (!hgvcs.getVersion().isParentRevisionTemplateSupported()) { - args.add("--debug"); - } - localRevisions = hgLogCommand.execute(hgFile, limit, withFiles, args); - } - catch (HgCommandException e) { - new HgCommandResultNotifier(project).notifyError(null, HgVcsMessages.message("hg4idea.error.log.command.execution"), e.getMessage()); - return Collections.emptyList(); - } - Collections.reverse(localRevisions); - for (HgFileRevision revision : localRevisions) { - HgRevisionNumber vcsRevisionNumber = revision.getRevisionNumber(); - List parents = vcsRevisionNumber.getParents(); - - HgRevisionNumber firstParent = parents.isEmpty() ? null : parents.get(0); // can have no parents if it is a root - - List changes = new ArrayList(); - for (String file : revision.getModifiedFiles()) { - changes.add(createChange(project, root, file, firstParent, file, vcsRevisionNumber, FileStatus.MODIFIED)); - } - for (String file : revision.getAddedFiles()) { - changes.add(createChange(project, root, null, null, file, vcsRevisionNumber, FileStatus.ADDED)); - } - for (String file : revision.getDeletedFiles()) { - changes.add(createChange(project, root, file, firstParent, null, vcsRevisionNumber, FileStatus.DELETED)); - } - for (Map.Entry copiedFile : revision.getCopiedFiles().entrySet()) { - changes - .add(createChange(project, root, copiedFile.getKey(), firstParent, copiedFile.getValue(), vcsRevisionNumber, FileStatus.ADDED)); - } - - result.add(new HgCommittedChangeList(hgvcs, vcsRevisionNumber, revision.getBranchName(), revision.getCommitMessage(), - revision.getAuthor(), revision.getRevisionDate(), changes)); - } - Collections.reverse(result); - return result; - } - - @NotNull - public static List readMiniDetails(Project project, final VirtualFile root, List hashes) - throws VcsException { - final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class); - return ContainerUtil.map(getCommittedChangeList(project, root, -1, false, prepareHashes(hashes)), - new Function() { - @Override - public VcsShortCommitDetails fun(HgCommittedChangeList record) { - HgRevisionNumber revNumber = (HgRevisionNumber)record.getRevisionNumber(); - List parents = new SmartList(); - for (HgRevisionNumber parent : revNumber.getParents()) { - parents.add(factory.createHash(parent.getChangeset())); - } - return factory.createShortDetails(factory.createHash(revNumber.getChangeset()), parents, - record.getCommitDate().getTime(), root, - revNumber.getSubject(), revNumber.getAuthor(), revNumber.getEmail()); - } - }); - } - - @NotNull - public static List readAllHashes(@NotNull Project project, @NotNull VirtualFile root, - @NotNull final Consumer userRegistry, List params) throws VcsException { - - final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class); - return ContainerUtil.map(getCommittedChangeList(project, root, -1, false, params), new Function() { - @Override - public TimedVcsCommit fun(HgCommittedChangeList record) { - HgRevisionNumber revNumber = (HgRevisionNumber)record.getRevisionNumber(); - List parents = new SmartList(); - for (HgRevisionNumber parent : revNumber.getParents()) { - parents.add(factory.createHash(parent.getChangeset())); - } - userRegistry.consume(factory.createUser(record.getRevision().getAuthor(), revNumber.getEmail())); - return factory.createTimedCommit(factory.createHash(revNumber.getChangeset()), - parents, record.getCommitDate().getTime()); - } - }); - } - - @NotNull - public static Change createChange(Project project, VirtualFile root, - @Nullable String fileBefore, - @Nullable HgRevisionNumber revisionBefore, - @Nullable String fileAfter, - HgRevisionNumber revisionAfter, - FileStatus aStatus) { - - HgContentRevision beforeRevision = - fileBefore == null ? null : new HgContentRevision(project, new HgFile(root, new File(root.getPath(), fileBefore)), revisionBefore); - HgContentRevision afterRevision = - fileAfter == null ? null : new HgContentRevision(project, new HgFile(root, new File(root.getPath(), fileAfter)), revisionAfter); - return new Change(beforeRevision, afterRevision, aStatus); - } - - @NotNull - private static VcsFullCommitDetails createCommit(@NotNull VirtualFile root, - @NotNull final HgCommittedChangeList record, @NotNull VcsLogObjectsFactory factory) { - - HgRevisionNumber revNumber = (HgRevisionNumber)record.getRevisionNumber(); - final Collection changes = record.getChanges(); - return factory.createFullDetails(factory.createHash(revNumber.getChangeset()), getParentHashes(factory, revNumber), - record.getCommitDate().getTime(), root, - revNumber.getSubject(), - revNumber.getAuthor(), revNumber.getEmail(), revNumber.getCommitMessage(), record.getCommitterName(), - "", record.getCommitDate().getTime(), new ThrowableComputable, Exception>() { - @Override - public Collection compute() throws Exception { - return changes; - } - } - ); - } - - @NotNull - private static List getParentHashes(@NotNull final VcsLogObjectsFactory factory, @NotNull HgRevisionNumber revNumber) { - return ContainerUtil.map(revNumber.getParents(), new Function() { - @Override - public Hash fun(HgRevisionNumber parent) { - return factory.createHash(parent.getChangeset()); - } - }); - } - - @Nullable - public static List prepareHashes(@NotNull List hashes) { - List hashArgs = new ArrayList(); - for (String hash : hashes) { - hashArgs.add("-r"); - hashArgs.add(hash); - } - return hashArgs; - } - - @NotNull - public static Collection getDescendingHeadsOfBranches(@NotNull Project project, @NotNull VirtualFile root, @NotNull Hash hash) - throws VcsException { - //hg log -r "descendants(659db54c1b6865c97c4497fa867194bcd759ca76) and head()" --template "{branch}{bookmarks}" - Set branchHeads = new HashSet(); - List params = new ArrayList(); - params.add("-r"); - params.add("descendants(" + hash.asString() + ") and head()"); - HgLogCommand hgLogCommand = new HgLogCommand(project); - hgLogCommand.setLogFile(false); - String template = HgChangesetUtil.makeTemplate("{branch}", "{bookmarks}"); - HgCommandResult logResult = hgLogCommand.execute(root, template, -1, null, params); - if (logResult == null || logResult.getExitValue() != 0) { - throw new VcsException("Couldn't get commit details: log command execution error."); - } - String output = logResult.getRawOutput(); - List changeSets = StringUtil.split(output, HgChangesetUtil.CHANGESET_SEPARATOR); - for (String line : changeSets) { - List attributes = StringUtil.split(line, HgChangesetUtil.ITEM_SEPARATOR); - branchHeads.addAll(attributes); - } - return branchHeads; - } -} diff --git a/plugins/hg4idea/testSrc/hg4idea/test/history/HgLogHistoryTest.java b/plugins/hg4idea/testSrc/hg4idea/test/history/HgLogHistoryTest.java index 691e7878c1f9..b890acfac44c 100644 --- a/plugins/hg4idea/testSrc/hg4idea/test/history/HgLogHistoryTest.java +++ b/plugins/hg4idea/testSrc/hg4idea/test/history/HgLogHistoryTest.java @@ -22,19 +22,14 @@ import com.intellij.vcs.log.Hash; import com.intellij.vcs.log.impl.HashImpl; import hg4idea.test.HgPlatformTest; import org.jetbrains.annotations.NotNull; -import org.zmlx.hg4idea.util.HgHistoryUtil; +import org.zmlx.hg4idea.log.HgHistoryUtil; import java.util.Arrays; import java.util.Collection; -import static com.intellij.openapi.vcs.Executor.cd; -import static com.intellij.openapi.vcs.Executor.echo; -import static com.intellij.openapi.vcs.Executor.touch; +import static com.intellij.openapi.vcs.Executor.*; import static hg4idea.test.HgExecutor.hg; -/** - * @author Nadya Zabrodina - */ public class HgLogHistoryTest extends HgPlatformTest { @Override diff --git a/plugins/hg4idea/testSrc/hg4idea/test/history/HgLogParseTest.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/log/HgLogParseTest.java similarity index 86% rename from plugins/hg4idea/testSrc/hg4idea/test/history/HgLogParseTest.java rename to plugins/hg4idea/testSrc/org/zmlx/hg4idea/log/HgLogParseTest.java index f209d6c79021..343398f88663 100644 --- a/plugins/hg4idea/testSrc/hg4idea/test/history/HgLogParseTest.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/log/HgLogParseTest.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package hg4idea.test.history; +package org.zmlx.hg4idea.log; import com.intellij.openapi.util.Pair; import org.junit.Test; -import org.zmlx.hg4idea.command.HgLogCommand; import org.zmlx.hg4idea.util.HgUtil; import java.util.Map; @@ -25,14 +24,12 @@ import java.util.Map; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; -/** - * @author Nadya Zabrodina - */ public class HgLogParseTest { @Test public void testParseFileCopiesWithWhitespaces() { - Map filesMap = HgLogCommand.parseCopiesFileList("/a/b c/d.txt (a/b a/d.txt)\u0001/a/b c/(d).txt (/a/b c/(f).txt)"); + Map filesMap = HgFileRevisionLogParser.parseCopiesFileList( + "/a/b c/d.txt (a/b a/d.txt)\u0001/a/b c/(d).txt (/a/b c/(f).txt)"); assertTrue(filesMap.containsKey("a/b a/d.txt")); assertTrue(filesMap.containsKey("/a/b c/(f).txt")); assertTrue(filesMap.containsValue("/a/b c/d.txt")); @@ -41,7 +38,7 @@ public class HgLogParseTest { @Test public void testParseFileCopiesOldVersion() { - Map filesMap = HgLogCommand.parseCopiesFileListAsOldVersion( + Map filesMap = HgFileRevisionLogParser.parseCopiesFileListAsOldVersion( "/a/b c/d.txt (a/b a/d.txt)/a/b c/(d).txt (/a/b c/(f).txt)"); assertTrue(filesMap.containsKey("a/b a/d.txt")); assertTrue(filesMap.containsKey("/a/b c/(f).txt"));