tikhomirov@74: /*
tikhomirov@526:  * Copyright (c) 2011-2013 TMate Software Ltd
tikhomirov@74:  *  
tikhomirov@74:  * This program is free software; you can redistribute it and/or modify
tikhomirov@74:  * it under the terms of the GNU General Public License as published by
tikhomirov@74:  * the Free Software Foundation; version 2 of the License.
tikhomirov@74:  *
tikhomirov@74:  * This program is distributed in the hope that it will be useful,
tikhomirov@74:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
tikhomirov@74:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
tikhomirov@74:  * GNU General Public License for more details.
tikhomirov@74:  *
tikhomirov@74:  * For information on how to redistribute this software under
tikhomirov@74:  * the terms of a license other than GNU General Public License
tikhomirov@102:  * contact TMate Software at support@hg4j.com
tikhomirov@74:  */
tikhomirov@74: package org.tmatesoft.hg.internal;
tikhomirov@74: 
tikhomirov@456: import static org.tmatesoft.hg.util.LogFacility.Severity.Error;
tikhomirov@74: 
tikhomirov@202: import java.io.File;
tikhomirov@411: import java.nio.charset.Charset;
tikhomirov@114: import java.util.ArrayList;
tikhomirov@379: import java.util.Arrays;
tikhomirov@379: import java.util.Collections;
tikhomirov@407: import java.util.Iterator;
tikhomirov@379: import java.util.LinkedHashSet;
tikhomirov@114: import java.util.List;
tikhomirov@379: import java.util.StringTokenizer;
tikhomirov@114: 
tikhomirov@628: import org.tmatesoft.hg.core.HgIOException;
tikhomirov@382: import org.tmatesoft.hg.core.SessionContext;
tikhomirov@493: import org.tmatesoft.hg.repo.HgDataFile;
tikhomirov@525: import org.tmatesoft.hg.repo.HgInternals;
tikhomirov@331: import org.tmatesoft.hg.repo.HgRepoConfig.ExtensionsSection;
tikhomirov@114: import org.tmatesoft.hg.repo.HgRepository;
tikhomirov@526: import org.tmatesoft.hg.repo.HgRepositoryFiles;
tikhomirov@526: import org.tmatesoft.hg.repo.HgRepositoryLock;
tikhomirov@526: import org.tmatesoft.hg.repo.HgRuntimeException;
tikhomirov@610: import org.tmatesoft.hg.util.LogFacility;
tikhomirov@591: import org.tmatesoft.hg.util.Path;
tikhomirov@74: import org.tmatesoft.hg.util.PathRewrite;
tikhomirov@74: 
tikhomirov@74: /**
tikhomirov@74:  * Fields/members that shall not be visible  
tikhomirov@74:  * 
tikhomirov@74:  * @author Artem Tikhomirov
tikhomirov@74:  * @author TMate Software Ltd.
tikhomirov@74:  */
tikhomirov@501: public final class Internals implements SessionContext.Source {
tikhomirov@74: 	
tikhomirov@388: 	/**
tikhomirov@388: 	 * Allows to specify Mercurial installation directory to detect installation-wide configurations.
tikhomirov@388: 	 * Without this property set, hg4j would attempt to deduce this value locating hg executable. 
tikhomirov@388: 	 */
tikhomirov@379: 	public static final String CFG_PROPERTY_HG_INSTALL_ROOT = "hg4j.hg.install_root";
tikhomirov@388: 
tikhomirov@388: 	/**
tikhomirov@388: 	 * Tells repository not to cache files/revlogs
tikhomirov@388: 	 * XXX perhaps, need to respect this property not only for data files, but for manifest and changelog as well?
tikhomirov@388: 	 * (@see HgRepository#getChangelog and #getManifest())  
tikhomirov@388: 	 */
tikhomirov@388: 	public static final String CFG_PROPERTY_REVLOG_STREAM_CACHE = "hg4j.repo.disable_revlog_cache";
tikhomirov@379: 	
tikhomirov@411: 	/**
tikhomirov@411: 	 * Name of charset to use when translating Unicode filenames to Mercurial storage paths, string, 
tikhomirov@411: 	 * to resolve with {@link Charset#forName(String)}.
tikhomirov@411: 	 * E.g. "cp1251" or "Latin-1".
tikhomirov@411: 	 * 
tikhomirov@411: 	 * 
Mercurial uses system encoding when mangling storage paths. Default value
tikhomirov@411: 	 * based on 'file.encoding' Java system property is usually fine here, however
tikhomirov@411: 	 * in certain scenarios it may be desirable to force a different one, and this 
tikhomirov@411: 	 * property is exactly for this purpose.
tikhomirov@411: 	 * 
tikhomirov@411: 	 * 
E.g. Eclipse defaults to project encoding (Launch config, Common page) when launching an application, 
tikhomirov@411: 	 * and if your project happen to use anything but filesystem default (say, UTF8 on cp1251 system),
tikhomirov@411: 	 * native storage paths won't match
tikhomirov@411: 	 */
tikhomirov@412: 	public static final String CFG_PROPERTY_FS_FILENAME_ENCODING = "hg.fs.filename.encoding";
tikhomirov@411: 	
tikhomirov@504: 	/**
tikhomirov@504: 	 * Timeout, in seconds, to acquire filesystem {@link HgRepositoryLock lock}.
tikhomirov@504: 	 * 
tikhomirov@504: 	 * Mercurial provides 'ui.timeout' in hgrc (defaults to 600 seconds) to specify how long 
tikhomirov@504: 	 * it shall try to acquire a lock for storage or working directory prior to fail.
tikhomirov@504: 	 *  
tikhomirov@504: 	 * This configuration property allows to override timeout value from Mercurial's configuration
tikhomirov@504: 	 * file and use Hg4J-specific value instead. 
tikhomirov@504: 	 * 
tikhomirov@504: 	 * Integer value, use negative for attempts to acquire lock until success, and zero to try once and fail immediately. 
tikhomirov@504: 	 */
tikhomirov@504: 	public static final String CFG_PROPERTY_FS_LOCK_TIMEOUT = "hg4j.fs.lock.timeout";
tikhomirov@584: 	
tikhomirov@584: 	/**
tikhomirov@584: 	 * Alternative, more effective approach to build revision text from revlog patches - collect all the
tikhomirov@584: 	 * patches one by one, starting at revision next to base, and apply against each other to get 
tikhomirov@584: 	 * one final patch, which in turned is applied to base revision. 
tikhomirov@584: 	 * 
tikhomirov@584: 	 * Original approach is to apply each patch to a previous revision, so that with base revision 
tikhomirov@584: 	 * of 1M and three patches, each altering just a tiny fraction
tikhomirov@584: 	 * of the origin, with latter approach we consume 1M (original) + 1M (first patch applied) + 1M (second
tikhomirov@584: 	 * patch applied) + 1M (third patch applied).
tikhomirov@584: 	 * 
tikhomirov@584: 	 * Alternative approach, controlled with this option, first combines these there patches into one,
tikhomirov@584: 	 * and only then applies it to base revision, eliminating 2 intermediate elements.
tikhomirov@584: 	 * 
tikhomirov@584: 	 * Present default value for this option is FALSE, and will be changed in future, once
tikhomirov@584: 	 * tests prove support is fully functional (likely in v1.2).
tikhomirov@584: 	 * 
tikhomirov@584: 	 * @since 1.1
tikhomirov@584: 	 */
tikhomirov@584: 	public static final String CFG_PROPERTY_PATCH_MERGE = "hg4j.repo.merge_revlog_patches";
tikhomirov@530: 
tikhomirov@530: 	public static final int REVLOGV1_RECORD_SIZE = 64;
tikhomirov@530: 
tikhomirov@114: 	private List filterFactories;
tikhomirov@490: 	private final HgRepository repo;
tikhomirov@490: 	private final File repoDir;
tikhomirov@388: 	private final boolean isCaseSensitiveFileSystem;
tikhomirov@490: 	private final DataAccessProvider dataAccess;
tikhomirov@591: 	private final ImplAccess implAccess;
tikhomirov@493: 	
tikhomirov@493: 	private final int requiresFlags;
tikhomirov@114: 
tikhomirov@493: 	private final PathRewrite dataPathHelper; // access to file storage area (usually under .hg/store/data/), with filenames mangled  
tikhomirov@493: 	private final PathRewrite repoPathHelper; // access to system files (under .hg/store if requires has 'store' flag)
tikhomirov@493: 
tikhomirov@608: 	private final boolean shallMergePatches;
tikhomirov@591: 	private final RevlogStreamFactory streamProvider;
tikhomirov@591: 
tikhomirov@591: 	public Internals(HgRepository hgRepo, File hgDir, ImplAccess implementationAccess) throws HgRuntimeException {
tikhomirov@490: 		repo = hgRepo;
tikhomirov@490: 		repoDir = hgDir;
tikhomirov@591: 		implAccess = implementationAccess;
tikhomirov@388: 		isCaseSensitiveFileSystem = !runningOnWindows();
tikhomirov@490: 		SessionContext ctx = repo.getSessionContext();
tikhomirov@490: 		dataAccess = new DataAccessProvider(ctx);
tikhomirov@493: 		RepoInitializer repoInit = new RepoInitializer().initRequiresFromFile(repoDir);
tikhomirov@493: 		requiresFlags = repoInit.getRequires();
tikhomirov@501: 		dataPathHelper = repoInit.buildDataFilesHelper(getSessionContext());
tikhomirov@493: 		repoPathHelper = repoInit.buildStoreFilesHelper();
tikhomirov@608: 		final PropertyMarshal pm = new PropertyMarshal(ctx);
tikhomirov@608: 		boolean shallCacheRevlogsInRepo = pm.getBoolean(CFG_PROPERTY_REVLOG_STREAM_CACHE, true);
tikhomirov@591: 		streamProvider = new RevlogStreamFactory(this, shallCacheRevlogsInRepo); 
tikhomirov@608: 		shallMergePatches = pm.getBoolean(Internals.CFG_PROPERTY_PATCH_MERGE, false);
tikhomirov@114: 	}
tikhomirov@295: 	
tikhomirov@490: 	public boolean isInvalid() {
tikhomirov@490: 		return !repoDir.exists() || !repoDir.isDirectory();
tikhomirov@490: 	}
tikhomirov@490: 	
tikhomirov@526: 	public File getRepositoryFile(HgRepositoryFiles f) {
tikhomirov@610: 		return f.residesUnderRepositoryRoot() ? getFileFromRepoDir(f.getName()) : new File(repo.getWorkingDir(), f.getName());
tikhomirov@526: 	}
tikhomirov@526: 
tikhomirov@493: 	/**
tikhomirov@493: 	 * Access files under ".hg/".
tikhomirov@493: 	 * File not necessarily exists, this method is merely a factory for Files at specific, configuration-dependent location. 
tikhomirov@493: 	 * 
tikhomirov@493: 	 * @param name shall be normalized path
tikhomirov@493: 	 */
tikhomirov@490: 	public File getFileFromRepoDir(String name) {
tikhomirov@490: 		return new File(repoDir, name);
tikhomirov@490: 	}
tikhomirov@493: 
tikhomirov@493: 	/**
tikhomirov@493: 	 * Access files under ".hg/store/" or ".hg/" depending on use of 'store' in requires.
tikhomirov@493: 	 * File not necessarily exists, this method is merely a factory for Files at specific, configuration-dependent location.
tikhomirov@493: 	 *  
tikhomirov@493: 	 * @param name shall be normalized path
tikhomirov@493: 	 */
tikhomirov@493: 	public File getFileFromStoreDir(String name) {
tikhomirov@493: 		CharSequence location = repoPathHelper.rewrite(name);
tikhomirov@493: 		return new File(repoDir, location.toString());
tikhomirov@493: 	}
tikhomirov@493: 	
tikhomirov@493: 	/**
tikhomirov@493: 	 * Access files under ".hg/store/data", ".hg/store/dh/" or ".hg/data" according to settings in requires file.
tikhomirov@493: 	 * File not necessarily exists, this method is merely a factory for Files at specific, configuration-dependent location.
tikhomirov@493: 	 * 
tikhomirov@493: 	 * @param name shall be normalized path, without .i or .d suffixes
tikhomirov@493: 	 */
tikhomirov@493: 	public File getFileFromDataDir(CharSequence path) {
tikhomirov@493: 		CharSequence storagePath = dataPathHelper.rewrite(path);
tikhomirov@493: 		return new File(repoDir, storagePath.toString());
tikhomirov@493: 	}
tikhomirov@490: 	
tikhomirov@501: 	public SessionContext getSessionContext() {
tikhomirov@490: 		return repo.getSessionContext();
tikhomirov@490: 	}
tikhomirov@490: 	
tikhomirov@610: 	public LogFacility getLog() {
tikhomirov@610: 		return getSessionContext().getLog();
tikhomirov@610: 	}
tikhomirov@610: 	
tikhomirov@490: 	public HgRepository getRepo() {
tikhomirov@490: 		return repo;
tikhomirov@490: 	}
tikhomirov@490: 	
tikhomirov@490: 	public DataAccessProvider getDataAccess() {
tikhomirov@490: 		return dataAccess;
tikhomirov@490: 	}
tikhomirov@490: 
tikhomirov@388: 	public PathRewrite buildNormalizePathRewrite() {
tikhomirov@388: 		if (runningOnWindows()) {
tikhomirov@409: 			return new WinToNixPathRewrite();
tikhomirov@388: 		} else {
tikhomirov@388: 			return new PathRewrite.Empty(); // or strip leading slash, perhaps? 
tikhomirov@388: 		}
tikhomirov@388: 	}
tikhomirov@74: 
tikhomirov@490: 	public List getFilters() {
tikhomirov@114: 		if (filterFactories == null) {
tikhomirov@114: 			filterFactories = new ArrayList();
tikhomirov@490: 			ExtensionsSection cfg = repo.getConfiguration().getExtensions();
tikhomirov@331: 			if (cfg.isEnabled("eol")) {
tikhomirov@114: 				NewlineFilter.Factory ff = new NewlineFilter.Factory();
tikhomirov@490: 				ff.initialize(repo);
tikhomirov@114: 				filterFactories.add(ff);
tikhomirov@114: 			}
tikhomirov@331: 			if (cfg.isEnabled("keyword")) {
tikhomirov@114: 				KeywordFilter.Factory ff = new KeywordFilter.Factory();
tikhomirov@490: 				ff.initialize(repo);
tikhomirov@114: 				filterFactories.add(ff);
tikhomirov@114: 			}
tikhomirov@114: 		}
tikhomirov@114: 		return filterFactories;
tikhomirov@114: 	}
tikhomirov@202: 	
tikhomirov@388: 	public boolean isCaseSensitiveFileSystem() {
tikhomirov@388: 		return isCaseSensitiveFileSystem;
tikhomirov@388: 	}
tikhomirov@412: 	
tikhomirov@412: 	public EncodingHelper buildFileNameEncodingHelper() {
tikhomirov@539: 		return new EncodingHelper(getFilenameEncoding(), repo.getSessionContext());
tikhomirov@539: 	}
tikhomirov@539: 	
tikhomirov@559: 	public boolean fncacheInUse() {
tikhomirov@559: 		return (getRequiresFlags() & RequiresFile.FNCACHE) != 0;
tikhomirov@559: 	}
tikhomirov@559: 	
tikhomirov@539: 	/*package-local*/ Charset getFilenameEncoding() {
tikhomirov@539: 		return getFileEncoding(getSessionContext());
tikhomirov@412: 	}
tikhomirov@412: 	
tikhomirov@490: 	/*package-local*/ static Charset getFileEncoding(SessionContext ctx) {
tikhomirov@490: 		Object altEncoding = ctx.getConfigurationProperty(CFG_PROPERTY_FS_FILENAME_ENCODING, null);
tikhomirov@412: 		Charset cs;
tikhomirov@412: 		if (altEncoding == null) {
tikhomirov@412: 			cs = Charset.defaultCharset();
tikhomirov@412: 		} else {
tikhomirov@412: 			try {
tikhomirov@412: 				cs = Charset.forName(altEncoding.toString());
tikhomirov@412: 			} catch (IllegalArgumentException ex) {
tikhomirov@412: 				// both IllegalCharsetNameException and UnsupportedCharsetException are subclasses of IAE, too
tikhomirov@412: 				// not severe enough to throw an exception, imo. Just record the fact it's bad ad we ignore it 
tikhomirov@490: 				ctx.getLog().dump(Internals.class, Error, ex, String.format("Bad configuration value for filename encoding %s", altEncoding));
tikhomirov@412: 				cs = Charset.defaultCharset();
tikhomirov@412: 			}
tikhomirov@412: 		}
tikhomirov@412: 		return cs;
tikhomirov@412: 	}
tikhomirov@493: 	
tikhomirov@493: 	/**
tikhomirov@493: 	 * Access to mangled name of a file in repository storage, may come handy for debug.
tikhomirov@493: 	 * @return mangled path of the repository file
tikhomirov@493: 	 */
tikhomirov@493: 	public CharSequence getStoragePath(HgDataFile df) {
tikhomirov@493: 		return dataPathHelper.rewrite(df.getPath().toString());
tikhomirov@493: 	}
tikhomirov@493: 
tikhomirov@539: 	public int getRequiresFlags() {
tikhomirov@539: 		return requiresFlags;
tikhomirov@539: 	}
tikhomirov@526: 	
tikhomirov@608: 	boolean shallMergePatches() {
tikhomirov@608: 		return shallMergePatches;
tikhomirov@608: 	}
tikhomirov@608: 
tikhomirov@608: 	RevlogChangeMonitor getRevlogTracker(File f) {
tikhomirov@608: 		// TODO decide whether to use one monitor per multiple files or 
tikhomirov@608: 		// an instance per file; and let SessionContext pass alternative implementation)
tikhomirov@608: 		return new RevlogChangeMonitor(f);
tikhomirov@608: 	}
tikhomirov@608: 	
tikhomirov@292: 	public static boolean runningOnWindows() {
tikhomirov@292: 		return System.getProperty("os.name").indexOf("Windows") != -1;
tikhomirov@292: 	}
tikhomirov@379: 	
tikhomirov@379: 	/**
tikhomirov@419: 	 * @param fsHint optional hint pointing to filesystem of interest (generally, it's possible to mount 
tikhomirov@413: 	 * filesystems with different capabilities and repository's capabilities would depend on which fs it resides) 
tikhomirov@413: 	 * @return true if executable files deserve tailored handling 
tikhomirov@413: 	 */
tikhomirov@413: 	public static boolean checkSupportsExecutables(File fsHint) {
tikhomirov@413: 		// *.exe are not executables for Mercurial
tikhomirov@413: 		return !runningOnWindows();
tikhomirov@413: 	}
tikhomirov@413: 
tikhomirov@413: 	/**
tikhomirov@419: 	 * @param fsHint optional hint pointing to filesystem of interest (generally, it's possible to mount 
tikhomirov@413: 	 * filesystems with different capabilities and repository's capabilities would depend on which fs it resides) 
tikhomirov@413: 	 * @return true if filesystem knows what symbolic links are 
tikhomirov@413: 	 */
tikhomirov@413: 	public static boolean checkSupportsSymlinks(File fsHint) {
tikhomirov@413: 		// Windows supports soft symbolic links starting from Vista 
tikhomirov@413: 		// However, as of Mercurial 2.1.1, no support for this functionality
tikhomirov@413: 		// XXX perhaps, makes sense to override with a property a) to speed up when no links are in use b) investigate how this runs windows
tikhomirov@413: 		return !runningOnWindows();
tikhomirov@413: 	}
tikhomirov@413: 
tikhomirov@413: 	
tikhomirov@413: 	/**
tikhomirov@379: 	 * For Unix, returns installation root, which is the parent directory of the hg executable (or symlink) being run.
tikhomirov@379: 	 * For Windows, it's Mercurial installation directory itself 
tikhomirov@382: 	 * @param ctx 
tikhomirov@379: 	 */
tikhomirov@382: 	private static File findHgInstallRoot(SessionContext ctx) {
tikhomirov@379: 		// let clients to override Hg install location 
tikhomirov@456: 		String p = (String) ctx.getConfigurationProperty(CFG_PROPERTY_HG_INSTALL_ROOT, null);
tikhomirov@379: 		if (p != null) {
tikhomirov@379: 			return new File(p);
tikhomirov@379: 		}
tikhomirov@379: 		StringTokenizer st = new StringTokenizer(System.getenv("PATH"), System.getProperty("path.separator"), false);
tikhomirov@379: 		final boolean runsOnWin = runningOnWindows();
tikhomirov@379: 		while (st.hasMoreTokens()) {
tikhomirov@379: 			String pe = st.nextToken();
tikhomirov@379: 			File execCandidate = new File(pe, runsOnWin ? "hg.exe" : "hg");
tikhomirov@379: 			if (execCandidate.exists() && execCandidate.isFile()) {
tikhomirov@379: 				File execDir = execCandidate.getParentFile();
tikhomirov@379: 				// e.g. on Unix runs "/shared/tools/bin/hg", directory of interest is "/shared/tools/" 
tikhomirov@379: 				return runsOnWin ? execDir : execDir.getParentFile();
tikhomirov@379: 			}
tikhomirov@379: 		}
tikhomirov@379: 		return null;
tikhomirov@379: 	}
tikhomirov@379: 	
tikhomirov@379: 	/**
tikhomirov@579: 	 * User-specific configuration, from system-wide and user home locations, without any repository-specific data.
tikhomirov@379: 	 * @see http://www.selenic.com/mercurial/hgrc.5.html
tikhomirov@379: 	 */
tikhomirov@628: 	public static ConfigFile readConfiguration(SessionContext sessionCtx) throws HgIOException {
tikhomirov@483: 		ConfigFile configFile = new ConfigFile(sessionCtx);
tikhomirov@483: 		File hgInstallRoot = findHgInstallRoot(sessionCtx); // may be null
tikhomirov@379: 		//
tikhomirov@351: 		if (runningOnWindows()) {
tikhomirov@379: 			if (hgInstallRoot != null) {
tikhomirov@379: 				for (File f : getWindowsConfigFilesPerInstall(hgInstallRoot)) {
tikhomirov@379: 					configFile.addLocation(f);
tikhomirov@379: 				}
tikhomirov@379: 			}
tikhomirov@379: 			LinkedHashSet locations = new LinkedHashSet();
tikhomirov@379: 			locations.add(System.getenv("USERPROFILE"));
tikhomirov@379: 			locations.add(System.getenv("HOME"));
tikhomirov@379: 			locations.remove(null);
tikhomirov@379: 			for (String loc : locations) {
tikhomirov@379: 				File location = new File(loc);
tikhomirov@379: 				configFile.addLocation(new File(location, "Mercurial.ini"));
tikhomirov@379: 				configFile.addLocation(new File(location, ".hgrc"));
tikhomirov@379: 			}
tikhomirov@351: 		} else {
tikhomirov@379: 			if (hgInstallRoot != null) {
tikhomirov@379: 				File d = new File(hgInstallRoot, "etc/mercurial/hgrc.d/");
tikhomirov@379: 				if (d.isDirectory() && d.canRead()) {
tikhomirov@379: 					for (File f : listConfigFiles(d)) {
tikhomirov@351: 						configFile.addLocation(f);
tikhomirov@351: 					}
tikhomirov@351: 				}
tikhomirov@379: 				configFile.addLocation(new File(hgInstallRoot, "etc/mercurial/hgrc"));
tikhomirov@379: 			}
tikhomirov@379: 			// same, but with absolute paths
tikhomirov@379: 			File d = new File("/etc/mercurial/hgrc.d/");
tikhomirov@379: 			if (d.isDirectory() && d.canRead()) {
tikhomirov@379: 				for (File f : listConfigFiles(d)) {
tikhomirov@379: 					configFile.addLocation(f);
tikhomirov@379: 				}
tikhomirov@351: 			}
tikhomirov@351: 			configFile.addLocation(new File("/etc/mercurial/hgrc"));
tikhomirov@379: 			configFile.addLocation(new File(System.getenv("HOME"), ".hgrc"));
tikhomirov@351: 		}
tikhomirov@579: 		return configFile;
tikhomirov@579: 	}
tikhomirov@579: 
tikhomirov@579: 	/**
tikhomirov@579: 	 * Repository-specific configuration
tikhomirov@579: 	 * @see http://www.selenic.com/mercurial/hgrc.5.html
tikhomirov@579: 	 */
tikhomirov@628: 	public ConfigFile readConfiguration() throws HgIOException {
tikhomirov@579: 		ConfigFile configFile = readConfiguration(repo.getSessionContext());
tikhomirov@331: 		// last one, overrides anything else
tikhomirov@331: 		// /.hg/hgrc
tikhomirov@490: 		configFile.addLocation(getFileFromRepoDir("hgrc"));
tikhomirov@331: 		return configFile;
tikhomirov@331: 	}
tikhomirov@579: 
tikhomirov@591: 	/*package-local*/ImplAccess getImplAccess() {
tikhomirov@591: 		return implAccess;
tikhomirov@591: 	}
tikhomirov@379: 	
tikhomirov@379: 	private static List getWindowsConfigFilesPerInstall(File hgInstallDir) {
tikhomirov@379: 		File f = new File(hgInstallDir, "Mercurial.ini");
tikhomirov@379: 		if (f.exists()) {
tikhomirov@379: 			return Collections.singletonList(f);
tikhomirov@379: 		}
tikhomirov@379: 		f = new File(hgInstallDir, "hgrc.d/");
tikhomirov@379: 		if (f.canRead() && f.isDirectory()) {
tikhomirov@379: 			return listConfigFiles(f);
tikhomirov@379: 		}
tikhomirov@608: 		// TODO [post-1.1] query registry, e.g. with
tikhomirov@379: 		// Runtime.exec("reg query HKLM\Software\Mercurial")
tikhomirov@379: 		//
tikhomirov@379: 		f = new File("C:\\Mercurial\\Mercurial.ini");
tikhomirov@379: 		if (f.exists()) {
tikhomirov@379: 			return Collections.singletonList(f);
tikhomirov@379: 		}
tikhomirov@379: 		return Collections.emptyList();
tikhomirov@379: 	}
tikhomirov@379: 	
tikhomirov@379: 	private static List listConfigFiles(File dir) {
tikhomirov@379: 		assert dir.canRead();
tikhomirov@379: 		assert dir.isDirectory();
tikhomirov@379: 		final File[] allFiles = dir.listFiles();
tikhomirov@379: 		// File is Comparable, lexicographically by default
tikhomirov@379: 		Arrays.sort(allFiles);
tikhomirov@379: 		ArrayList rv = new ArrayList(allFiles.length);
tikhomirov@379: 		for (File f : allFiles) {
tikhomirov@379: 			if (f.getName().endsWith(".rc")) {
tikhomirov@379: 				rv.add(f);
tikhomirov@379: 			}
tikhomirov@379: 		}
tikhomirov@379: 		return rv;
tikhomirov@379: 	}
tikhomirov@379: 	
tikhomirov@382: 	public static File getInstallationConfigurationFileToWrite(SessionContext ctx) {
tikhomirov@382: 		File hgInstallRoot = findHgInstallRoot(ctx); // may be null
tikhomirov@379: 		// choice of which hgrc to pick here is according to my own pure discretion
tikhomirov@379: 		if (hgInstallRoot != null) {
tikhomirov@379: 			// use this location only if it's writable
tikhomirov@379: 			File cfg = new File(hgInstallRoot, runningOnWindows() ? "Mercurial.ini" : "etc/mercurial/hgrc");
tikhomirov@379: 			if (cfg.canWrite() || cfg.getParentFile().canWrite()) {
tikhomirov@379: 				return cfg;
tikhomirov@379: 			}
tikhomirov@379: 		}
tikhomirov@379: 		// fallback
tikhomirov@379: 		if (runningOnWindows()) {
tikhomirov@379: 			if (hgInstallRoot == null) {
tikhomirov@379: 				return new File("C:\\Mercurial\\Mercurial.ini");
tikhomirov@379: 			} else {
tikhomirov@379: 				// yes, we tried this file already (above) and found it non-writable
tikhomirov@379: 				// let caller fail with can't write
tikhomirov@379: 				return new File(hgInstallRoot, "Mercurial.ini");
tikhomirov@379: 			}
tikhomirov@379: 		} else {
tikhomirov@379: 			return new File("/etc/mercurial/hgrc");
tikhomirov@379: 		}
tikhomirov@378: 	}
tikhomirov@378: 
tikhomirov@382: 	public static File getUserConfigurationFileToWrite(SessionContext ctx) {
tikhomirov@379: 		LinkedHashSet locations = new LinkedHashSet();
tikhomirov@379: 		final boolean runsOnWindows = runningOnWindows();
tikhomirov@379: 		if (runsOnWindows) {
tikhomirov@379: 			locations.add(System.getenv("USERPROFILE"));
tikhomirov@378: 		}
tikhomirov@379: 		locations.add(System.getenv("HOME"));
tikhomirov@379: 		locations.remove(null);
tikhomirov@379: 		for (String loc : locations) {
tikhomirov@379: 			File location = new File(loc);
tikhomirov@379: 			File rv = new File(location, ".hgrc");
tikhomirov@379: 			if (rv.exists() && rv.canWrite()) {
tikhomirov@379: 				return rv;
tikhomirov@379: 			}
tikhomirov@379: 			if (runsOnWindows) {
tikhomirov@379: 				rv = new File(location, "Mercurial.ini");
tikhomirov@379: 				if (rv.exists() && rv.canWrite()) {
tikhomirov@379: 					return rv;
tikhomirov@379: 				}
tikhomirov@378: 			}
tikhomirov@378: 		}
tikhomirov@379: 		// fallback to default, let calling code fail with Exception if can't write
tikhomirov@379: 		return new File(System.getProperty("user.home"), ".hgrc");
tikhomirov@378: 	}
tikhomirov@591: 	
tikhomirov@591: 	public RevlogStream createManifestStream() {
tikhomirov@591: 		File manifestFile = getFileFromStoreDir("00manifest.i");
tikhomirov@591: 		return streamProvider.create(manifestFile);
tikhomirov@591: 	}
tikhomirov@388: 
tikhomirov@591: 	public RevlogStream createChangelogStream() {
tikhomirov@591: 		File chlogFile = getFileFromStoreDir("00changelog.i");
tikhomirov@591: 		return streamProvider.create(chlogFile);
tikhomirov@591: 	}
tikhomirov@591: 
tikhomirov@591: 	public RevlogStream resolveStoreFile(Path path) {
tikhomirov@621: 		return streamProvider.getStoreFile(path, false);
tikhomirov@591: 	}
tikhomirov@591: 
tikhomirov@526: 	// marker method
tikhomirov@526: 	public static IllegalStateException notImplemented() {
tikhomirov@526: 		return new IllegalStateException("Not implemented");
tikhomirov@526: 	}
tikhomirov@526: 
tikhomirov@525: 	public static Internals getInstance(HgRepository repo) {
tikhomirov@525: 		return HgInternals.getImplementationRepo(repo);
tikhomirov@525: 	}
tikhomirov@525: 	
tikhomirov@407: 	public static  CharSequence join(Iterable col, CharSequence separator) {
tikhomirov@407: 		if (col == null) {
tikhomirov@407: 			return String.valueOf(col);
tikhomirov@407: 		}
tikhomirov@407: 		Iterator it = col.iterator();
tikhomirov@407: 		if (!it.hasNext()) {
tikhomirov@407: 			return "[]";
tikhomirov@407: 		}
tikhomirov@407: 		String v = String.valueOf(it.next());
tikhomirov@407: 		StringBuilder sb = new StringBuilder(v);
tikhomirov@407: 		while (it.hasNext()) {
tikhomirov@407: 			sb.append(separator);
tikhomirov@407: 			v = String.valueOf(it.next());
tikhomirov@407: 			sb.append(v);
tikhomirov@407: 		}
tikhomirov@407: 		return sb;
tikhomirov@407: 	}
tikhomirov@420: 	
tikhomirov@420: 	/**
tikhomirov@420: 	 * keep an eye on all long to int downcasts to get a chance notice the lost of data
tikhomirov@420: 	 * Use if there's even subtle chance there might be loss
tikhomirov@420: 	 * (ok not to use if there's no way for l to be greater than int) 
tikhomirov@420: 	 */
tikhomirov@420: 	public static int ltoi(long l) {
tikhomirov@420: 		int i = (int) l;
tikhomirov@420: 		assert ((long) i) == l : "Loss of data!";
tikhomirov@420: 		return i;
tikhomirov@420: 	}
tikhomirov@591: 
tikhomirov@591: 	// access implementation details (fields, methods) of oth.repo package
tikhomirov@591: 	public interface ImplAccess {
tikhomirov@591: 		public RevlogStream getStream(HgDataFile df);
tikhomirov@591: 		public RevlogStream getManifestStream();
tikhomirov@591: 		public RevlogStream getChangelogStream();
tikhomirov@591: 	}
tikhomirov@74: }