# HG changeset patch # User Artem Tikhomirov # Date 1368545495 -7200 # Node ID 5e0313485eef4e449fc8d7b184bf2a6f387795b5 # Parent 84104448a0bfaaee8a5eab873a689d6ac87d4dd3 encode directories as demanded by fncache format diff -r 84104448a0bf -r 5e0313485eef src/org/tmatesoft/hg/core/HgCloneCommand.java --- 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) { diff -r 84104448a0bf -r 5e0313485eef src/org/tmatesoft/hg/internal/CommitFacility.java --- 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 newlyAddedFiles = new ArrayList(); + LinkedHashMap newlyAddedFiles = new LinkedHashMap(); ArrayList touchInDirstate = new ArrayList(); for (Pair 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; } /* diff -r 84104448a0bf -r 5e0313485eef src/org/tmatesoft/hg/internal/EncodeDirPathHelper.java --- /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; + +/** + *
Directory names ending in .i or .d have .hg appended
+ * + * @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; + } + +} diff -r 84104448a0bf -r 5e0313485eef src/org/tmatesoft/hg/internal/FNCacheFile.java --- 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 files; - private List added; + private final List addedDotI; + private final List addedDotD; + private final FNCachePathHelper pathHelper; public FNCacheFile(Internals internalRepo) { repo = internalRepo; // files = new ArrayList(); + pathHelper = new FNCachePathHelper(); + addedDotI = new ArrayList(5); + addedDotD = new ArrayList(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 added = new ArrayList(); + 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(); - } - added.add(p); + public void addIndex(Path p) { + addedDotI.add(p); + } + + public void addData(Path p) { + addedDotD.add(p); } private File fncacheFile() { diff -r 84104448a0bf -r 5e0313485eef src/org/tmatesoft/hg/internal/FNCachePathHelper.java --- /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 + */ +} diff -r 84104448a0bf -r 5e0313485eef src/org/tmatesoft/hg/internal/StoragePathHelper.java --- 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) { diff -r 84104448a0bf -r 5e0313485eef test/org/tmatesoft/hg/test/TestRepositoryLock.java --- 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(); }