Package org.tmatesoft.hg.repo

Source Code of org.tmatesoft.hg.repo.EM

/*
* Copyright (c) 2010-2013 TMate Software Ltd
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* For information on how to redistribute this software under
* the terms of a license other than GNU General Public License
* contact TMate Software at support@hg4j.com
*/
package org.tmatesoft.hg.repo;

import static org.tmatesoft.hg.repo.HgRepositoryFiles.*;
import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.tmatesoft.hg.core.HgIOException;
import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.core.SessionContext;
import org.tmatesoft.hg.internal.ConfigFile;
import org.tmatesoft.hg.internal.DirstateReader;
import org.tmatesoft.hg.internal.FileUtils;
import org.tmatesoft.hg.internal.Filter;
import org.tmatesoft.hg.internal.Internals;
import org.tmatesoft.hg.internal.PropertyMarshal;
import org.tmatesoft.hg.internal.RevlogStream;
import org.tmatesoft.hg.internal.SubrepoManager;
import org.tmatesoft.hg.repo.ext.HgExtensionsManager;
import org.tmatesoft.hg.util.Pair;
import org.tmatesoft.hg.util.Path;
import org.tmatesoft.hg.util.PathRewrite;
import org.tmatesoft.hg.util.ProgressSupport;



/**
* Shall be as state-less as possible, all the caching happens outside the repo, in commands/walkers
*
* @author Artem Tikhomirov
* @author TMate Software Ltd.
*/
public final class HgRepository implements SessionContext.Source {

  // IMPORTANT: if new constants added, consider fixing HgInternals#wrongRevisionIndex and HgInvalidRevisionException#getMessage

  /**
   * Revision index constant to indicate most recent revision
   */
  public static final int TIP = -3; // XXX TIP_REVISION?

  /**
   * Revision index constant to indicate invalid revision index value.
   * Primary use is default/uninitialized values where user input is expected and as return value where
   * an exception (e.g. {@link HgInvalidRevisionException}) is not desired
   */
  public static final int BAD_REVISION = Integer.MIN_VALUE; // XXX INVALID_REVISION?

  /**
   * Revision index constant to indicate working copy
   */
  public static final int WORKING_COPY = -2; // XXX WORKING_COPY_REVISION?
 
  /**
   * Constant ({@value #NO_REVISION}) to indicate revision absence or a fictitious revision of an empty repository.
   *
   * <p>Revision absence is vital e.g. for missing parent from {@link HgChangelog#parents(int, int[], byte[], byte[])} call and
   * to report cases when changeset records no corresponding manifest
   * revision {@link HgManifest#walk(int, int, org.tmatesoft.hg.repo.HgManifest.Inspector)}.
   *
   * <p> Use as imaginary revision/empty repository is handy as an argument (contrary to {@link #BAD_REVISION})
   * e.g in a status operation to visit changes from the very beginning of a repository.
   */
  public static final int NO_REVISION = -1;
 
  /**
   * Name of the primary branch, "default".
   */
  public static final String DEFAULT_BRANCH_NAME = "default";

  private final File workingDir; // .hg/../
  private final String repoLocation;
  /*
   * normalized slashes but otherwise regular file names
   * the only front-end path rewrite, kept here as rest of the library shall
   * not bother with names normalization.
   */
  private final PathRewrite normalizePath;
  private final SessionContext sessionContext;

  private HgChangelog changelog;
  private HgManifest manifest;
  private HgTags tags;
  private HgBranches branches;
  private HgMergeState mergeState;
  private SubrepoManager subRepos;
  private HgBookmarks bookmarks;
  private HgExtensionsManager extManager;
  private HgIgnore ignore;
  private HgRepoConfig repoConfig;
 
  private HgRepositoryLock wdLock, storeLock;

  private final org.tmatesoft.hg.internal.Internals impl;
 
  HgRepository(String repositoryPath) {
    workingDir = null;
    repoLocation = repositoryPath;
    normalizePath = null;
    sessionContext = null;
    impl = null;
  }
 
