view src/org/tmatesoft/hg/repo/HgBookmarks.java @ 628:6526d8adbc0f

Explicit HgRuntimeException to facilitate easy switch from runtime to checked exceptions
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 22 May 2013 15:52:31 +0200
parents 507602cb4fb3
children
line wrap: on
line source
/*
 * Copyright (c) 2012-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.util.LogFacility.Severity.Error;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import org.tmatesoft.hg.core.HgIOException;
import org.tmatesoft.hg.core.HgRepositoryLockException;
import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.internal.FileChangeMonitor;
import org.tmatesoft.hg.internal.Internals;
import org.tmatesoft.hg.internal.LineReader;
import org.tmatesoft.hg.util.LogFacility;

/**
 * Access to bookmarks state
 * 
 * @see http://mercurial.selenic.com/wiki/Bookmarks
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public final class HgBookmarks {
	private final Internals repo;
	private Map<String, Nodeid> bookmarks = Collections.emptyMap();
	private String activeBookmark;
	private FileChangeMonitor activeTracker, bmFileTracker;

	HgBookmarks(Internals internals) {
		repo = internals;
	}
	
	/*package-local*/ void read() throws HgRuntimeException {
		readBookmarks();
		readActiveBookmark();
	}
	
	private void readBookmarks() throws HgRuntimeException {
		final LogFacility log = repo.getLog();
		File all = repo.getRepositoryFile(HgRepositoryFiles.Bookmarks);
		try {
			LinkedHashMap<String, Nodeid> bm = new LinkedHashMap<String, Nodeid>();
			if (all.canRead() && all.isFile()) {
				LineReader lr1 = new LineReader(all, log);
				ArrayList<String> c = new ArrayList<String>();
				lr1.read(new LineReader.SimpleLineCollector(), c);
				for (String s : c) {
					int x = s.indexOf(' ');
					try {
						if (x > 0) {
							Nodeid nid = Nodeid.fromAscii(s.substring(0, x));
							String name = new String(s.substring(x+1));
							if (repo.getRepo().getChangelog().isKnown(nid)) {
								// copy name part not to drag complete line
								bm.put(name, nid);
							} else {
								log.dump(getClass(), LogFacility.Severity.Info, "Bookmark %s points to non-existent revision %s, ignored.", name, nid);
							}
						} else {
							log.dump(getClass(), LogFacility.Severity.Warn, "Can't parse bookmark entry: %s", s);
						}
					} catch (IllegalArgumentException ex) {
						log.dump(getClass(), LogFacility.Severity.Warn, ex, String.format("Can't parse bookmark entry: %s", s));
					}
				}
				bookmarks = bm;
			} else {
				bookmarks = Collections.emptyMap();
			}
			if (bmFileTracker == null) {
				bmFileTracker = new FileChangeMonitor(all);
			}
			bmFileTracker.touch(this);
		} catch (HgInvalidControlFileException ex) {
			// do not translate HgInvalidControlFileException into another HgInvalidControlFileException
			// but only HgInvalidFileException
			throw ex;
		} catch (HgIOException ex) {
			throw new HgInvalidControlFileException(ex, true);
		}
	}

	private void readActiveBookmark() throws HgInvalidControlFileException { 
		activeBookmark = null;
		File active = repo.getRepositoryFile(HgRepositoryFiles.BookmarksCurrent);
		try {
			if (active.canRead() && active.isFile()) {
				LineReader lr2 = new LineReader(active, repo.getLog());
				ArrayList<String> c = new ArrayList<String>(2);
				lr2.read(new LineReader.SimpleLineCollector(), c);
				if (c.size() > 0) {
					activeBookmark = c.get(0);
				}
			}
			if (activeTracker == null) {
				activeTracker = new FileChangeMonitor(active);
			}
			activeTracker.touch(this);
		} catch (HgIOException ex) {
			throw new HgInvalidControlFileException(ex, true);
		}
	}
	
	/*package-local*/void reloadIfChanged() throws HgRuntimeException {
		assert activeTracker != null;
		assert bmFileTracker != null;
		if (bmFileTracker.changed(this)) {
			readBookmarks();
		}
		if (activeTracker.changed(this)) {
			readActiveBookmark();
		}
	}


	/**
	 * Tell name of the active bookmark 
	 * @return <code>null</code> if none active
	 */
	public String getActiveBookmarkName() {
		return activeBookmark;
	}

	/**
	 * Retrieve revision associated with the named bookmark.
	 * 
	 * @param bookmarkName name of the bookmark
	 * @return revision or <code>null</code> if bookmark is not known
	 */
	public Nodeid getRevision(String bookmarkName) {
		return bookmarks.get(bookmarkName);
	}

	/**
	 * Retrieve all bookmarks known in the repository
	 * @return collection with names, never <code>null</code>
	 */
	public Collection<String> getAllBookmarks() {
		// bookmarks are initialized with atomic assignment,
		// hence can use view (not a synchronized copy) here
		return Collections.unmodifiableSet(bookmarks.keySet());
	}

	/**
	 * Update currently bookmark with new commit.
	 * Note, child has to be descendant of a p1 or p2
	 * 
	 * @param p1 first parent, or <code>null</code>
	 * @param p2 second parent, or <code>null</code>
	 * @param child new commit, descendant of one of the parents, not <code>null</code>
	 * @throws HgIOException if failed to write updated bookmark information 
	 * @throws HgRepositoryLockException  if failed to lock repository for modifications
	 */
	public void updateActive(Nodeid p1, Nodeid p2, Nodeid child) throws HgIOException, HgRepositoryLockException {
		if (activeBookmark == null) {
			return;
		}
		Nodeid activeRev = getRevision(activeBookmark);
		if (!activeRev.equals(p1) && !activeRev.equals(p2)) {
			return; // TestCommit#testNoBookmarkUpdate
		}
		if (child.equals(activeRev)) {
			return;
		}
		LinkedHashMap<String, Nodeid> copy = new LinkedHashMap<String, Nodeid>(bookmarks);
		copy.put(activeBookmark, child);
		bookmarks = copy;
		write();
	}
	
	private void write() throws HgIOException, HgRepositoryLockException {
		File bookmarksFile = repo.getRepositoryFile(HgRepositoryFiles.Bookmarks);
		HgRepositoryLock workingDirLock = repo.getRepo().getWorkingDirLock();
		FileWriter fileWriter = null;
		workingDirLock.acquire();
		try {
			fileWriter = new FileWriter(bookmarksFile);
			for (String bm : bookmarks.keySet()) {
				Nodeid nid = bookmarks.get(bm);
				fileWriter.write(String.format("%s %s\n", nid.toString(), bm));
			}
			fileWriter.flush();
		} catch (IOException ex) {
			throw new HgIOException("Failed to serialize bookmarks", ex, bookmarksFile);
		} finally {
			try {
				if (fileWriter != null) {
					fileWriter.close();
				}
			} catch (IOException ex) {
				repo.getLog().dump(getClass(), Error, ex, null);
			}
			workingDirLock.release();
		}
	}
}