tikhomirov@10: /* tikhomirov@388: * Copyright (c) 2010-2012 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@0: */ tikhomirov@74: package org.tmatesoft.hg.repo; tikhomirov@74: tikhomirov@482: import static org.tmatesoft.hg.repo.HgRepositoryFiles.*; tikhomirov@456: import static org.tmatesoft.hg.util.LogFacility.Severity.*; tikhomirov@456: tikhomirov@74: import java.io.File; tikhomirov@481: import java.io.FileReader; tikhomirov@74: import java.io.IOException; tikhomirov@234: import java.io.StringReader; tikhomirov@74: import java.lang.ref.SoftReference; tikhomirov@481: import java.nio.CharBuffer; tikhomirov@114: import java.util.ArrayList; tikhomirov@114: import java.util.Collections; tikhomirov@74: import java.util.HashMap; tikhomirov@114: import java.util.List; tikhomirov@0: tikhomirov@235: import org.tmatesoft.hg.core.Nodeid; tikhomirov@295: import org.tmatesoft.hg.core.SessionContext; tikhomirov@234: import org.tmatesoft.hg.internal.ByteArrayChannel; tikhomirov@114: import org.tmatesoft.hg.internal.ConfigFile; tikhomirov@74: import org.tmatesoft.hg.internal.DataAccessProvider; tikhomirov@486: import org.tmatesoft.hg.internal.Experimental; tikhomirov@114: import org.tmatesoft.hg.internal.Filter; tikhomirov@407: import org.tmatesoft.hg.internal.Internals; tikhomirov@486: import org.tmatesoft.hg.internal.Lock; tikhomirov@77: import org.tmatesoft.hg.internal.RevlogStream; tikhomirov@239: import org.tmatesoft.hg.internal.SubrepoManager; tikhomirov@234: import org.tmatesoft.hg.util.CancelledException; tikhomirov@235: import org.tmatesoft.hg.util.Pair; tikhomirov@133: import org.tmatesoft.hg.util.Path; tikhomirov@64: import org.tmatesoft.hg.util.PathRewrite; tikhomirov@220: import org.tmatesoft.hg.util.ProgressSupport; tikhomirov@64: tikhomirov@1: tikhomirov@74: tikhomirov@0: /** tikhomirov@64: * Shall be as state-less as possible, all the caching happens outside the repo, in commands/walkers tikhomirov@74: * tikhomirov@74: * @author Artem Tikhomirov tikhomirov@74: * @author TMate Software Ltd. tikhomirov@0: */ tikhomirov@74: public final class HgRepository { tikhomirov@0: tikhomirov@405: // IMPORTANT: if new constants added, consider fixing HgInternals#wrongRevisionIndex and HgInvalidRevisionException#getMessage tikhomirov@405: tikhomirov@405: /** tikhomirov@405: * Revision index constant to indicate most recent revision tikhomirov@405: */ tikhomirov@405: public static final int TIP = -3; // XXX TIP_REVISION? tikhomirov@405: tikhomirov@405: /** tikhomirov@405: * Revision index constant to indicate invalid revision index value. tikhomirov@405: * Primary use is default/uninitialized values where user input is expected and as return value where tikhomirov@405: * an exception (e.g. {@link HgInvalidRevisionException}) is not desired tikhomirov@405: */ tikhomirov@403: public static final int BAD_REVISION = Integer.MIN_VALUE; // XXX INVALID_REVISION? tikhomirov@405: tikhomirov@405: /** tikhomirov@405: * Revision index constant to indicate working copy tikhomirov@405: */ tikhomirov@405: public static final int WORKING_COPY = -2; // XXX WORKING_COPY_REVISION? tikhomirov@252: tikhomirov@405: /** tikhomirov@423: * Constant ({@value #NO_REVISION}) to indicate revision absence or a fictitious revision of an empty repository. tikhomirov@423: * tikhomirov@423: *
Revision absence is vital e.g. for missing parent from {@link HgChangelog#parents(int, int[], byte[], byte[])} call and tikhomirov@423: * to report cases when changeset records no corresponding manifest tikhomirov@423: * revision {@link HgManifest#walk(int, int, org.tmatesoft.hg.repo.HgManifest.Inspector)}. tikhomirov@423: * tikhomirov@423: *
Use as imaginary revision/empty repository is handy as an argument (contrary to {@link #BAD_REVISION})
tikhomirov@423: * e.g in a status operation to visit changes from the very beginning of a repository.
tikhomirov@405: */
tikhomirov@405: public static final int NO_REVISION = -1;
tikhomirov@405:
tikhomirov@405: /**
tikhomirov@405: * Name of the primary branch, "default".
tikhomirov@405: */
tikhomirov@252: public static final String DEFAULT_BRANCH_NAME = "default";
tikhomirov@5:
tikhomirov@2: // temp aux marker method
tikhomirov@2: public static IllegalStateException notImplemented() {
tikhomirov@2: return new IllegalStateException("Not implemented");
tikhomirov@2: }
tikhomirov@148:
tikhomirov@74: private final File repoDir; // .hg folder
tikhomirov@237: private final File workingDir; // .hg/../
tikhomirov@74: private final String repoLocation;
tikhomirov@74: private final DataAccessProvider dataAccess;
tikhomirov@445: private final PathRewrite normalizePath; // normalized slashes but otherwise regular file names
tikhomirov@445: private final PathRewrite dataPathHelper; // access to file storage area (usually under .hg/store/data/), with filenames mangled
tikhomirov@445: private final PathRewrite repoPathHelper; // access to system files
tikhomirov@295: private final SessionContext sessionContext;
tikhomirov@74:
tikhomirov@97: private HgChangelog changelog;
tikhomirov@2: private HgManifest manifest;
tikhomirov@50: private HgTags tags;
tikhomirov@220: private HgBranches branches;
tikhomirov@231: private HgMergeState mergeState;
tikhomirov@239: private SubrepoManager subRepos;
tikhomirov@484: private HgBookmarks bookmarks;
tikhomirov@220:
tikhomirov@74: // XXX perhaps, shall enable caching explicitly
tikhomirov@74: private final HashMapnull
tikhomirov@482: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception
tikhomirov@482: */
tikhomirov@366: public HgBranches getBranches() throws HgInvalidControlFileException {
tikhomirov@220: if (branches == null) {
tikhomirov@220: branches = new HgBranches(this);
tikhomirov@220: branches.collect(ProgressSupport.Factory.get(null));
tikhomirov@220: }
tikhomirov@220: return branches;
tikhomirov@220: }
tikhomirov@231:
tikhomirov@484: /**
tikhomirov@484: * Access state of the recent merge
tikhomirov@484: * @return merge state facility, never null
tikhomirov@484: */
tikhomirov@231: public HgMergeState getMergeState() {
tikhomirov@231: if (mergeState == null) {
tikhomirov@231: mergeState = new HgMergeState(this);
tikhomirov@231: }
tikhomirov@231: return mergeState;
tikhomirov@231: }
tikhomirov@220:
tikhomirov@74: public HgDataFile getFileNode(String path) {
tikhomirov@292: CharSequence nPath = normalizePath.rewrite(path);
tikhomirov@292: CharSequence storagePath = dataPathHelper.rewrite(nPath);
tikhomirov@202: RevlogStream content = resolve(Path.create(storagePath), false);
tikhomirov@115: Path p = Path.create(nPath);
tikhomirov@115: if (content == null) {
tikhomirov@115: return new HgDataFile(this, p);
tikhomirov@115: }
tikhomirov@115: return new HgDataFile(this, p, content);
tikhomirov@74: }
tikhomirov@1:
tikhomirov@74: public HgDataFile getFileNode(Path path) {
tikhomirov@292: CharSequence storagePath = dataPathHelper.rewrite(path.toString());
tikhomirov@202: RevlogStream content = resolve(Path.create(storagePath), false);
tikhomirov@115: // XXX no content when no file? or HgDataFile.exists() to detect that?
tikhomirov@115: if (content == null) {
tikhomirov@115: return new HgDataFile(this, path);
tikhomirov@115: }
tikhomirov@74: return new HgDataFile(this, path, content);
tikhomirov@74: }
tikhomirov@2:
tikhomirov@142: /* clients need to rewrite path from their FS to a repository-friendly paths, and, perhaps, vice versa*/
tikhomirov@142: public PathRewrite getToRepoPathHelper() {
tikhomirov@74: return normalizePath;
tikhomirov@74: }
tikhomirov@284:
tikhomirov@284: /**
tikhomirov@348: * @return pair of values, {@link Pair#first()} and {@link Pair#second()} are respective parents, never null
.
tikhomirov@348: * @throws HgInvalidControlFileException if attempt to read information about working copy parents from dirstate failed
tikhomirov@284: */
tikhomirov@348: public Pairnull
.
tikhomirov@348: * @throws HgInvalidControlFileException if attempt to read branch name failed.
tikhomirov@252: */
tikhomirov@348: public String getWorkingCopyBranchName() throws HgInvalidControlFileException {
tikhomirov@430: if (wcBranch == null) {
tikhomirov@430: wcBranch = HgDirstate.readBranch(this, new File(repoDir, "branch"));
tikhomirov@430: }
tikhomirov@430: return wcBranch;
tikhomirov@252: }
tikhomirov@2:
tikhomirov@237: /**
tikhomirov@237: * @return location where user files (shall) reside
tikhomirov@237: */
tikhomirov@237: public File getWorkingDir() {
tikhomirov@237: return workingDir;
tikhomirov@237: }
tikhomirov@239:
tikhomirov@239: /**
tikhomirov@239: * Provides access to sub-repositories defined in this repository. Enumerated sub-repositories are those directly
tikhomirov@239: * known, not recursive collection of all nested sub-repositories.
tikhomirov@239: * @return list of all known sub-repositories in this repository, or empty list if none found.
tikhomirov@482: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception
tikhomirov@239: */
tikhomirov@348: public Listnull
if none
tikhomirov@482: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception
tikhomirov@481: */
tikhomirov@482: public String getCommitLastMessage() throws HgInvalidControlFileException {
tikhomirov@482: File lastMessage = new File(repoDir, LastMessage.getPath());
tikhomirov@481: if (!lastMessage.canRead()) {
tikhomirov@481: return null;
tikhomirov@481: }
tikhomirov@481: FileReader fr = null;
tikhomirov@481: try {
tikhomirov@481: fr = new FileReader(lastMessage);
tikhomirov@481: CharBuffer cb = CharBuffer.allocate(Internals.ltoi(lastMessage.length()));
tikhomirov@481: fr.read(cb);
tikhomirov@481: return cb.flip().toString();
tikhomirov@481: } catch (IOException ex) {
tikhomirov@481: throw new HgInvalidControlFileException("Can't retrieve message of last commit attempt", ex, lastMessage);
tikhomirov@481: } finally {
tikhomirov@481: if (fr != null) {
tikhomirov@481: try {
tikhomirov@481: fr.close();
tikhomirov@481: } catch (IOException ex) {
tikhomirov@481: getContext().getLog().dump(getClass(), Warn, "Failed to close %s after read", lastMessage);
tikhomirov@481: }
tikhomirov@481: }
tikhomirov@481: }
tikhomirov@481: }
tikhomirov@486:
tikhomirov@486: private Lock wdLock, storeLock;
tikhomirov@486:
tikhomirov@486: /**
tikhomirov@486: * PROVISIONAL CODE, DO NOT USE
tikhomirov@486: *
tikhomirov@486: * Access repository lock that covers non-store parts of the repository (dirstate, branches, etc -
tikhomirov@486: * everything that has to do with working directory state).
tikhomirov@486: *
tikhomirov@486: * Note, the lock object returned merely gives access to lock mechanism. NO ACTUAL LOCKING IS DONE.
tikhomirov@486: * Use {@link Lock#acquire()} to actually lock the repository.
tikhomirov@486: *
tikhomirov@486: * @return lock object, never null
tikhomirov@486: */
tikhomirov@486: @Experimental(reason="WORK IN PROGRESS")
tikhomirov@486: public Lock getWorkingDirLock() {
tikhomirov@486: if (wdLock == null) {
tikhomirov@486: synchronized (this) {
tikhomirov@486: if (wdLock == null) {
tikhomirov@486: wdLock = new Lock(new File(repoPathHelper.rewrite("wlock").toString()));
tikhomirov@486: }
tikhomirov@486: }
tikhomirov@486: }
tikhomirov@486: return wdLock;
tikhomirov@486: }
tikhomirov@486:
tikhomirov@486: @Experimental(reason="WORK IN PROGRESS")
tikhomirov@486: public Lock getStoreLock() {
tikhomirov@486: if (storeLock == null) {
tikhomirov@486: synchronized (this) {
tikhomirov@486: if (storeLock == null) {
tikhomirov@486: storeLock = new Lock(new File(repoPathHelper.rewrite("store/lock").toString()));
tikhomirov@486: }
tikhomirov@486: }
tikhomirov@486: }
tikhomirov@486: return storeLock;
tikhomirov@486: }
tikhomirov@486:
tikhomirov@484: /**
tikhomirov@484: * Access bookmarks-related functionality
tikhomirov@484: * @return facility to manage bookmarks, never null
tikhomirov@484: * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception
tikhomirov@484: */
tikhomirov@484: public HgBookmarks getBookmarks() throws HgInvalidControlFileException {
tikhomirov@484: if (bookmarks == null) {
tikhomirov@484: bookmarks = new HgBookmarks(this);
tikhomirov@484: bookmarks.read();
tikhomirov@484: }
tikhomirov@484: return bookmarks;
tikhomirov@484: }
tikhomirov@74:
tikhomirov@74: /*package-local*/ DataAccessProvider getDataAccess() {
tikhomirov@74: return dataAccess;
tikhomirov@74: }
tikhomirov@74:
tikhomirov@2: /**
tikhomirov@2: * Perhaps, should be separate interface, like ContentLookup
tikhomirov@74: * path - repository storage path (i.e. one usually with .i or .d)
tikhomirov@2: */
tikhomirov@202: /*package-local*/ RevlogStream resolve(Path path, boolean shallFakeNonExistent) {
tikhomirov@74: final SoftReference