  /**
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  HgRepository(SessionContext ctx, String repositoryPath, File repositoryRoot) throws HgRuntimeException {
    assert ".hg".equals(repositoryRoot.getName()) && repositoryRoot.isDirectory();
    assert repositoryPath != null;
    assert repositoryRoot != null;
    assert ctx != null;
    workingDir = repositoryRoot.getParentFile();
    if (workingDir == null) {
      throw new IllegalArgumentException(repositoryRoot.toString());
    }
    repoLocation = repositoryPath;
    sessionContext = ctx;
    impl = new Internals(this, repositoryRoot, new Internals.ImplAccess() {
     
      public RevlogStream getStream(HgDataFile df) {
        return df.content;
      }
      public RevlogStream getManifestStream() {
        return HgRepository.this.getManifest().content;
      }
      public RevlogStream getChangelogStream() {
        return HgRepository.this.getChangelog().content;
      }
    });
    normalizePath = impl.buildNormalizePathRewrite();
  }

  @Override
  public String toString() {
    return getClass().getSimpleName() + "[" + getLocation() + (isInvalid() ? "(BAD)" : "") + "]";
  }                        
 
  /**
   * Path to repository which has been used to initialize this instance. The value is always present, even
   * if no repository has been found at that location ({@link #isInvalid()} is <code>true</code>) and serves
   * as an extra description of the failure.
   *
   * <p> It's important to understand this is purely descriptive attribute, it's kept as close as possible to
   * original value users supply to {@link HgLookup}. To get actual repository location, use methods that
   * provide {@link File}, e.g. {@link #getWorkingDir()}
   *
   * @return repository location information, never <code>null</code>
   */
  public String getLocation() {
    return repoLocation; // XXX field to keep this is bit too much
  }

  public boolean isInvalid() {
    return impl == null || impl.isInvalid();
  }
 
  public HgChangelog getChangelog() {
    if (changelog == null) {
      RevlogStream content = impl.createChangelogStream();
      changelog = new HgChangelog(this, content);
    }
    return changelog;
  }
 
  public HgManifest getManifest() {
    if (manifest == null) {
      RevlogStream content = impl.createManifestStream();
      manifest = new HgManifest(this, content, impl.buildFileNameEncodingHelper());
    }
    return manifest;
  }
 
  /**
   * Access snapshot of repository tags.
   *
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public HgTags getTags() throws HgRuntimeException {
    if (tags == null) {
      tags = new HgTags(impl);
      tags.read();
    } else {
      tags.reloadIfChanged();
    }
    return tags;
  }
 
  /**
   * Access branch information. Returns a snapshot of branch information as it's available at the time of the call.
   * If repository get changed, use this method to obtain an up-to-date state.
   *
   * @return branch manager instance, never <code>null</code>
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public HgBranches getBranches() throws HgRuntimeException {
    final ProgressSupport ps = ProgressSupport.Factory.get(null);
    if (branches == null) {
      branches = new HgBranches(impl);
      branches.collect(ps);
    } else {
      branches.reloadIfChanged(ps);
    }
    return branches;
  }

  /**
   * Access state of the recent merge
   * @return merge state facility, never <code>null</code>
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public HgMergeState getMergeState() throws HgRuntimeException {
    if (mergeState == null) {
      mergeState = new HgMergeState(impl);
      mergeState.refresh();
    }
    return mergeState;
  }
 
  public HgDataFile getFileNode(String path) {
    CharSequence nPath = normalizePath.rewrite(path);
    Path p = sessionContext.getPathFactory().path(nPath);
    return getFileNode(p);
  }

  public HgDataFile getFileNode(Path path) {
    RevlogStream content = impl.resolveStoreFile(path);
    assert content != null;
    return new HgDataFile(this, path, content);
  }

  /* clients need to rewrite path from their FS to a repository-friendly paths, and, perhaps, vice versa*/
  public PathRewrite getToRepoPathHelper() {
    return normalizePath;
  }

