diff src/org/tmatesoft/hg/internal/DirstateReader.java @ 526:2f9ed6bcefa2

Initial support for Revert command with accompanying minor refactoring
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 15 Jan 2013 17:07:19 +0100
parents src/org/tmatesoft/hg/repo/HgDirstate.java@0be5be8d57e9
children 47b7bedf0569
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/DirstateReader.java	Tue Jan 15 17:07:19 2013 +0100
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2010-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 static org.tmatesoft.hg.core.Nodeid.NULL;
+import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate;
+import static org.tmatesoft.hg.util.LogFacility.Severity.Debug;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgDirstate;
+import org.tmatesoft.hg.repo.HgDirstate.EntryKind;
+import org.tmatesoft.hg.repo.HgInvalidControlFileException;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.LogFacility.Severity;
+import org.tmatesoft.hg.util.Pair;
+import org.tmatesoft.hg.util.Path;
+
+
+/**
+ * Parse dirstate file
+ * 
+ * @see http://mercurial.selenic.com/wiki/DirState
+ * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class DirstateReader {
+	// dirstate read code originally lived in org.tmatesoft.hg.repo.HgDirstate
+	
+	private final Internals repo;
+	private final Path.Source pathPool;
+	private Pair<Nodeid, Nodeid> parents;
+	
+	public DirstateReader(Internals hgRepo, Path.Source pathSource) {
+		repo = hgRepo;
+		pathPool = pathSource;
+	}
+
+	public void readInto(HgDirstate.Inspector target) throws HgInvalidControlFileException {
+		EncodingHelper encodingHelper = repo.buildFileNameEncodingHelper();
+		parents = new Pair<Nodeid,Nodeid>(Nodeid.NULL, Nodeid.NULL);
+		File dirstateFile = getDirstateFile(repo);
+		if (dirstateFile == null || !dirstateFile.exists()) {
+			return;
+		}
+		DataAccess da = repo.getDataAccess().create(dirstateFile);
+		try {
+			if (da.isEmpty()) {
+				return;
+			}
+			parents = internalReadParents(da);
+			// hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only
+			while (!da.isEmpty()) {
+				final byte state = da.readByte();
+				final int fmode = da.readInt();
+				final int size = da.readInt();
+				final int time = da.readInt();
+				final int nameLen = da.readInt();
+				String fn1 = null, fn2 = null;
+				byte[] name = new byte[nameLen];
+				da.readBytes(name, 0, nameLen);
+				for (int i = 0; i < nameLen; i++) {
+					if (name[i] == 0) {
+						fn1 = encodingHelper.fromDirstate(name, 0, i);
+						fn2 = encodingHelper.fromDirstate(name, i+1, nameLen - i - 1);
+						break;
+					}
+				}
+				if (fn1 == null) {
+					fn1 = encodingHelper.fromDirstate(name, 0, nameLen);
+				}
+				HgDirstate.Record r = new HgDirstate.Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2));
+				if (state == 'n') {
+					target.next(EntryKind.Normal, r);
+				} else if (state == 'a') {
+					target.next(EntryKind.Added, r);
+				} else if (state == 'r') {
+					target.next(EntryKind.Removed, r);
+				} else if (state == 'm') {
+					target.next(EntryKind.Merged, r);
+				} else {
+					repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name(), r.size(), r.modificationTime(), state);
+				}
+			}
+		} catch (IOException ex) {
+			throw new HgInvalidControlFileException("Dirstate read failed", ex, dirstateFile); 
+		} finally {
+			da.done();
+		}
+	}
+
+	private static Pair<Nodeid, Nodeid> internalReadParents(DataAccess da) throws IOException {
+		byte[] parents = new byte[40];
+		da.readBytes(parents, 0, 40);
+		Nodeid n1 = Nodeid.fromBinary(parents, 0);
+		Nodeid n2 = Nodeid.fromBinary(parents, 20);
+		parents = null;
+		return new Pair<Nodeid, Nodeid>(n1, n2);
+	}
+	
+	/**
+	 * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values.
+	 */
+	public Pair<Nodeid,Nodeid> parents() {
+		assert parents != null; // instance not initialized with #read()
+		return parents;
+	}
+	
+	private static File getDirstateFile(Internals repo) {
+		return repo.getFileFromRepoDir(Dirstate.getName());
+	}
+
+	/**
+	 * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available
+	 */
+	public static Pair<Nodeid, Nodeid> readParents(Internals internalRepo) throws HgInvalidControlFileException {
+		// do not read whole dirstate if all we need is WC parent information
+		File dirstateFile = getDirstateFile(internalRepo);
+		if (dirstateFile == null || !dirstateFile.exists()) {
+			return new Pair<Nodeid,Nodeid>(NULL, NULL);
+		}
+		DataAccess da = internalRepo.getDataAccess().create(dirstateFile);
+		try {
+			if (da.isEmpty()) {
+				return new Pair<Nodeid,Nodeid>(NULL, NULL);
+			}
+			return internalReadParents(da);
+		} catch (IOException ex) {
+			throw new HgInvalidControlFileException("Error reading working copy parents from dirstate", ex, dirstateFile);
+		} finally {
+			da.done();
+		}
+	}
+	
+	/**
+	 * TODO [post-1.0] it's really not a proper place for the method, need WorkingCopyContainer or similar
+	 * @return branch associated with the working directory
+	 */
+	public static String readBranch(Internals internalRepo) throws HgInvalidControlFileException {
+		File branchFile = internalRepo.getFileFromRepoDir("branch"); // FIXME constant in the HgRepositoryFiles
+		String branch = HgRepository.DEFAULT_BRANCH_NAME;
+		if (branchFile.exists()) {
+			try {
+				BufferedReader r = new BufferedReader(new FileReader(branchFile));
+				String b = r.readLine();
+				if (b != null) {
+					b = b.trim().intern();
+				}
+				branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b;
+				r.close();
+			} catch (FileNotFoundException ex) {
+				internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here 
+				// IGNORE
+			} catch (IOException ex) {
+				throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile);
+			}
+		}
+		return branch;
+	}
+}