view src/org/tmatesoft/hg/repo/HgBookmarks.java @ 709:497e697636fc

Report merged lines as changed block if possible, not as a sequence of added/deleted blocks. To facilitate access to merge parent lines AddBlock got mergeLineAt() method that reports index of the line in the second parent (if any), while insertedAt() has been changed to report index in the first parent always
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 21 Aug 2013 16:23:27 +0200
parents 6526d8adbc0f
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();
		}
	}
}