  /**
   * @return pair of values, {@link Pair#first()} and {@link Pair#second()} are respective parents, never <code>null</code>.
   * @throws HgInvalidControlFileException if attempt to read information about working copy parents from dirstate failed
   */
  public Pair<Nodeid,Nodeid> getWorkingCopyParents() throws HgInvalidControlFileException {
    return DirstateReader.readParents(impl);
  }
 
  /**
   * @return name of the branch associated with working directory, never <code>null</code>.
   * @throws HgInvalidControlFileException if attempt to read branch name failed.
   */
  public String getWorkingCopyBranchName() throws HgInvalidControlFileException {
    /*
     * TODO [post-1.1] 1) cache value (now if cached, is not updated after commit)
     * 2) move to a better place, e.g. WorkingCopy container that tracks both dirstate and branches
     * (and, perhaps, undo, lastcommit and other similar information), and is change listener so that we don't need to
     * worry about this cached value become stale
     */
    String wcBranch = DirstateReader.readBranch(impl);
    return wcBranch;
  }

  /**
   * @return location where user files (shall) reside
   */
  public File getWorkingDir() {
    return workingDir;
  }
 
  /**
   * Provides access to sub-repositories defined in this repository. Enumerated  sub-repositories are those directly
   * known, not recursive collection of all nested sub-repositories.
   * @return list of all known sub-repositories in this repository, or empty list if none found.
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public List<HgSubrepoLocation> getSubrepositories() throws HgInvalidControlFileException {
    if (subRepos == null) {
      subRepos = new SubrepoManager(this);
      subRepos.read();
    }
    return subRepos.all();
  }

 
  /**
   * Repository-specific configuration.
   * @return access to configuration options, never <code>null</code>
   */
  public HgRepoConfig getConfiguration() /* XXX throws HgInvalidControlFileException? Description of the exception suggests it is only for files under ./hg/*/ {
    if (repoConfig == null) {
      try {
        ConfigFile configFile = impl.readConfiguration();
        repoConfig = new HgRepoConfig(configFile);
      } catch (HgIOException ex) {
        String m = "Errors while reading user configuration file";
        getSessionContext().getLog().dump(getClass(), Warn, ex, m);
        return new HgRepoConfig(new ConfigFile(getSessionContext())); // empty config, do not cache, allow to try once again
        //throw new HgInvalidControlFileException(m, ex, null);
      }
    }
    return repoConfig;
  }

  // There seem to be no cases when access to HgDirstate is required from outside
  // (guess, working dir/revision walkers may hide dirstate access and no public visibility needed)
  /*package-local*/ final HgDirstate loadDirstate(Path.Source pathFactory) throws HgInvalidControlFileException {
    PathRewrite canonicalPath = null;
    if (!impl.isCaseSensitiveFileSystem()) {
      canonicalPath = new PathRewrite() {

        public CharSequence rewrite(CharSequence path) {
          return path.toString().toLowerCase();
        }
      };
    }
    HgDirstate ds = new HgDirstate(impl, pathFactory, canonicalPath);
    ds.read();
    return ds;
  }

