view src/org/tmatesoft/hg/internal/SubrepoManager.java @ 596:43cfa08ff3fd

HgBlameFacility refactoring: extract code to build file history that spans renames
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 02 May 2013 19:23:53 +0200
parents e4ee4bf4c7d0
children
line wrap: on
line source
/*
 * Copyright (c) 2011-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.internal;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.repo.HgInternals;
import org.tmatesoft.hg.repo.HgInvalidControlFileException;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.repo.HgSubrepoLocation;
import org.tmatesoft.hg.util.Path;

/**
 * 
 * @see http://mercurial.selenic.com/wiki/SubrepoWork
 * @see http://mercurial.selenic.com/wiki/Subrepository
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class SubrepoManager /* XXX RepoChangeNotifier, RepoChangeListener */{

	private final HgRepository repo;
	private List<HgSubrepoLocation> subRepos;

	public SubrepoManager(HgRepository hgRepo) {
		assert hgRepo != null;
		repo = hgRepo;
	}

	private List<HgSubrepoLocation> readActualState() throws HgInvalidControlFileException {
		File hgsubFile = new File(repo.getWorkingDir(), ".hgsub");
		if (!hgsubFile.canRead()) {
			return Collections.emptyList();
		}
		Map<String, String> state; // path -> revision
		File hgstateFile = null;
		try {
			hgstateFile = new File(repo.getWorkingDir(), ".hgsubstate");
			if (hgstateFile.canRead()) {
				state = readState(new BufferedReader(new FileReader(hgstateFile)));
			} else {
				state = Collections.emptyMap();
			}
		} catch (IOException ex) {
			throw new HgInvalidControlFileException("Subrepo state read failed", ex, hgstateFile);
		}
		try {
			BufferedReader br = new BufferedReader(new FileReader(hgsubFile));
			return readConfig(br, state);
		} catch (IOException ex) {
			throw new HgInvalidControlFileException("Subrepo state read failed", ex, hgsubFile);
		}
	}

	private List<HgSubrepoLocation> readConfig(BufferedReader br, Map<String, String> substate) throws IOException {
		try {
			String line;
			LinkedList<HgSubrepoLocation> res = new LinkedList<HgSubrepoLocation>();
			HgInternals hgRepoInternal = new HgInternals(repo);
			final Path.Source pathFactory = repo.getSessionContext().getPathFactory();
			while ((line = br.readLine()) != null) {
				int sep = line.indexOf('=');
				if (sep == -1) {
					continue;
				}
				// since both key and value are referenced from HgSubrepoLocation, doesn't make sense
				// to have separate String instances (new String(line.substring()))
				String key = line.substring(0, sep).trim();
				String value = line.substring(sep + 1).trim();
				if (key.length() == 0 || value.length() == 0) {
					// XXX log bad line?
					continue;
				}
				HgSubrepoLocation.Kind kind = HgSubrepoLocation.Kind.Hg;
				int kindEnd = value.indexOf(']', 1);
				if (value.charAt(0) == '[' && kindEnd != -1) {
					String kindStr = value.substring(1, kindEnd);
					value = value.substring(kindEnd + 1);
					if ("svn".equals(kindStr)) {
						kind = HgSubrepoLocation.Kind.SVN;
					} else if ("git".equals(kindStr)) {
						kind = HgSubrepoLocation.Kind.Git;
					}
				}
				// TODO respect paths mappings in config file
				//
				// apparently, key value can't end with '/', `hg commit` fails if it does:
				// abort: path ends in directory separator: fourth/
				Path p = pathFactory.path(key.charAt(key.length()-1) == '/' ? key : key + '/');
				String revValue = substate.get(key);
				HgSubrepoLocation loc = hgRepoInternal.newSubrepo(p, value, kind, revValue == null ? null : Nodeid.fromAscii(revValue));
				res.add(loc);
			}
			return Arrays.asList(res.toArray(new HgSubrepoLocation[res.size()]));
		} finally {
			br.close();
		}
	}

	private Map<String, String> readState(BufferedReader br) throws IOException {
		// TODO reuse for other files with <revision><space><value> format, like .hgtags
		HashMap<String, String> rv = new HashMap<String, String>();
		try {
			String line;
			while ((line = br.readLine()) != null) {
				int sep = line.trim().indexOf(' ');
				if (sep != -1) {
					rv.put(line.substring(sep+1).trim(), line.substring(0, sep).trim());
				}
			}
		} finally {
			br.close();
		}
		return rv;
	}

	/*public to allow access from HgRepository, otherwise package-local*/
	public void read() throws HgInvalidControlFileException {
		subRepos = readActualState();
	}
	
	public List<HgSubrepoLocation> all(/*int revision, or TIP|WC*/) {
		assert subRepos != null;
		return subRepos;
	}
}