view mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java @ 977:38adef4f1b8f Indore-2017.2.x

Update copyright
author pavel.sher
date Mon, 22 Jan 2018 11:40:45 +0100
parents ed4ae4bfd691
children 1168c4c64d49
line wrap: on
line source
/*
 * Copyright 2000-2018 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;

import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
import jetbrains.buildServer.log.Loggers;
import jetbrains.buildServer.util.FileUtil;
import jetbrains.buildServer.vcs.VcsException;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil.deleteDir;
import static jetbrains.buildServer.util.FileUtil.isEmptyDir;

/**
* @author dmitry.neverov
*/
public class HgRepo {

  protected final CommandSettingsFactory myCommandSettingsFactory;
  protected final File myWorkingDir;
  protected final String myHgPath;
  protected final AuthSettings myAuthSettings;
  protected final Map<String, Map<String, SubRepo>> mySubreposCache = new HashMap<String, Map<String, SubRepo>>();

  public HgRepo(@NotNull CommandSettingsFactory commandSettingsFactory,
                @NotNull File workingDir,
                @NotNull String hgPath,
                @NotNull AuthSettings authSettings) {
    myCommandSettingsFactory = commandSettingsFactory;
    myWorkingDir = workingDir;
    myHgPath = hgPath;
    myAuthSettings = authSettings;
  }

