blob: 2d3a7bce191b519fa0c559c2cfbfbdd8761f2cfb [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zmlx.hg4idea.log;
import com.intellij.openapi.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<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.
* For null destination return log command result</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 {
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));
return createFullCommitsFromResult(project, root, result, version, false);
}
public static List<? extends VcsFullCommitDetails> createFullCommitsFromResult(@NotNull Project project,
@NotNull VirtualFile root,
@Nullable HgCommandResult result,
@NotNull HgVersion version, boolean silent) {
final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project);
if (factory == null) {
return Collections.emptyList();
}
List<HgFileRevision> hgRevisions =
getCommitRecords(project, result, new HgFileRevisionLogParser(project, getOriginalHgFile(project, root), version), silent);
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) {
return getCommitRecords(project, result, converter, false);
}
@NotNull
public static <CommitInfo> List<CommitInfo> getCommitRecords(@NotNull Project project,
@Nullable HgCommandResult result,
@NotNull Function<String, CommitInfo> converter, boolean silent) {
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) {
if (silent) {
LOG.warn(errors.toString());
}
else {
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;
}
public static String prepareParameter(String paramName, String value) {
return "--" + paramName + "=" + value; // no value escaping needed, because the parameter itself will be quoted by GeneralCommandLine
}
}