  /**
   * Access to configured set of ignored files.
   * @see HgIgnore#isIgnored(Path)
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public HgIgnore getIgnore() throws HgInvalidControlFileException {
    // TODO read config for additional locations
    if (ignore == null) {
      ignore = new HgIgnore(getToRepoPathHelper());
      ignore.read(impl);
    } else {
      ignore.reloadIfChanged(impl);
    }
    return ignore;
  }

  /**
   * Mercurial saves message user has supplied for a commit to facilitate message re-use in case commit fails.
   * This method provides this saved message.
   * 
   * @return message used for last commit attempt, or <code>null</code> if none
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public String getCommitLastMessage() throws HgInvalidControlFileException {
    File lastMessage = impl.getRepositoryFile(LastMessage);
    if (!lastMessage.canRead()) {
      return null;
    }
    FileReader fr = null;
    try {
      fr = new FileReader(lastMessage);
      CharBuffer cb = CharBuffer.allocate(Internals.ltoi(lastMessage.length()));
      fr.read(cb);
      return cb.flip().toString();
    } catch (IOException ex) {
      throw new HgInvalidControlFileException("Can't retrieve message of last commit attempt", ex, lastMessage);
    } finally {
      new FileUtils(getSessionContext().getLog(), this).closeQuietly(fr, lastMessage);
    }
  }

  /**
   * Access repository lock that covers non-store parts of the repository (dirstate, branches, etc -
   * everything that has to do with working directory state).
   *
   * Note, the lock object returned merely gives access to lock mechanism. NO ACTUAL LOCKING IS DONE.
   * Use {@link HgRepositoryLock#acquire()} to actually lock the repository. 
   *  
   * @return lock object, never <code>null</code>
   */
  public HgRepositoryLock getWorkingDirLock() {
    if (wdLock == null) {
      int timeout = getLockTimeout();
      File lf = impl.getRepositoryFile(WorkingCopyLock);
      synchronized (this) {
        if (wdLock == null) {
          wdLock = new HgRepositoryLock(lf, timeout);
        }
      }
    }
    return wdLock;
  }

  /**
   * Access repository lock that covers repository intrinsic files, unrelated to
   * the state of working directory
   * @return lock object, never <code>null</code>
   */
  public HgRepositoryLock getStoreLock() {
    if (storeLock == null) {
      int timeout = getLockTimeout();
      File fl = impl.getRepositoryFile(StoreLock);
      synchronized (this) {
        if (storeLock == null) {
          storeLock = new HgRepositoryLock(fl, timeout);
        }
      }
    }
    return storeLock;
  }

  /**
   * Access bookmarks-related functionality
   * @return facility to manage bookmarks, never <code>null</code>
   * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
   */
  public HgBookmarks getBookmarks() throws HgRuntimeException {
    if (bookmarks == null) {
      bookmarks = new HgBookmarks(impl);
      bookmarks.read();
    } else {
      bookmarks.reloadIfChanged();
    }
    return bookmarks;
  }
 
  public HgExtensionsManager getExtensions() {
    if (extManager == null) {
      class EM extends HgExtensionsManager {
        EM() {
          super(HgRepository.this.getImplHelper());
        }
      }
      extManager = new EM();
    }
    return extManager;
  }

  /**
   * @return session environment of the repository
   */
  public SessionContext getSessionContext() {
    return sessionContext;
  }
 
  /*package-local*/ List<Filter> getFiltersFromRepoToWorkingDir(Path p) {
    return instantiateFilters(p, new Filter.Options(Filter.Direction.FromRepo));
  }

  /*package-local*/ List<Filter> getFiltersFromWorkingDirToRepo(Path p) {
    return instantiateFilters(p, new Filter.Options(Filter.Direction.ToRepo));
  }
 
  /*package-local*/ File getFile(HgDataFile dataFile) {
    return new File(getWorkingDir(), dataFile.getPath().toString());
  }
 
  /*package-local*/ Internals getImplHelper() {
    return impl;
  }

  private List<Filter> instantiateFilters(Path p, Filter.Options opts) {
    List<Filter.Factory> factories = impl.getFilters();
    if (factories.isEmpty()) {
      return Collections.emptyList();
    }
    ArrayList<Filter> rv = new ArrayList<Filter>(factories.size());
    for (Filter.Factory ff : factories) {
      Filter f = ff.create(p, opts);
      if (f != null) {
        rv.add(f);
      }
    }
    return rv;
  }

  private int getLockTimeout() {
    int cfgValue = getConfiguration().getIntegerValue("ui", "timeout", 600);
    if (getSessionContext().getConfigurationProperty(Internals.CFG_PROPERTY_FS_LOCK_TIMEOUT, null) != null) {
      return new PropertyMarshal(sessionContext).getInt(Internals.CFG_PROPERTY_FS_LOCK_TIMEOUT, cfgValue);
    }
    return cfgValue;
  }
}
TOP

Related Classes of org.tmatesoft.hg.repo.EM

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.