view mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java @ 299:e9e7d9fcf57d

Use customized xml output from the 'hg log' command instead of running 'hg status' for every commit
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Thu, 08 Sep 2011 12:56:56 +0400
parents 8c1fd2e565ae
children e9cdb499350d
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.util.JDOMUtil;
import jetbrains.buildServer.ExecResult;
import jetbrains.buildServer.vcs.VcsException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class LogCommand extends VcsRootCommand {

  private final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
  private static final String DATE_FORMAT = "EEE MMM d HH:mm:ss yyyy Z";

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

  public LogCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull final File template) {
    super(settings, workingDir);
    myTemplate = template;
    myBranchName = settings.getBranchName();
  }

  public void setFromRevId(String id) {
    myFromId = id;
  }

  public void setToRevId(String id) {
    myToId = id;
  }

  public void setLimit(final int limit) {
    myLimit = limit;
  }

  public void showCommitsFromAllBranches() {
    myBranchName = null;
  }

  public void setCalculateParents(boolean doCalculate) {
    myCalculateParents = doCalculate;
  }

  public void setRevsets(String revsets) {
    myRevsets = revsets;
  }

  public List<ChangeSet> execute() throws VcsException {
    GeneralCommandLine cli = createCommandLine();
    cli.addParameter("log");
    cli.addParameter("-v");
    cli.addParameter("--style=" + myTemplate.getAbsolutePath());
    if (myBranchName != null) {
      cli.addParameter("-b");
      cli.addParameter(getSettings().getBranchName());
    }
    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());
    }

    ExecResult res = runCommand(cli);
    try {
      List<ChangeSet> changes = parseChangeSetsXml(res.getStdout());
      if (myCalculateParents)
        assignTrivialParents(changes);
      return changes;
    } catch (Exception e) {
      throw new VcsException("Error while parsing log output:\n" + res.getStdout(), e);
    }
  }


  private List<ChangeSet> parseChangeSetsXml(@NotNull final String xml) throws JDOMException, IOException, ParseException {
    Document doc = JDOMUtil.loadDocument(xml);
    Element log = doc.getRootElement();
    return parseLog(log);
  }


  private List<ChangeSet> parseLog(@NotNull final Element logElement) throws ParseException {
    List<ChangeSet> result = new ArrayList<ChangeSet>();
    for (Object o : logElement.getChildren("logentry")) {
      Element entry = (Element) o;
      result.add(parseLogEntry(entry));
    }
    return result;
  }


  private ChangeSet parseLogEntry(@NotNull final Element logEntry) throws ParseException {
    ChangeSet cset = new ChangeSet(getRevision(logEntry), getId(logEntry));
    addParents(cset, logEntry);
    cset.setUser(getAuthor(logEntry));
    cset.setDescription(getDescription(logEntry));
    cset.setTimestamp(getDate(logEntry));
    cset.setModifiedFiles(getModifiedFiles(logEntry));
    return cset;
  }


  private int getRevision(@NotNull final Element logEntry) {
    return Integer.parseInt(logEntry.getAttribute("revision").getValue());
  }


  private String getId(@NotNull final Element logEntry) {
    return logEntry.getAttribute("shortnode").getValue();
  }


  private void addParents(@NotNull final ChangeSet cset, @NotNull final Element logEntry) {
    List parents = logEntry.getChildren("parent");
    for (Object p : parents) {
      Element parent = (Element) p;
      ChangeSetRevision parentCset = getParent(parent);
      cset.addParent(parentCset);
    }
  }


  private ChangeSetRevision getParent(@NotNull final Element parent) {
    return new ChangeSetRevision(getRevision(parent), getId(parent));
  }


  private String getAuthor(@NotNull final Element logEntry) {
    Element author = logEntry.getChild("author");
    return author.getAttribute("original").getValue();
  }


  private String getDescription(@NotNull final Element logEntry) {
    Element msg = logEntry.getChild("msg");
    return msg.getText();
  }


  private Date getDate(@NotNull final Element logEntry) throws ParseException {
    Element date = logEntry.getChild("date");
    return new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH).parse(date.getText());
  }


  private List<ModifiedFile> getModifiedFiles(@NotNull final Element logEntry) {
    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
    Element paths = logEntry.getChild("paths");
    for (Object o : paths.getChildren("path")) {
      Element path = (Element) o;
      result.add(getModifiedFile(path));
    }
    return result;
  }


  private ModifiedFile getModifiedFile(@NotNull final Element path) {
    String filePath = path.getText();
    ModifiedFile.Status status = getStatus(path);
    return new ModifiedFile(status, filePath);
  }


  private ModifiedFile.Status getStatus(@NotNull final Element path) {
    String action = path.getAttribute("action").getValue();
    if (action.equals("A")) {
      return ModifiedFile.Status.ADDED;
    } else if (action.equals("M")) {
      return ModifiedFile.Status.MODIFIED;
    } else if (action.equals("R")) {
      return ModifiedFile.Status.REMOVED;
    } else {
      return ModifiedFile.Status.UNKNOWN;
    }
  }

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

  private String getIdOf(int revNumber) throws VcsException {
    if (revNumber < 0)
      return ZERO_PARENT_ID;
    IdentifyCommand identify = new IdentifyCommand(getSettings(), getWorkDirectory());
    identify.setInLocalRepository(true);
    identify.setRevisionNumber(revNumber);
    String output = identify.execute().trim();
    if (output.contains(" ")) {
      return output.substring(0, output.indexOf(" "));
    } else {
      return output;
    }
  }
}