changeset 231:1792b37650f2

Introduced access to conflict resolution information (merge state)
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 01 Jun 2011 05:44:25 +0200
parents 0dd9da7489dc
children b7347daa50e3
files cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/core/HgChangeset.java src/org/tmatesoft/hg/core/HgFileRevision.java src/org/tmatesoft/hg/core/HgLogCommand.java src/org/tmatesoft/hg/core/HgManifestCommand.java src/org/tmatesoft/hg/repo/HgDirstate.java src/org/tmatesoft/hg/repo/HgMergeState.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/util/PathPool.java
diffstat 10 files changed, 326 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java	Tue May 31 05:33:16 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/ChangesetDumpHandler.java	Wed Jun 01 05:44:25 2011 +0200
@@ -22,8 +22,8 @@
 
 import org.tmatesoft.hg.core.HgChangeset;
 import org.tmatesoft.hg.core.HgChangesetHandler;
+import org.tmatesoft.hg.core.HgFileRevision;
 import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Path;
 
@@ -122,7 +122,7 @@
 		if (complete) {
 			if (!cset.getModifiedFiles().isEmpty()) {
 				sb.append("files:      ");
-				for (FileRevision s : cset.getModifiedFiles()) {
+				for (HgFileRevision s : cset.getModifiedFiles()) {
 					sb.append(' ');
 					sb.append(s.getPath());
 				}
@@ -130,7 +130,7 @@
 			}
 			if (!cset.getAddedFiles().isEmpty()) {
 				sb.append("files+:     ");
-				for (FileRevision s : cset.getAddedFiles()) {
+				for (HgFileRevision s : cset.getAddedFiles()) {
 					sb.append(' ');
 					sb.append(s.getPath());
 				}
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Tue May 31 05:33:16 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Wed Jun 01 05:44:25 2011 +0200
@@ -24,6 +24,7 @@
 import java.util.Map;
 
 import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.core.HgFileRevision;
 import org.tmatesoft.hg.core.HgManifestCommand;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
@@ -33,6 +34,7 @@
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgManifest;
+import org.tmatesoft.hg.repo.HgMergeState;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.repo.HgStatusInspector;
@@ -62,7 +64,8 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
-		m.testFileStatus();
+		m.testMergeState();
+//		m.testFileStatus();
 //		m.dumpBranches();
 //		m.inflaterLengthException();
 //		m.dumpIgnored();
@@ -74,28 +77,31 @@
 //		m.bunchOfTests();
 	}
 	
+	private void testMergeState() throws Exception {
+		final HgMergeState mergeState = hgRepo.getMergeState();
+		mergeState.refresh();
+		for (HgMergeState.Entry e : mergeState.getConflicts()) {
+			System.out.println(e.getState() + " " + e.getActualFile());
+			System.out.println("p1:       " + formatFileRevision(e.getFirstParent()));
+			System.out.println("p2:       " + formatFileRevision(e.getSecondParent()));
+			System.out.println("ancestor: " + formatFileRevision(e.getCommonAncestor()));
+			System.out.println();
+		}
+	}
+	
+	private static String formatFileRevision(HgFileRevision r) throws Exception {
+		final ByteArrayChannel sink = new ByteArrayChannel();
+		r.putContentTo(sink);
+		return String.format("%s %s (%d bytes)", r.getPath(), r.getRevision(), sink.toArray().length);
+	}
+	
 	private void testFileStatus() {
 //		final Path path = Path.create("src/org/tmatesoft/hg/util/");
 //		final Path path = Path.create("src/org/tmatesoft/hg/internal/Experimental.java");
-//		final Path path = Path.create("dir/file3");
+//		final Path path = Path.create("missing-dir/");
 //		HgWorkingCopyStatusCollector wcsc = HgWorkingCopyStatusCollector.create(hgRepo, path);
-		HgWorkingCopyStatusCollector wcsc = HgWorkingCopyStatusCollector.create(hgRepo, new PathGlobMatcher("*"));
+		HgWorkingCopyStatusCollector wcsc = HgWorkingCopyStatusCollector.create(hgRepo, new PathGlobMatcher("missing-dir/**/*"));
 		wcsc.walk(TIP, new StatusDump());
-		new HgManifestCommand(hgRepo).dirs(true).revision(TIP).execute(new HgManifestCommand.Handler() {
-			
-			public void file(FileRevision fileRevision) {
-			}
-			
-			public void end(Nodeid manifestRevision) {
-			}
-			
-			public void dir(Path p) {
-				System.out.println(p);
-			}
-			
-			public void begin(Nodeid manifestRevision) {
-			}
-		});
 	}
 	
 	private void dumpBranches() {
--- a/src/org/tmatesoft/hg/core/HgChangeset.java	Tue May 31 05:33:16 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgChangeset.java	Wed Jun 01 05:44:25 2011 +0200
@@ -22,7 +22,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgRepository;
@@ -49,7 +48,7 @@
 	private Nodeid nodeid;
 
 	//
-	private List<FileRevision> modifiedFiles, addedFiles;
+	private List<HgFileRevision> modifiedFiles, addedFiles;
 	private List<Path> deletedFiles;
 	private int revNumber;
 	private byte[] parent1, parent2;
@@ -118,14 +117,14 @@
 		return rv;
 	}
 
-	public List<FileRevision> getModifiedFiles() {
+	public List<HgFileRevision> getModifiedFiles() {
 		if (modifiedFiles == null) {
 			initFileChanges();
 		}
 		return modifiedFiles;
 	}
 
-	public List<FileRevision> getAddedFiles() {
+	public List<HgFileRevision> getAddedFiles() {
 		if (addedFiles == null) {
 			initFileChanges();
 		}
@@ -182,8 +181,8 @@
 
 	private /*synchronized*/ void initFileChanges() {
 		ArrayList<Path> deleted = new ArrayList<Path>();
-		ArrayList<FileRevision> modified = new ArrayList<FileRevision>();
-		ArrayList<FileRevision> added = new ArrayList<FileRevision>();
+		ArrayList<HgFileRevision> modified = new ArrayList<HgFileRevision>();
+		ArrayList<HgFileRevision> added = new ArrayList<HgFileRevision>();
 		HgStatusCollector.Record r = new HgStatusCollector.Record();
 		statusHelper.change(revNumber, r);
 		final HgRepository repo = statusHelper.getRepo();
@@ -192,14 +191,14 @@
 			if (nid == null) {
 				throw new HgBadStateException();
 			}
-			modified.add(new FileRevision(repo, nid, s));
+			modified.add(new HgFileRevision(repo, nid, s));
 		}
 		for (Path s : r.getAdded()) {
 			Nodeid nid = r.nodeidAfterChange(s);
 			if (nid == null) {
 				throw new HgBadStateException();
 			}
-			added.add(new FileRevision(repo, nid, s));
+			added.add(new HgFileRevision(repo, nid, s));
 		}
 		for (Path s : r.getRemoved()) {
 			// with Path from getRemoved, may just copy
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgFileRevision.java	Wed Jun 01 05:44:25 2011 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2011 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.core;
+
+import java.io.IOException;
+
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.ByteChannel;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Keeps together information about specific file revision
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class HgFileRevision implements HgLogCommand.FileRevision {
+	private final HgRepository repo;
+	private final Nodeid revision;
+	private final Path path;
+	
+	public HgFileRevision(HgRepository hgRepo, Nodeid rev, Path p) {
+		if (hgRepo == null || rev == null || p == null) {
+			// since it's package local, it is our code to blame for non validated arguments
+			throw new HgBadStateException();
+		}
+		repo = hgRepo;
+		revision = rev;
+		path = p;
+	}
+	
+	public Path getPath() {
+		return path;
+	}
+	public Nodeid getRevision() {
+		return revision;
+	}
+	public void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
+		HgDataFile fn = repo.getFileNode(path);
+		int localRevision = fn.getLocalRevision(revision);
+		fn.contentWithFilters(localRevision, sink);
+	}
+
+}
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java	Tue May 31 05:33:16 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgLogCommand.java	Wed Jun 01 05:44:25 2011 +0200
@@ -215,8 +215,8 @@
 					// even if we do not follow history, report file rename
 					do {
 						if (handler instanceof FileHistoryHandler) {
-							FileRevision src = new FileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName());
-							FileRevision dst = new FileRevision(repo, fileNode.getRevision(0), fileNode.getPath());
+							HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), fileNode.getCopySourceName());
+							HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), fileNode.getPath());
 							try {
 								((FileHistoryHandler) handler).copy(src, dst);
 							} catch (RuntimeException ex) {
@@ -315,31 +315,13 @@
 		}
 	}
 
-	public static final class FileRevision {
-		private final HgRepository repo;
-		private final Nodeid revision;
-		private final Path path;
-		
-		/*package-local*/FileRevision(HgRepository hgRepo, Nodeid rev, Path p) {
-			if (hgRepo == null || rev == null || p == null) {
-				// since it's package local, it is our code to blame for non validated arguments
-				throw new HgBadStateException();
-			}
-			repo = hgRepo;
-			revision = rev;
-			path = p;
-		}
-		
-		public Path getPath() {
-			return path;
-		}
-		public Nodeid getRevision() {
-			return revision;
-		}
-		public void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
-			HgDataFile fn = repo.getFileNode(path);
-			int localRevision = fn.getLocalRevision(revision);
-			fn.contentWithFilters(localRevision, sink);
-		}
+	/**
+	 * @deprecated pulled up, use {@link HgFileRevision} instead.
+	 */
+	@Deprecated
+	public interface FileRevision {
+		public abstract Path getPath();
+		public abstract Nodeid getRevision();
+		public abstract void putContentTo(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException;
 	}
 }
--- a/src/org/tmatesoft/hg/core/HgManifestCommand.java	Tue May 31 05:33:16 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgManifestCommand.java	Wed Jun 01 05:44:25 2011 +0200
@@ -25,7 +25,6 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
 import org.tmatesoft.hg.repo.HgManifest;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Path;
@@ -124,7 +123,7 @@
 	public interface Handler {
 		void begin(Nodeid manifestRevision);
 		void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs
-		void file(FileRevision fileRevision); // XXX allow to check p is invalid (df.exists())
+		void file(HgLogCommand.FileRevision fileRevision); // XXX allow to check p is invalid (df.exists())
 		void end(Nodeid manifestRevision);
 	}
 
@@ -134,7 +133,7 @@
 		// However, once HgManifest.Inspector switches to Path objects, perhaps global Path pool
 		// might be more effective?
 		private PathPool pathPool;
-		private List<FileRevision> manifestContent;
+		private List<HgFileRevision> manifestContent;
 		private Nodeid manifestNodeid;
 		
 		public void start() {
@@ -149,27 +148,27 @@
 	
 		public boolean begin(int manifestRevision, Nodeid nid, int changelogRevision) {
 			if (needDirs && manifestContent == null) {
-				manifestContent = new LinkedList<FileRevision>();
+				manifestContent = new LinkedList<HgFileRevision>();
 			}
 			visitor.begin(manifestNodeid = nid);
 			return true;
 		}
 		public boolean end(int revision) {
 			if (needDirs) {
-				LinkedHashMap<Path, LinkedList<FileRevision>> breakDown = new LinkedHashMap<Path, LinkedList<FileRevision>>();
-				for (FileRevision fr : manifestContent) {
+				LinkedHashMap<Path, LinkedList<HgFileRevision>> breakDown = new LinkedHashMap<Path, LinkedList<HgFileRevision>>();
+				for (HgFileRevision fr : manifestContent) {
 					Path filePath = fr.getPath();
 					Path dirPath = pathPool.parent(filePath);
-					LinkedList<FileRevision> revs = breakDown.get(dirPath);
+					LinkedList<HgFileRevision> revs = breakDown.get(dirPath);
 					if (revs == null) {
-						revs = new LinkedList<FileRevision>();
+						revs = new LinkedList<HgFileRevision>();
 						breakDown.put(dirPath, revs);
 					}
 					revs.addLast(fr);
 				}
 				for (Path dir : breakDown.keySet()) {
 					visitor.dir(dir);
-					for (FileRevision fr : breakDown.get(dir)) {
+					for (HgFileRevision fr : breakDown.get(dir)) {
 						visitor.file(fr);
 					}
 				}
@@ -184,7 +183,7 @@
 			if (matcher != null && !matcher.accept(p)) {
 				return true;
 			}
-			FileRevision fr = new FileRevision(repo, nid, p);
+			HgFileRevision fr = new HgFileRevision(repo, nid, p);
 			if (needDirs) {
 				manifestContent.add(fr);
 			} else {
--- a/src/org/tmatesoft/hg/repo/HgDirstate.java	Tue May 31 05:33:16 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgDirstate.java	Wed Jun 01 05:44:25 2011 +0200
@@ -23,6 +23,8 @@
 import java.util.Map;
 import java.util.TreeSet;
 
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.DataAccessProvider;
 import org.tmatesoft.hg.util.Path;
@@ -44,6 +46,7 @@
 	private Map<String, Record> added;
 	private Map<String, Record> removed;
 	private Map<String, Record> merged;
+	private Nodeid p1, p2;
 
 	/*package-local*/ HgDirstate() {
 		// empty instance
@@ -71,9 +74,10 @@
 		removed = new LinkedHashMap<String, Record>();
 		merged = new LinkedHashMap<String, Record>();
 		try {
-			// XXX skip(40) if we don't need these? 
 			byte[] parents = new byte[40];
 			da.readBytes(parents, 0, 40);
+			p1 = Nodeid.fromBinary(parents, 0);
+			p2 = Nodeid.fromBinary(parents, 20);
 			parents = null;
 			// hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only
 			while (!da.isEmpty()) {
@@ -115,6 +119,41 @@
 		}
 	}
 
+	// do not read whole dirstate if all we need is WC parent information
+	private void readParents() {
+		if (dirstateFile == null || !dirstateFile.exists()) {
+			return;
+		}
+		DataAccess da = accessProvider.create(dirstateFile);
+		if (da.isEmpty()) {
+			return;
+		}
+		try {
+			byte[] parents = new byte[40];
+			da.readBytes(parents, 0, 40);
+			p1 = Nodeid.fromBinary(parents, 0);
+			p2 = Nodeid.fromBinary(parents, 20);
+			parents = null;
+		} catch (IOException ex) {
+			throw new HgBadStateException(ex); // XXX in fact, our exception is not the best solution here.
+		} finally {
+			da.done();
+		}
+	}
+	
+	/**
+	 * @return array of length 2 with working copy parents, non null.
+	 */
+	public Nodeid[] parents() {
+		if (p1 == null) {
+			readParents();
+		}
+		Nodeid[] rv = new Nodeid[2];
+		rv[0] = p1;
+		rv[1] = p2;
+		return rv;
+	}
+
 	// new, modifiable collection
 	/*package-local*/ TreeSet<String> all() {
 		read();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgMergeState.java	Wed Jun 01 05:44:25 2011 +0200
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2011 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 java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgFileRevision;
+import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.Pool;
+import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector;
+import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.PathPool;
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgMergeState {
+	private Nodeid wcp1, wcp2;
+	
+	public enum Kind {
+		Resolved, Unresolved;
+	}
+	
+	public static class Entry {
+		private final Kind state;
+		private final HgFileRevision parent1;
+		private final HgFileRevision parent2;
+		private final HgFileRevision ancestor;
+		private final Path wcFile;
+
+		/*package-local*/Entry(Kind s, Path actualCopy, HgFileRevision p1, HgFileRevision p2, HgFileRevision ca) {
+			if (p1 == null || p2 == null || ca == null || actualCopy == null) {
+				throw new IllegalArgumentException();
+			}
+			state = s;
+			wcFile = actualCopy;
+			parent1 = p1;
+			parent2 = p2;
+			ancestor = ca;
+		}
+		
+		public Kind getState() {
+			return state;
+		}
+		public Path getActualFile() {
+			return wcFile;
+		}
+		public HgFileRevision getFirstParent() {
+			return parent1;
+		}
+		public HgFileRevision getSecondParent() {
+			return parent2;
+		}
+		public HgFileRevision getCommonAncestor() {
+			return ancestor;
+		}
+	}
+
+	private final HgRepository repo;
+	private Entry[] entries;
+
+	HgMergeState(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	public void refresh() throws IOException/*XXX it's unlikely caller can do anything reasonable about IOException */ {
+		entries = null;
+		final File f = new File(repo.getRepositoryRoot(), "merge/state");
+		if (!f.canRead()) {
+			// empty state
+			return;
+		}
+		Nodeid[] wcParents = repo.loadDirstate().parents();
+		wcp1 = wcParents[0]; wcp2 = wcParents[1];
+		ArrayList<Entry> result = new ArrayList<Entry>();
+		PathPool pathPool = new PathPool(new PathRewrite.Empty());
+		Pool<Nodeid> nodeidPool = new Pool<Nodeid>();
+		Pool<String> fnamePool = new Pool<String>();
+		final ManifestRevisionInspector m1 = new ManifestRevisionInspector(nodeidPool, fnamePool);
+		final ManifestRevisionInspector m2 = new ManifestRevisionInspector(nodeidPool, fnamePool);
+		final int rp1 = repo.getChangelog().getLocalRevision(wcp1);
+		final int rp2 = repo.getChangelog().getLocalRevision(wcp2);
+		repo.getManifest().walk(rp1, rp1, m1);
+		repo.getManifest().walk(rp2, rp2, m2);
+		BufferedReader br = new BufferedReader(new FileReader(f));
+		String s = br.readLine();
+		Nodeid n = Nodeid.fromAscii(s);
+		if (!wcp1.equals(n)) {
+			throw new AssertionError("I assume merge/state records revision of the wc we merge into");
+		}
+		while ((s = br.readLine()) != null) {
+			String[] r = s.split("\\00");
+			HgFileRevision p1 = new HgFileRevision(repo, m1.nodeid(r[3]), pathPool.path(r[3]));
+			HgFileRevision ca = new HgFileRevision(repo, Nodeid.fromAscii(r[5]), pathPool.path(r[4]));
+			HgFileRevision p2 = new HgFileRevision(repo, m2.nodeid(r[6]), pathPool.path(r[6]));
+			final Kind k;
+			if ("u".equals(r[1])) {
+				k = Kind.Unresolved;
+			} else if ("r".equals(r[1])) {
+				k = Kind.Resolved;
+			} else {
+				throw new HgBadStateException(r[1]);
+			}
+			Entry e = new Entry(k, pathPool.path(r[0]), p1, p2, ca);
+			result.add(e);
+		}
+		entries = result.toArray(new Entry[result.size()]);
+		br.close();
+		pathPool.clear();
+	}
+	
+	public Nodeid getFirstParent() {
+		if (wcp1 == null) {
+			throw new HgBadStateException("Call #refresh() first");
+		}
+		return wcp1;
+	}
+	
+	public Nodeid getSecondParent() {
+		if (wcp2 == null) {
+			throw new HgBadStateException("Call #refresh() first");
+		}
+		return wcp2;
+	}
+	
+	public List<Entry> getConflicts() {
+		return entries == null ? Collections.<Entry>emptyList() : Arrays.asList(entries);
+	}
+}
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Tue May 31 05:33:16 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Wed Jun 01 05:44:25 2011 +0200
@@ -26,6 +26,7 @@
 
 import org.tmatesoft.hg.internal.ConfigFile;
 import org.tmatesoft.hg.internal.DataAccessProvider;
+import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.Filter;
 import org.tmatesoft.hg.internal.RequiresFile;
 import org.tmatesoft.hg.internal.RevlogStream;
@@ -64,6 +65,7 @@
 	private HgManifest manifest;
 	private HgTags tags;
 	private HgBranches branches;
+	private HgMergeState mergeState;
 
 	// XXX perhaps, shall enable caching explicitly
 	private final HashMap<Path, SoftReference<RevlogStream>> streamsCache = new HashMap<Path, SoftReference<RevlogStream>>();
@@ -138,7 +140,7 @@
 		return this.manifest;
 	}
 	
-	public final HgTags getTags() {
+	public HgTags getTags() {
 		if (tags == null) {
 			tags = new HgTags();
 			try {
@@ -151,13 +153,21 @@
 		return tags;
 	}
 	
-	public final HgBranches getBranches() {
+	public HgBranches getBranches() {
 		if (branches == null) {
 			branches = new HgBranches(this);
 			branches.collect(ProgressSupport.Factory.get(null));
 		}
 		return branches;
 	}
+
+	@Experimental(reason="Perhaps, shall not cache instance, and provide loadMergeState as it may change often")
+	public HgMergeState getMergeState() {
+		if (mergeState == null) {
+			mergeState = new HgMergeState(this);
+		}
+		return mergeState;
+	}
 	
 	public HgDataFile getFileNode(String path) {
 		String nPath = normalizePath.rewrite(path);
--- a/src/org/tmatesoft/hg/util/PathPool.java	Tue May 31 05:33:16 2011 +0200
+++ b/src/org/tmatesoft/hg/util/PathPool.java	Wed Jun 01 05:44:25 2011 +0200
@@ -41,6 +41,7 @@
 	}
 
 	// pipes path object through cache to reuse instance, if possible
+	// TODO unify with Pool<Path>
 	public Path path(Path p) {
 		String s = pathRewrite.rewrite(p.toString());
 		Path cached = get(s, false);
@@ -63,6 +64,11 @@
 		}
 		return get("", true);
 	}
+	
+	// invoke when path pool is no longer in use, to ease gc work
+	public void clear() {
+		cache.clear();
+	}
 
 	private Path get(String p, boolean create) {
 		SoftReference<Path> sr = cache.get(p);