  public PullCommand pull() {
    return new PullCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public PushCommand push() {
    return new PushCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public CloneCommand doClone() {
    return new CloneCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public IdentifyCommand id() {
    return new IdentifyCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public Init init() {
    return new Init(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public LogCommand log() {
    return new LogCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  @NotNull
  public CommitsAndMountPointsCommand logSubstates() throws VcsException {
    return new CommitsAndMountPointsCommand(this, myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public UpdateCommand update() {
    return new UpdateCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public BranchCommand branch() {
    return new BranchCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public BranchesCommand branches() {
    return new BranchesCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public BookmarksCommand bookmarks() {
    return new BookmarksCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public UpdateBookmarkCommand updateBookmark() {
    return new UpdateBookmarkCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public TagsCommand tags() {
    return new TagsCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public Map<String, String> getBranchRevisions(boolean includeBookmarks, boolean includeTags) throws VcsException {
    Map<String, String> revisions = new HashMap<String, String>();
    if (includeTags)
      revisions.putAll(tags().call());
    if (includeBookmarks && version().call().isEqualsOrGreaterThan(BookmarksCommand.REQUIRED_HG_VERSION))
      revisions.putAll(bookmarks().call());
    revisions.putAll(branches().call());
    return revisions;
  }

  public StatusCommand status() {
    return new StatusCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public TagCommand tag() {
    return new TagCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public CatCommand cat() {
    return new CatCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public ArchiveCommand archive() {
    return new ArchiveCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
  }

  public VersionCommand version() {
    return new VersionCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public ParentsCommand parents() {
    return new ParentsCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public MergeCommand merge() {
    return new MergeCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public ResolveCommand resolve() {
    return new ResolveCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public CommitCommand commit() {
    return new CommitCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public AddRemoveCommand addRemove() {
    return new AddRemoveCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public RecoverCommand recover() {
    return new RecoverCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  public String path() {
    return myWorkingDir.getAbsolutePath();
  }

  public File getWorkingDir() {
    return myWorkingDir;
  }

  public boolean isEmpty() {
    return isEmptyDir(myWorkingDir);
  }

  public boolean isBookmark(@NotNull String branch) throws VcsException {
    if (branches().call().keySet().contains(branch))
      return false;
    return bookmarks().call().keySet().contains(branch);
  }

  public void resetBookmarks() {
    File dotHg = new File(getWorkingDir(), ".hg");
    FileUtil.delete(new File(dotHg, "bookmarks"));
    FileUtil.delete(new File(dotHg, "bookmarks.current"));
  }

  public PurgeCommand purge() {
    return new PurgeCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
  }

  @NotNull
  public CommandResult runCommand(@NotNull String command, @NotNull String... args) throws VcsException {
    return new FreeStyleCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, command, args).call();
  }

  public String getHgPath() {
    return myHgPath;
  }

  @NotNull
  public List<String> listFiles(@NotNull String revision) throws VcsException {
    List<FileStatus> fileStatuses = status()
            .fromRevision(revision)
            .toRevision(revision)
            .hideStatus()
            .showAllFiles()
            .call();
    List<String> files = new ArrayList<String>(fileStatuses.size());
    for (FileStatus fileStatus : fileStatuses)
      files.add(fileStatus.getPath());
    return files;
  }

  @NotNull
  public String getWorkingDirRevision() throws VcsException {
    List<String> workingDirParents = parents().call();
    if (workingDirParents.isEmpty())
      return LogCommand.ZERO_PARENT_SHORT_ID;//'hg id' shows zeroid when a working dir has no parents
    //if a working dir is in an uncommitted merge state, choose the first parent
    return workingDirParents.get(0);
  }

  public boolean containsRevision(@NotNull String revision) {
    return containsRevision(new ChangeSet(revision));
  }

  public boolean containsRevision(@NotNull ChangeSet cset) {
    try {
      id().revision(cset).inLocalRepository().call();
      return true;
    } catch (VcsException e) {
      return false;
    }
  }

  public boolean isValidRepository() {
    // need better way to check that repository copy is ok
    return myWorkingDir.isDirectory() && new File(myWorkingDir, ".hg").isDirectory();
  }

  public void setDefaultPath(@NotNull String defaultPath) throws VcsException {
    try {
      File hgrc = new File(new File(myWorkingDir, ".hg"), "hgrc");
      String content = "%include " + Constants.TEAMCITY_HG_CONFIG_FILE_NAME + "\n\n[paths]\ndefault = " + defaultPath;
      FileUtil.writeFileAndReportErrors(hgrc, content);
    } catch (IOException e) {
      throw new VcsException(e);
    }
  }

  public void setTeamCityConfig(@NotNull String configContent) throws VcsException {
    try {
      File teamcityConfig = new File(new File(myWorkingDir, ".hg"), Constants.TEAMCITY_HG_CONFIG_FILE_NAME);
      FileUtil.writeFileAndReportErrors(teamcityConfig, configContent);
    } catch (IOException e) {
      throw new VcsException(e);
    }
  }

  public boolean hasSubreposAtRevision(@NotNull String revision) {
    return !getSubrepositories(new ChangeSet(revision)).isEmpty();
  }

  public boolean hasSubreposAtRevision(@NotNull ChangeSet cset) {
    return !getSubrepositories(cset).isEmpty();
  }

  public Map<String, SubRepo> getSubrepositories(@NotNull String revision) {
    return getSubrepositories(new ChangeSet(revision));
  }

  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String revision) throws VcsException {
    if (containsSubrepoConfigChange(revision)) {
      List<String> parents = parents().ofRevision(revision).call();
      return getSubrepoConfigChanges(revision, parents);
    }
    return emptyList();
  }

  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull ChangeSet cset) {
    if (containsSubrepoConfigChange(cset)) {
      List<String> parents = new ArrayList<String>();
      for (ChangeSetRevision p : cset.getParents()) {
        parents.add(p.getId());
      }
      return getSubrepoConfigChanges(cset.getId(), parents);
    }
    return emptyList();
  }

  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String fromRevision, @NotNull String toRevision) {
    return getSubrepoConfigChanges(toRevision, asList(fromRevision));
  }

  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String revision, @NotNull List<String> parentRevisions) {
    Map<String, SubRepo> curSubrepos = getSubrepositories(revision);
    List<Map<String, SubRepo>> prevSubrepos = new ArrayList<Map<String, SubRepo>>();
    for (String parentRevision : parentRevisions) {
      prevSubrepos.add(getSubrepositories(parentRevision));
    }
    return getSubrepoConfigChanges(revision, prevSubrepos, curSubrepos);

  }

  private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String mainRepoRevision,
                                                              @NotNull List<Map<String, SubRepo>> prevSubrepos,
                                                              @NotNull Map<String, SubRepo> curSubrepos) {
    List<HgSubrepoConfigChange> configChanges = new ArrayList<HgSubrepoConfigChange>();
    for (Map.Entry<String, SubRepo> e : curSubrepos.entrySet()) {
      String path = e.getKey();
      SubRepo curSubrepo = e.getValue();
      List<SubRepo> prevs = new ArrayList<SubRepo>();
      for (Map<String, SubRepo> prev : prevSubrepos) {
        SubRepo prevSubrepo = prev.remove(path);
        if (prevSubrepo == null) //no subrepo at this path in previous revision
          continue;
        if (prevSubrepo.equals(curSubrepo)) //subrepo configuration doesn't change since previous revision
          continue;
        prevs.add(prevSubrepo);
      }
      configChanges.add(new HgSubrepoConfigChange(mainRepoRevision, e.getKey(), prevs, curSubrepo));
    }
    for (Map<String, SubRepo> prev : prevSubrepos) {
      for (Map.Entry<String, SubRepo> e : prev.entrySet()) {
        configChanges.add(new HgSubrepoConfigChange(mainRepoRevision, e.getKey(), e.getValue(), null));
      }
    }
    return configChanges;
  }

  private boolean containsSubrepoConfigChange(@NotNull ChangeSet cset) {
    for (FileStatus f : cset.getModifiedFiles()) {
      if (containsSubrepoConfigChange(f))
        return true;
    }
    return false;
  }

  private boolean containsSubrepoConfigChange(@NotNull String revision) throws VcsException {
    List<FileStatus> changedFiles = status()
            .fromRevision(revision)
            .toRevision(revision)
            .showAllFiles()
            .call();
    for (FileStatus f : changedFiles) {
      if (containsSubrepoConfigChange(f))
        return true;
    }
    return false;
  }

  private boolean containsSubrepoConfigChange(@NotNull FileStatus f) {
    return f.getPath().equals(".hgsubstate");
  }

  //path->subrepo
  @NotNull
  public Map<String, SubRepo> getSubrepositories(@NotNull final ChangeSet cset) {
    final String revId = cset.getId();

    Map<String, SubRepo> subrepos = mySubreposCache.get(revId);
    if (subrepos != null) {
      return new HashMap<String, SubRepo>(subrepos);
    }

    File catDir = null;
    final CatCommand cc = cat().setRevId(revId).files(asList(".hgsub", ".hgsubstate")).checkForFailure(false);
    try {
      catDir = cc.call();
      subrepos = HgSubs.readSubrepositories(new File(catDir, ".hgsub"), new File(catDir, ".hgsubstate"));
      mySubreposCache.put(revId, subrepos);
      return new HashMap<String, SubRepo>(subrepos);
    } catch (VcsException e) {
      return emptyMap();
    } finally {
      deleteDir(catDir, Loggers.VCS);
    }
  }

  @Override
  public String toString() {
    return myWorkingDir.getAbsolutePath();
  }

  @NotNull
  public static String shortId(@NotNull final String s) {
    if (s.length() > 12)
      return s.substring(0, 12);
    return s;
  }
}