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
This commit is contained in:
Nadya Zabrodina
2014-04-01 20:48:04 +04:00
parent 7333a7849e
commit d783d869a3
8 changed files with 703 additions and 509 deletions
@@ -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<HgFileRevision> revisions = new LinkedList<HgFileRevision>();
if (result == null) {
return revisions;
}
List<String> 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<HgRevisionNumber> 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<String> filesAdded = Collections.emptySet();
Set<String> filesModified = Collections.emptySet();
Set<String> filesDeleted = Collections.emptySet();
Map<String, String> 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<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();
}
}
}
}
}
}
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<String> parseFileList(String fileListString) {
if (StringUtil.isEmpty(fileListString)) {
return Collections.emptySet();
}
else {
return new HashSet<String>(Arrays.asList(fileListString.split(" ")));
}
}
@NotNull
public static Map<String, String> parseCopiesFileList(@Nullable String fileListString) {
if (StringUtil.isEmpty(fileListString)) {
return Collections.emptyMap();
}
Map<String, String> copies = new HashMap<String, String>();
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<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;
}
@NotNull
private static String[] constructTemplateArgument(boolean includeFiles, @NotNull HgVersion currentVersion) {
List<String> templates = new ArrayList<String>();
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<HgRevisionNumber> parseParentRevisions(@NotNull String parentsString, @NotNull String currentRevisionString) {
List<HgRevisionNumber> parents = new ArrayList<HgRevisionNumber>(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;
}
}
@@ -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 <CommitT>
*/
public abstract class HgBaseLogParser<CommitT> implements Function<String, CommitT> {
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<String> 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<HgRevisionNumber> parents = parseParentRevisions(parentsString, revisionString);
Date revisionDate = DATE_FORMAT.parse(attributes.get(DATE_INDEX));
Pair<String, String> 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<HgRevisionNumber> parents,
@NotNull Date revisionDate,
@NotNull String author,
@NotNull String email,
@NotNull List<String> attributes);
@NotNull
public static List<String> constructDefaultTemplate(HgVersion currentVersion) {
List<String> templates = new ArrayList<String>();
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<String> templates = new ArrayList<String>();
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<HgRevisionNumber> parseParentRevisions(@NotNull String parentsString, @NotNull String currentRevisionString) {
SmartList<HgRevisionNumber> parents = new SmartList<HgRevisionNumber>();
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<String> parentStrings = StringUtil.split(parentsString.trim(), " ");
for (String parentString : parentStrings) {
List<String> 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<String> 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 "";
}
}
@@ -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<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;
}
}
@@ -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<? extends VcsCommitMetadata> loadMetadata(@NotNull final Project project,
@NotNull final VirtualFile root, int limit,
@NotNull List<String> 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<String> templateList = HgBaseLogParser.constructDefaultTemplate(version);
templateList.add("{desc}");
String[] templates = ArrayUtil.toStringArray(templateList);
HgCommandResult result = getLogResult(project, root, version, limit, parameters, HgChangesetUtil.makeTemplate(templates));
HgBaseLogParser<VcsCommitMetadata> baseParser = new HgBaseLogParser<VcsCommitMetadata>() {
@Override
protected VcsCommitMetadata convertDetails(@NotNull String rev,
@NotNull String changeset,
@NotNull SmartList<HgRevisionNumber> parents,
@NotNull Date revisionDate,
@NotNull String author,
@NotNull String email,
@NotNull List<String> attributes) {
String message = parseAdditionalStringAttribute(attributes, MESSAGE_INDEX);
int subjectIndex = message.indexOf('\n');
String subject = subjectIndex == -1 ? message : message.substring(0, subjectIndex);
List<Hash> parentsHash = new SmartList<Hash>();
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);
}
/**
* <p>Get & parse hg log detailed output with commits, their parents and their changes.</p>
* <p/>
* <p>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.</p>
*/
@NotNull
public static List<? extends VcsFullCommitDetails> history(@NotNull final Project project, @NotNull final VirtualFile root, int limit,
@NotNull List<String> 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<HgFileRevision> hgRevisions =
getCommitRecords(project, result, new HgFileRevisionLogParser(project, getOriginalHgFile(project, root), version));
List<VcsFullCommitDetails> vcsFullCommitDetailsList = new ArrayList<VcsFullCommitDetails>();
for (HgFileRevision revision : hgRevisions) {
HgRevisionNumber vcsRevisionNumber = revision.getRevisionNumber();
List<HgRevisionNumber> parents = vcsRevisionNumber.getParents();
HgRevisionNumber firstParent = parents.isEmpty() ? null : parents.get(0); // can have no parents if it is a root
List<Hash> parentsHash = new SmartList<Hash>();
for (HgRevisionNumber parent : parents) {
parentsHash.add(factory.createHash(parent.getChangeset()));
}
final Collection<Change> changes = new ArrayList<Change>();
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<String, String> 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<Collection<Change>, Exception>() {
@Override
public Collection<Change> 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<String> parameters, @NotNull String template) {
HgFile originalHgFile = getOriginalHgFile(project, root);
HgLogCommand hgLogCommand = new HgLogCommand(project);
List<String> args = new ArrayList<String>(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 <CommitInfo> List<CommitInfo> getCommitRecords(@NotNull Project project,
@Nullable HgCommandResult result,
@NotNull Function<String, CommitInfo> converter) {
final List<CommitInfo> revisions = new LinkedList<CommitInfo>();
if (result == null) {
return revisions;
}
List<String> 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<String> changeSets = StringUtil.split(output, HgChangesetUtil.CHANGESET_SEPARATOR);
return ContainerUtil.mapNotNull(changeSets, converter);
}
@NotNull
public static List<? extends VcsShortCommitDetails> readMiniDetails(@NotNull Project project,
@NotNull final VirtualFile root,
@NotNull List<String> 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<String> 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<VcsShortCommitDetails>() {
@Override
protected VcsShortCommitDetails convertDetails(@NotNull String rev,
@NotNull String changeset,
@NotNull SmartList<HgRevisionNumber> parents,
@NotNull Date revisionDate,
@NotNull String author,
@NotNull String email,
@NotNull List<String> attributes) {
String message = parseAdditionalStringAttribute(attributes, MESSAGE_INDEX);
int subjectIndex = message.indexOf('\n');
String subject = subjectIndex == -1 ? message : message.substring(0, subjectIndex);
List<Hash> parentsHash = new SmartList<Hash>();
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<TimedVcsCommit> readAllHashes(@NotNull Project project, @NotNull VirtualFile root,
@NotNull final Consumer<VcsUser> userRegistry, @NotNull List<String> 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<TimedVcsCommit>() {
@Override
protected TimedVcsCommit convertDetails(@NotNull String rev,
@NotNull String changeset,
@NotNull SmartList<HgRevisionNumber> parents,
@NotNull Date revisionDate,
@NotNull String author,
@NotNull String email,
@NotNull List<String> attributes) {
List<Hash> parentsHash = new SmartList<Hash>();
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<String> prepareHashes(@NotNull List<String> hashes) {
List<String> hashArgs = new ArrayList<String>();
for (String hash : hashes) {
hashArgs.add("-r");
hashArgs.add(hash);
}
return hashArgs;
}
@NotNull
public static Collection<String> getDescendingHeadsOfBranches(@NotNull Project project, @NotNull VirtualFile root, @NotNull Hash hash)
throws VcsException {
//hg log -r "descendants(659db54c1b6865c97c4497fa867194bcd759ca76) and head()" --template "{branch}{bookmarks}"
Set<String> branchHeads = new HashSet<String>();
List<String> params = new ArrayList<String>();
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<String> changeSets = StringUtil.split(output, HgChangesetUtil.CHANGESET_SEPARATOR);
for (String line : changeSets) {
List<String> attributes = StringUtil.split(line, HgChangesetUtil.ITEM_SEPARATOR);
branchHeads.addAll(attributes);
}
return branchHeads;
}
}
@@ -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<VcsRef> 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);
@@ -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<? extends VcsCommitMetadata> loadMetadata(@NotNull final Project project,
@NotNull final VirtualFile root, int limit,
List<String> parameters) throws VcsException {
final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class);
List<HgCommittedChangeList> result = getCommittedChangeList(project, root, limit, false, parameters);
return ContainerUtil.mapNotNull(result, new Function<HgCommittedChangeList, VcsCommitMetadata>() {
@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());
}
});
}
/**
* <p>Get & parse hg log detailed output with commits, their parents and their changes.</p>
* <p/>
* <p>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.</p>
*/
@NotNull
public static List<? extends VcsFullCommitDetails> history(@NotNull final Project project, @NotNull final VirtualFile root, int limit,
List<String> parameters) throws VcsException {
final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class);
List<HgCommittedChangeList> result = getCommittedChangeList(project, root, limit, true, parameters);
return ContainerUtil.mapNotNull(result, new Function<HgCommittedChangeList, VcsFullCommitDetails>() {
@Override
public VcsFullCommitDetails fun(HgCommittedChangeList record) {
return createCommit(root, record, factory);
}
});
}
@NotNull
private static List<HgCommittedChangeList> getCommittedChangeList(@NotNull final Project project,
@NotNull final VirtualFile root, int limit, boolean withFiles,
List<String> parameters) {
HgFile hgFile = new HgFile(root, VcsUtil.getFilePath(root.getPath()));
List<HgCommittedChangeList> result = new LinkedList<HgCommittedChangeList>();
final List<HgFileRevision> localRevisions;
HgLogCommand hgLogCommand = new HgLogCommand(project);
List<String> args = new ArrayList<String>(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<HgRevisionNumber> parents = vcsRevisionNumber.getParents();
HgRevisionNumber firstParent = parents.isEmpty() ? null : parents.get(0); // can have no parents if it is a root
List<Change> changes = new ArrayList<Change>();
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<String, String> 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<? extends VcsShortCommitDetails> readMiniDetails(Project project, final VirtualFile root, List<String> hashes)
throws VcsException {
final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class);
return ContainerUtil.map(getCommittedChangeList(project, root, -1, false, prepareHashes(hashes)),
new Function<HgCommittedChangeList, VcsShortCommitDetails>() {
@Override
public VcsShortCommitDetails fun(HgCommittedChangeList record) {
HgRevisionNumber revNumber = (HgRevisionNumber)record.getRevisionNumber();
List<Hash> parents = new SmartList<Hash>();
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<TimedVcsCommit> readAllHashes(@NotNull Project project, @NotNull VirtualFile root,
@NotNull final Consumer<VcsUser> userRegistry, List<String> params) throws VcsException {
final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class);
return ContainerUtil.map(getCommittedChangeList(project, root, -1, false, params), new Function<HgCommittedChangeList, TimedVcsCommit>() {
@Override
public TimedVcsCommit fun(HgCommittedChangeList record) {
HgRevisionNumber revNumber = (HgRevisionNumber)record.getRevisionNumber();
List<Hash> parents = new SmartList<Hash>();
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<Change> 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<Collection<Change>, Exception>() {
@Override
public Collection<Change> compute() throws Exception {
return changes;
}
}
);
}
@NotNull
private static List<Hash> getParentHashes(@NotNull final VcsLogObjectsFactory factory, @NotNull HgRevisionNumber revNumber) {
return ContainerUtil.map(revNumber.getParents(), new Function<HgRevisionNumber, Hash>() {
@Override
public Hash fun(HgRevisionNumber parent) {
return factory.createHash(parent.getChangeset());
}
});
}
@Nullable
public static List<String> prepareHashes(@NotNull List<String> hashes) {
List<String> hashArgs = new ArrayList<String>();
for (String hash : hashes) {
hashArgs.add("-r");
hashArgs.add(hash);
}
return hashArgs;
}
@NotNull
public static Collection<String> getDescendingHeadsOfBranches(@NotNull Project project, @NotNull VirtualFile root, @NotNull Hash hash)
throws VcsException {
//hg log -r "descendants(659db54c1b6865c97c4497fa867194bcd759ca76) and head()" --template "{branch}{bookmarks}"
Set<String> branchHeads = new HashSet<String>();
List<String> params = new ArrayList<String>();
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<String> changeSets = StringUtil.split(output, HgChangesetUtil.CHANGESET_SEPARATOR);
for (String line : changeSets) {
List<String> attributes = StringUtil.split(line, HgChangesetUtil.ITEM_SEPARATOR);
branchHeads.addAll(attributes);
}
return branchHeads;
}
}
@@ -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
@@ -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<String, String> 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<String, String> 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<String, String> filesMap = HgLogCommand.parseCopiesFileListAsOldVersion(
Map<String, String> 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"));