view mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java @ 460:8eb05f24d883

Merge Faradi-7.0.x
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 06 Jul 2012 21:26:25 +0400
parents a400b00b8b2d 1725588ec4f6
children efba721f9a1d
line wrap: on
line source
/*
 * Copyright 2000-2011 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 jetbrains.buildServer.buildTriggers.vcs.mercurial.command;

import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.diagnostic.Logger;
import jetbrains.buildServer.vcs.VcsException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LogCommand extends VcsRootCommand {

  private final static Logger LOG = Logger.getInstance(LogCommand.class.getName());
  private final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
  private final static SAXParserFactory ourSAXFactory = SAXParserFactory.newInstance();

  private String myFromId;
  private String myToId;
  private Integer myLimit = null;
  private String myBranchName;
  private boolean myCalculateParents = true;
  private String myRevsets;
  private File myTemplate;

  public LogCommand(@NotNull String hgPath, @NotNull File workingDir, @NotNull AuthSettings authSettings) {
    super(hgPath, workingDir, authSettings);
  }

  public LogCommand withTemplate(@NotNull File template) {
    myTemplate = template;
    return this;
  }

  public LogCommand inBranch(@NotNull String branchName) {
    myBranchName = branchName;
    return this;
  }

  public LogCommand fromRevision(@Nullable String fromRevision) {
    myFromId = fromRevision == null ? fromRevision : new ChangeSet(fromRevision).getId();
    return this;
  }

  public LogCommand toRevision(@Nullable String toRevision) {
    myToId = toRevision == null ? toRevision: new ChangeSet(toRevision).getId();
    return this;
  }

  public LogCommand toNamedRevision(@Nullable String namedRevision) {
    myToId = namedRevision;
    return this;
  }

  public LogCommand setLimit(final int limit) {
    myLimit = limit;
    return this;
  }

  public LogCommand showCommitsFromAllBranches() {
    myBranchName = null;
    return this;
  }

  public LogCommand dontCalculateParents() {
    myCalculateParents = false;
    return this;
  }

  public LogCommand withRevsets(String revsets) {
    myRevsets = revsets;
    return this;
  }

  public List<ChangeSet> call() throws VcsException {
    GeneralCommandLine cli = createCommandLine();
    cli.setCharset(Charset.forName("UTF-8"));
    cli.addParameters("--encoding", "UTF-8");
    cli.addParameter("log");
    cli.addParameter("-v");
    if (myTemplate != null)
      cli.addParameter("--style=" + myTemplate.getAbsolutePath());
    if (myBranchName != null) {
      cli.addParameter("-b");
      cli.addParameter(myBranchName);
    }
    cli.addParameter("-r");
    if (myRevsets != null) {
      cli.addParameter(myRevsets);
    } else {
      String from = myFromId != null ? myFromId : "0";
      String to = myToId != null ? myToId : "tip";
      cli.addParameter(from + ":" + to);
    }
    if (myLimit != null) {
      cli.addParameter("--limit");
      cli.addParameter(myLimit.toString());
    }

    CommandResult res = runCommand(cli);
    String output = res.getStdout();
    try {
      List<ChangeSet> changes = parseChangeSetsXml(output);
      if (myCalculateParents)
        assignTrivialParents(changes);
      return changes;
    } catch (Exception e) {
      LOG.error("Error while parsing log output:\n" + output, e);
      throw new VcsException("Error while parsing log output, see teamcity-vcs.log for details", e);
    }
  }

  private List<ChangeSet> parseChangeSetsXml(@NotNull final String xml) throws SAXException, ParserConfigurationException, IOException {
    if ("".equals(xml))
      return Collections.emptyList();

    String validXml = makeValidXml(xml);
    MercurialXmlLogParser parser = new MercurialXmlLogParser();
    SAXParser saxParser = ourSAXFactory.newSAXParser();
    saxParser.parse(new ByteArrayInputStream(validXml.getBytes("UTF-8")), parser);
    return parser.getChangeSets();
  }

  private String makeValidXml(@NotNull final String xml) {
    String trimmed = xml.trim();
    if (trimmed.endsWith("</log>"))
      return xml;
    else
      return xml + "</log>";
  }

  private void assignTrivialParents(final @NotNull List<ChangeSet> csets) throws VcsException {
    Map<Integer, ChangeSet> revNumberMap = makeMapByRevNumber(csets);
    for (ChangeSet cset : csets) {
      if (cset.getParents().isEmpty()) {
        int parentRevNumber = cset.getRevNumber() - 1;
        ChangeSet parent = revNumberMap.get(parentRevNumber);
        if (parent != null) {
          cset.addParent(parent);
        } else {
          String parentId = getIdOf(parentRevNumber);
          cset.addParent(new ChangeSetRevision(parentRevNumber, parentId));
        }
      }
    }
  }

  private Map<Integer, ChangeSet> makeMapByRevNumber(@NotNull List<ChangeSet> csets) throws VcsException {
    Map<Integer, ChangeSet> csetMap = new HashMap<Integer, ChangeSet>();
    for (ChangeSet cset : csets) {
      csetMap.put(cset.getRevNumber(), cset);
    }
    return csetMap;
  }

  private String getIdOf(int revNumber) throws VcsException {
    if (revNumber < 0)
      return ZERO_PARENT_ID;
    return new IdentifyCommand(getHgPath(), getWorkDirectory(), myAuthSettings)
            .revisionNumber(revNumber)
            .inLocalRepository()
            .call();
  }
}