changeset 616:5e0313485eef

encode directories as demanded by fncache format
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 14 May 2013 17:31:35 +0200
parents 84104448a0bf
children 65c01508f002
files src/org/tmatesoft/hg/core/HgCloneCommand.java src/org/tmatesoft/hg/internal/CommitFacility.java src/org/tmatesoft/hg/internal/EncodeDirPathHelper.java src/org/tmatesoft/hg/internal/FNCacheFile.java src/org/tmatesoft/hg/internal/FNCachePathHelper.java src/org/tmatesoft/hg/internal/StoragePathHelper.java test/org/tmatesoft/hg/test/TestRepositoryLock.java
diffstat 7 files changed, 175 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgCloneCommand.java	Mon May 13 22:48:29 2013 +0200
+++ b/src/org/tmatesoft/hg/core/HgCloneCommand.java	Tue May 14 17:31:35 2013 +0200
@@ -245,7 +245,6 @@
 			try {
 				revlogHeader.offset(0).baseRevision(-1);
 				revisionSequence.clear();
-				fncacheFile.add(pathFactory.path(name)); 
 				File file = new File(hgDir, filename = storagePathHelper.rewrite(name).toString());
 				file.getParentFile().mkdirs();
 				indexFile = new FileOutputStream(file);
@@ -258,6 +257,7 @@
 
 		public void fileEnd(String name) {
 			try {
+				fncacheFile.addIndex(pathFactory.path(name)); 
 				clearPreviousContent();
 				closeIndexFile();
 			} catch (IOException ex) {
--- a/src/org/tmatesoft/hg/internal/CommitFacility.java	Mon May 13 22:48:29 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/CommitFacility.java	Tue May 14 17:31:35 2013 +0200
@@ -133,7 +133,7 @@
 		}
 		//
 		// Register new/changed
-		ArrayList<Path> newlyAddedFiles = new ArrayList<Path>();
+		LinkedHashMap<Path, RevlogStream> newlyAddedFiles = new LinkedHashMap<Path, RevlogStream>();
 		ArrayList<Path> touchInDirstate = new ArrayList<Path>();
 		for (Pair<HgDataFile, ByteDataSupplier> e : files.values()) {
 			HgDataFile df = e.first();
@@ -156,7 +156,7 @@
 				contentStream = repo.getImplAccess().getStream(df);
 			} else {
 				contentStream = repo.createStoreFile(df.getPath());
-				newlyAddedFiles.add(df.getPath());
+				newlyAddedFiles.put(df.getPath(), contentStream);
 				// FIXME df doesn't get df.content updated, and clients
 				// that would attempt to access newly added file after commit would fail
 				// (despite the fact the file is in there)
@@ -183,11 +183,14 @@
 		byte[] clogContent = changelogBuilder.build(manifestRev, message);
 		RevlogStreamWriter changelogWriter = new RevlogStreamWriter(repo, repo.getImplAccess().getChangelogStream());
 		Nodeid changesetRev = changelogWriter.addRevision(clogContent, clogRevisionIndex, p1Commit, p2Commit);
-		// FIXME move fncache update to an external facility, along with dirstate and bookmark update
+		// TODO move fncache update to an external facility, along with dirstate and bookmark update
 		if (!newlyAddedFiles.isEmpty() && repo.fncacheInUse()) {
 			FNCacheFile fncache = new FNCacheFile(repo);
-			for (Path p : newlyAddedFiles) {
-				fncache.add(p);
+			for (Path p : newlyAddedFiles.keySet()) {
+				fncache.addIndex(p);
+				if (!newlyAddedFiles.get(p).isInlineData()) {
+					fncache.addData(p);
+				}
 			}
 			try {
 				fncache.write();
@@ -232,9 +235,10 @@
 		if (p1Commit != NO_REVISION || p2Commit != NO_REVISION) {
 			repo.getRepo().getBookmarks().updateActive(p1Cset, p2Cset, changesetRev);
 		}
+		// TODO undo.dirstate and undo.branch as described in http://mercurial.selenic.com/wiki/FileFormats#undo..2A
 		// TODO Revisit: might be reasonable to send out a "Repo changed" notification, to clear
 		// e.g. cached branch, tags and so on, not to rely on file change detection methods?
-		// The same notificaion might come useful once Pull is implemented
+		// The same notification might come useful once Pull is implemented
 		return changesetRev;
 	}
 /*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/EncodeDirPathHelper.java	Tue May 14 17:31:35 2013 +0200
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 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.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ * <blockquote cite="http://mercurial.selenic.com/wiki/FileFormats#data.2F">Directory names ending in .i or .d have .hg appended</blockquote>
+ *  
+ * @see http://mercurial.selenic.com/wiki/FileFormats#data.2F
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+final class EncodeDirPathHelper implements PathRewrite {
+	private final Pattern suffix2replace;
+	
+	public EncodeDirPathHelper() {
+		suffix2replace = Pattern.compile("\\.([id]|hg)/");
+	}
+
+	public CharSequence rewrite(CharSequence p) {
+		Matcher suffixMatcher = suffix2replace.matcher(p);
+		CharSequence path;
+		// Matcher.replaceAll, but without extra toString
+		boolean found = suffixMatcher.find();
+		if (found) {
+			StringBuffer sb = new StringBuffer(p.length()  + 20);
+			do {
+				suffixMatcher.appendReplacement(sb, ".$1.hg/");
+			} while (found = suffixMatcher.find());
+			suffixMatcher.appendTail(sb);
+			path = sb;
+		} else {
+			path = p;
+		}
+		return path;
+	}
+
+}
--- a/src/org/tmatesoft/hg/internal/FNCacheFile.java	Mon May 13 22:48:29 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/FNCacheFile.java	Tue May 14 17:31:35 2013 +0200
@@ -19,6 +19,9 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.FileChannel;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
@@ -41,11 +44,16 @@
 	
 	private final Internals repo;
 //	private final List<Path> files;
-	private List<Path> added;
+	private final List<Path> addedDotI;
+	private final List<Path> addedDotD;
+	private final FNCachePathHelper pathHelper;
 
 	public FNCacheFile(Internals internalRepo) {
 		repo = internalRepo;
 //		files = new ArrayList<Path>();
+		pathHelper = new FNCachePathHelper();
+		addedDotI = new ArrayList<Path>(5);
+		addedDotD = new ArrayList<Path>(5);
 	}
 
 	/*
@@ -67,26 +75,42 @@
 	*/
 	
 	public void write() throws IOException {
-		if (added == null || added.isEmpty()) {
+		if (addedDotI.isEmpty() && addedDotD.isEmpty()) {
 			return;
 		}
 		File f = fncacheFile();
 		f.getParentFile().mkdirs();
 		final Charset filenameEncoding = repo.getFilenameEncoding();
-		FileOutputStream fncacheFile = new FileOutputStream(f, true);
-		for (Path p : added) {
-			String s = "data/" + p.toString() + ".i"; // TODO post-1.0 this is plain wrong. (a) need .d files, too; (b) what about dh/ location? 
-			fncacheFile.write(s.getBytes(filenameEncoding));
-			fncacheFile.write(0x0A); // http://mercurial.selenic.com/wiki/fncacheRepoFormat
+		ArrayList<CharBuffer> added = new ArrayList<CharBuffer>();
+		for (Path p : addedDotI) {
+			added.add(CharBuffer.wrap(pathHelper.rewrite(p)));
+		}
+		for (Path p : addedDotD) {
+			// XXX FNCachePathHelper always return name of an index file, need to change it into a name of data file,
+			// although the approach (to replace last char) is depressingly awful
+			CharSequence cs = pathHelper.rewrite(p);
+			CharBuffer cb = CharBuffer.allocate(cs.length());
+			cb.append(cs);
+			cb.put(cs.length()-1, 'd');
+			cb.flip();
+			added.add(cb);
+		}
+		FileChannel fncacheFile = new FileOutputStream(f, true).getChannel();
+		ByteBuffer lf = ByteBuffer.wrap(new byte[] { 0x0A });
+		for (CharBuffer b : added) {
+			fncacheFile.write(filenameEncoding.encode(b));
+			fncacheFile.write(lf);
+			lf.rewind();
 		}
 		fncacheFile.close();
 	}
 
-	public void add(Path p) {
-		if (added == null) {
-			added = new ArrayList<Path>();
-		}
-		added.add(p);
+	public void addIndex(Path p) {
+		addedDotI.add(p);
+	}
+
+	public void addData(Path p) {
+		addedDotD.add(p);
 	}
 
 	private File fncacheFile() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/FNCachePathHelper.java	Tue May 14 17:31:35 2013 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 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.internal.StoragePathHelper.STR_DATA;
+
+import org.tmatesoft.hg.util.PathRewrite;
+
+/**
+ * Prepare filelog names to be written into fncache. 
+ * 
+ * @see http://mercurial.selenic.com/wiki/fncacheRepoFormat#The_fncache_file
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+final class FNCachePathHelper implements PathRewrite {
+
+	private final EncodeDirPathHelper dirPathRewrite;
+
+	
+	public FNCachePathHelper() {
+		dirPathRewrite = new EncodeDirPathHelper();
+	}
+
+	/**
+	 * Input: repository-relative path of a filelog, i.e. without 'data/' or 'dh/' prefix, and/or '.i'/'.d' suffix.
+	 * Output: path ready to be written into fncache file, alaways with '.i' suffix (caller is free to alter the suffix to '.d' as appropriate
+	 */
+	public CharSequence rewrite(CharSequence path) {
+		CharSequence p = dirPathRewrite.rewrite(path);
+		StringBuilder result = new StringBuilder(p.length() + STR_DATA.length() + ".i".length());
+		result.append(STR_DATA);
+		result.append(p);
+		result.append(".i");
+		return result;
+	}
+
+	/*
+	 * There's always 'data/' prefix, even if actual file resides under 'dh/':
+	 *  
+	 * $ cat .hg/store/fncache
+	 * data/very-long-directory-name-level-1/very-long-directory-name-level-2/very-long-directory-name-level-3/file-with-longest-name-i-am-not-lazy-to-type.txt.i
+	 * $ ls .hg/store/dh/very-lon/very-lon/very-lon/
+	 * file-with-longest-name-i-am-not-lazy-to-type.txtbbd4d3327f6364027211b0cd8ca499d3d6308849.i
+	 */
+}
--- a/src/org/tmatesoft/hg/internal/StoragePathHelper.java	Mon May 13 22:48:29 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/StoragePathHelper.java	Tue May 14 17:31:35 2013 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2012 TMate Software Ltd
+ * 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
@@ -22,8 +22,6 @@
 import java.nio.charset.CharsetEncoder;
 import java.util.Arrays;
 import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.tmatesoft.hg.util.PathRewrite;
 
@@ -36,11 +34,15 @@
  * @author TMate Software Ltd.
  */
 class StoragePathHelper implements PathRewrite {
-	
+
+	static final String STR_STORE = "store/";
+	static final String STR_DATA = "data/";
+	static final String STR_DH = "dh/";
+
 	private final boolean store;
 	private final boolean fncache;
 	private final boolean dotencode;
-	private final Pattern suffix2replace;
+	private final EncodeDirPathHelper dirPathRewrite;
 	private final CharsetEncoder csEncoder;
 	private final char[] hexEncodedByte = new char[] {'~', '0', '0'};
 	private final ByteBuffer byteEncodingBuf;
@@ -55,7 +57,7 @@
 		store = isStore;
 		fncache = isFncache;
 		dotencode = isDotencode;
-		suffix2replace = Pattern.compile("\\.([id]|hg)/");
+		dirPathRewrite = new EncodeDirPathHelper();
 		csEncoder = fsEncoding.newEncoder();
 		byteEncodingBuf = ByteBuffer.allocate(Math.round(csEncoder.maxBytesPerChar()) + 1/*in fact, need ceil, hence +1*/);
 		charEncodingBuf = CharBuffer.allocate(1);
@@ -66,25 +68,9 @@
 	 * It has to be normalized (slashes) and shall not include extension .i or .d.
 	 */
 	public CharSequence rewrite(CharSequence p) {
-		final String STR_STORE = "store/";
-		final String STR_DATA = "data/";
-		final String STR_DH = "dh/";
 		final String reservedChars = "\\:*?\"<>|";
 		
-		Matcher suffixMatcher = suffix2replace.matcher(p);
-		CharSequence path;
-		// Matcher.replaceAll, but without extra toString
-		boolean found = suffixMatcher.find();
-		if (found) {
-			StringBuffer sb = new StringBuffer(p.length()  + 20);
-			do {
-				suffixMatcher.appendReplacement(sb, ".$1.hg/");
-			} while (found = suffixMatcher.find());
-			suffixMatcher.appendTail(sb);
-			path = sb;
-		} else {
-			path = p;
-		}
+		CharSequence path = dirPathRewrite.rewrite(p);
 		
 		StringBuilder sb = new StringBuilder(path.length() << 1);
 		if (store || fncache) {
--- a/test/org/tmatesoft/hg/test/TestRepositoryLock.java	Mon May 13 22:48:29 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestRepositoryLock.java	Tue May 14 17:31:35 2013 +0200
@@ -37,13 +37,15 @@
 		// turn off lock timeout, to fail fast
 		File hgrc = new File(repoLoc, ".hg/hgrc");
 		RepoUtils.createFile(hgrc, "[ui]\ntimeout=0\n"); // or 1
-		ExecHelper eh = new ExecHelper(new OutputParser.Stub(true), repoLoc);
+		final OutputParser.Stub p = new OutputParser.Stub();
+		ExecHelper eh = new ExecHelper(p, repoLoc);
 		HgRepository hgRepo = new HgLookup().detect(repoLoc);
 		final HgRepositoryLock wdLock = hgRepo.getWorkingDirLock();
 		try {
 			wdLock.acquire();
 			eh.run("hg", "tag", "tag-aaa");
 			Assert.assertNotSame(0 /*returns 0 on success*/, eh.getExitValue());
+			Assert.assertTrue(p.result().toString().contains("abort"));
 		} finally {
 			wdLock.release();
 		}