/*
* #%L
* JavaHg
* %%
* Copyright (C) 2011 aragost Trifork ag
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/
package com.aragost.javahg;
import java.io.File;
import java.io.IOException;
import java.nio.charset.CharsetDecoder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import com.aragost.javahg.RepositoryConfiguration.CachePolicy;
import com.aragost.javahg.commands.HeadsCommand;
import com.aragost.javahg.commands.LogCommand;
import com.aragost.javahg.internals.GenericCommand;
import com.aragost.javahg.internals.GenericLogCommand;
import com.aragost.javahg.internals.HgInputStream;
import com.aragost.javahg.internals.RuntimeIOException;
import com.aragost.javahg.internals.ServerPool;
import com.aragost.javahg.internals.Utils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
/**
* A Mercurial repository.
*/
public abstract class Repository {
private final LoadingCache<String, Changeset> changesetCache;
protected Repository(CachePolicy cachePolicy) {
this.changesetCache = createCache(cachePolicy);
}
/**
* Open an existing Mercurial repository.
*
* @param mercurialRepository
* path to the local repository.
* @return the repository.
*/
public static BaseRepository open(RepositoryConfiguration conf, File mercurialRepository) {
return new BaseRepository(conf, mercurialRepository, false, null);
}
/**
* Create a new Mercurial repository and open a javahg Repository
* on it.
*
* @param directory
* the local destination path of the clone
* @return the newly created repository.
*/
public static BaseRepository create(RepositoryConfiguration conf, File directory) {
return new BaseRepository(conf, directory, true, null);
}
/**
* Clone an existing Mercurial repository.
*
* @param directory
* the local destination path of the clone
* @param otherRepoUrl
* the (possible remote) repository to clone.
* @return the newly cloned repository.
*/
public static BaseRepository clone(RepositoryConfiguration conf, File directory, String otherRepoUrl) {
return new BaseRepository(conf, directory, false, otherRepoUrl);
}
/**
* Open an existing Mercurial repository. This uses the default
* Mercurial binary from {@link RepositoryConfiguration#DEFAULT}.
*
* @param mercurialRepository
* the local path to the repository.
* @return the repository.
*/
public static BaseRepository open(File mercurialRepository) {
return new BaseRepository(RepositoryConfiguration.DEFAULT, mercurialRepository, false, null);
}
/**
* Create a new Mercurial repository. This uses the default
* Mercurial binary from {@link RepositoryConfiguration#DEFAULT}.
*
* @param directory
* the local destination path of the clone
* @return the newly created repository.
*/
public static BaseRepository create(File directory) {
return new BaseRepository(RepositoryConfiguration.DEFAULT, directory, true, null);
}
/**
* Clone an existing Mercurial repository. This uses the default
* Mercurial binary from {@link RepositoryConfiguration#DEFAULT}.
*
* @param directory
* the local destination path of the clone
* @param otherRepoUrl
* the (possible remote) repository to clone.
* @return the newly cloned repository.
*/
public static BaseRepository clone(File directory, String otherRepoUrl) {
return new BaseRepository(RepositoryConfiguration.DEFAULT, directory, false, otherRepoUrl);
}
/**
* @deprecated Use changeset instead.
*
* @param node
* a changeset ID, must be 40 hexadecimal characters.
* @return the changeset
*/
@Deprecated
public final Changeset changeSet(String node) {
return changeset(node);
}
/**
* Return a {@link Changeset} object for the specified node id.
* <p>
* The changeset is not actually loaded, but will be loaded on
* demand when a getter is called.
* <p>
* {@code null} is used to represent the null changeset.
*
* @param node
* a changeset ID, must be 40 hexadecimal characters.
* @return the changeset
*/
public final Changeset changeset(String node) {
if (node.equals(Changeset.NULL_ID)) {
return null;
} else {
try {
return basicChangeset(node);
} catch (ExecutionException e) {
throw Utils.asRuntime(e);
}
}
}
/**
*
* @param node
* a changeset node id, that is assume not to be the
* null id
* @return Changeset object with the specified id
*/
protected Changeset basicChangeset(String node) throws ExecutionException {
return this.changesetCache.get(node);
}
/**
* @return the command server pool associated with this repository.
*/
public abstract ServerPool getServerPool();
/**
* @return Get a new decoder for data in this repository
*/
public abstract CharsetDecoder newDecoder();
/**
* Close the repository. This also stops the command server
* associated with the repository.
*/
public void close() {
getServerPool().decrementRefCount();
}
/**
* @return the root directory of this repository.
*/
public abstract File getDirectory();
public abstract BaseRepository getBaseRepository();
@Override
public String toString() {
return "repo@" + getDirectory();
}
/**
* Add repository specific arguments to the hg command line for
* command execution
*
* @param commandLine
*/
public void addToCommandLine(List<String> commandLine) {
String sshBin = getBaseRepository().getConfiguration().getSshBin();
if (sshBin != null) {
commandLine.add("--config");
commandLine.add("ui.ssh=" + sshBin);
}
}
/**
* @return The version string for the Mercurial server
*/
public HgVersion getHgVersion() {
return getServerPool().getHgVersion(this);
}
/**
* Create a new WorkingCopy object for this repository.
*
* @return a working copy object for this repository
*/
public WorkingCopy workingCopy() {
return new WorkingCopy(this);
}
/**
*
* @return heads for the repository
*/
public List<Changeset> heads() {
return HeadsCommand.on(this).execute();
}
/**
* @deprecated use phases instead
*
* @param revs
* @return mapping from Changeset to Phase
*/
@Deprecated
public Map<Changeset, Phase> readPhases(String... revs) {
return phases(revs);
}
/**
* Return the phases of the specified revisions
*
* @param revs
* @return Map mapping a {@link Changeset} to a {@link Phase}
*/
public Map<Changeset, Phase> phases(String... revs) {
GenericLogCommand cmd = new GenericLogCommand(this).template("{node} {phase}\\0");
cmd.rev(revs);
HgInputStream stream = cmd.stream();
Map<Changeset, Phase> result = Maps.newHashMap();
try {
while (!stream.isEof()) {
// $ hg log --debug --template "{node} {phase}" --rev 5b80e11a7c32121b5fd926b06056bb773eff050f
// removing unknown node dd8c766936b9 from 1-phase boundary
// 5b80e11a7c32121b5fd926b06056bb773eff050f draft
// Observed with at least Mercurial Distributed SCM (version 2.3+10-9d9d15928521)
String node = stream.textUpTo(' ');
while ("removing".equals(node) && stream.find('\n')) {
node = stream.textUpTo(' ');
}
String phaseName = stream.textUpTo('\0');
Phase phase = Phase.fromText(phaseName);
result.put(changeset(node), phase);
}
} catch (IOException e) {
throw new RuntimeIOException(e);
} finally {
try {
stream.consumeAll();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
return result;
}
/**
*
* @return the tip Changeset for the repository
*/
public Changeset tip() {
List<Changeset> changesets = LogCommand.on(this).rev("tip").execute();
return Utils.single(changesets);
}
/**
* Convert the specified file object to a file object that is
* relative to the root of this repository.
* <p>
* If the file argument is not absolute it is returned as it is.
*
* @param file
* @return
*/
public File relativeFile(File file) {
if (file.isAbsolute()) {
String filePath = Utils.resolveSymlinks(file).getPath();
String repoPath = getDirectory().getPath() + File.separator;
if (filePath.startsWith(repoPath)) {
return new File(filePath.substring(repoPath.length()));
} else {
throw new IllegalArgumentException("" + file + " is not under root of repository");
}
} else {
return file;
}
}
/**
* Return a File object for the file name specified in this
* repository.
*
* @param name
* @return the File
*/
public File file(String name) {
return new File(getDirectory(), name);
}
public void lock() {
GenericCommand lock = new GenericCommand(this, "javahg-lock");
lock.execute();
}
public void unlock() {
GenericCommand unlock = new GenericCommand(this, "javahg-unlock");
unlock.execute();
}
private LoadingCache<String, Changeset> createCache(CachePolicy cachePolicy) {
CacheLoader<String, Changeset> cacheLoader = new CacheLoader<String, Changeset>() {
@Override
public Changeset load(String key) throws Exception {
return new Changeset(Repository.this, key);
}
};
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder();
switch (cachePolicy) {
case STRONG:
break;
case SOFT:
cacheBuilder.softValues();
break;
case WEAK:
cacheBuilder.weakValues();
break;
case NONE:
// Consider not to have any cache at all
cacheBuilder.maximumSize(0);
break;
}
return cacheBuilder.build(cacheLoader);
}
protected LoadingCache<String, Changeset> getChangesetCache() {
return changesetCache;
}
protected Changeset getChangesetIfInCache(String node) {
return this.changesetCache.getIfPresent(node);
}
public CacheStats getCacheStats() {
return this.changesetCache.stats();
}
}