view mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java @ 761:196f62e515db

extract superclass
author eugene.petrenko@jetbrains.com
date Tue, 25 Feb 2014 11:53:50 +0100
parents 388b7f309865
children 5cf7d1b3c476
line wrap: on
line source
/*
 * 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 jetbrains.buildServer.buildTriggers.vcs.mercurial.command;

import com.intellij.openapi.diagnostic.Logger;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialTemplate;
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.*;

public class LogCommand extends VcsRootCommand {

  private final static Logger LOG = Logger.getInstance(LogCommand.class.getName());
  public final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
  public final static String ZERO_PARENT_SHORT_ID = "000000000000";
  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 MercurialTemplate myTemplate;
  private List<String> myFiles = new ArrayList<String>();

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

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

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

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

  public LogCommand toRevision(@Nullable String toRevision) {
    myToId = toRevision == null ? null : 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 LogCommand forFile(@NotNull final String file) {
    myFiles.add(file);
    return this;
  }

  @NotNull
  public List<ChangeSet> call() throws VcsException {
    return myTemplate.withTemplate(new MercurialTemplate.WithTemplate<List<ChangeSet>>() {
      @NotNull
      public List<ChangeSet> action(@NotNull File template) throws VcsException {
        final MercurialCommandLine 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=" + template.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());
        }

        cli.addParameters(myFiles);

        final CommandResult res = runCommand(cli);
        final 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(myCommandSettings, getHgPath(), getWorkDirectory(), myAuthSettings)
            .revisionNumber(revNumber)
            .inLocalRepository()
            .call();
  }
}