# HG changeset patch # User Artem Tikhomirov # Date 1369920257 -7200 # Node ID 8a5cdcb27b8f5c281a37bda6e704035176e0e8dd # Parent 72c979555cb8c1147a6739c3fc6146651acae986 AIOOBE in HgManifest.RevisionMapper. Provide more details about exception context. Create lock file atomically. Test concurrent pull-rebase and read diff -r 72c979555cb8 -r 8a5cdcb27b8f src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Thu May 23 19:44:28 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Thu May 30 15:24:17 2013 +0200 @@ -662,7 +662,12 @@ } // XXX can be replaced with Revlog.RevisionInspector, but I don't want Nodeid instances - public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgInvalidRevisionException { + if (linkRevision >= changelogRevisionCount) { + String storeLock = HgManifest.this.getRepo().getStoreLock().readLockInfo(); + String message = String.format("Manifest revision %d references changeset %d, which is beyond known scope [0..%d). Lock: %s", revisionNumber, linkRevision, changelogRevisionCount, storeLock); + throw new HgInvalidRevisionException(message, null, linkRevision); + } if (changelog2manifest != null) { // next assertion is not an error, rather assumption check, which is too development-related to be explicit exception - // I just wonder if there are manifests that have two entries pointing to single changeset. It seems unrealistic, though - diff -r 72c979555cb8 -r 8a5cdcb27b8f src/org/tmatesoft/hg/repo/HgRepositoryLock.java --- a/src/org/tmatesoft/hg/repo/HgRepositoryLock.java Thu May 23 19:44:28 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRepositoryLock.java Thu May 30 15:24:17 2013 +0200 @@ -120,7 +120,7 @@ do { synchronized(this) { try { - if (!lockFile.exists()) { + if (lockFile.createNewFile()) { write(lockFile, bytes); use++; return; diff -r 72c979555cb8 -r 8a5cdcb27b8f test/org/tmatesoft/hg/test/RepoUtils.java --- a/test/org/tmatesoft/hg/test/RepoUtils.java Thu May 23 19:44:28 2013 +0200 +++ b/test/org/tmatesoft/hg/test/RepoUtils.java Thu May 30 15:24:17 2013 +0200 @@ -66,6 +66,10 @@ } static File cloneRepoToTempLocation(HgRepository repo, String name, boolean noupdate) throws IOException, InterruptedException { + return cloneRepoToTempLocation(repo.getWorkingDir(), name, noupdate, false); + } + + static File cloneRepoToTempLocation(File repoLoc, String name, boolean noupdate, boolean usePull) throws IOException, InterruptedException { File testRepoLoc = createEmptyDir(name); ExecHelper eh = new ExecHelper(new OutputParser.Stub(), testRepoLoc.getParentFile()); ArrayList cmd = new ArrayList(); @@ -74,7 +78,10 @@ if (noupdate) { cmd.add("--noupdate"); } - cmd.add(repo.getWorkingDir().toString()); + if (usePull) { + cmd.add("--pull"); + } + cmd.add(repoLoc.toString()); cmd.add(testRepoLoc.getName()); eh.run(cmd.toArray(new String[cmd.size()])); assertEquals("[sanity]", 0, eh.getExitValue()); diff -r 72c979555cb8 -r 8a5cdcb27b8f test/org/tmatesoft/hg/test/TestRepositoryLock.java --- a/test/org/tmatesoft/hg/test/TestRepositoryLock.java Thu May 23 19:44:28 2013 +0200 +++ b/test/org/tmatesoft/hg/test/TestRepositoryLock.java Thu May 30 15:24:17 2013 +0200 @@ -17,9 +17,15 @@ package org.tmatesoft.hg.test; import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; import org.junit.Assert; import org.junit.Test; +import org.tmatesoft.hg.core.HgStatusCommand; +import org.tmatesoft.hg.internal.BasicSessionContext; +import org.tmatesoft.hg.internal.DataAccessProvider; import org.tmatesoft.hg.repo.HgLookup; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRepositoryLock; @@ -50,4 +56,123 @@ wdLock.release(); } } + + public static void main(String[] args) throws Exception { + Map po = new HashMap(); + po.put(DataAccessProvider.CFG_PROPERTY_MAPIO_LIMIT, 0); + final HgLookup hgLookup = new HgLookup(new BasicSessionContext(po , null)); + final File rebaseFromRepoLoc = RepoUtils.cloneRepoToTempLocation(new File("/temp/hg/junit-test-repos/test-annotate"), "repo-lock-remote", false, true); + final File rebaseToRepoLoc = RepoUtils.cloneRepoToTempLocation(rebaseFromRepoLoc, "repo-lock-local", false, true); + final File remoteChanges = new File(rebaseFromRepoLoc, "file1"); + // + // create commit in the "local" repository that will be rebased on top of changes + // pulled from "remote repository" + File localChanges = new File(rebaseToRepoLoc, "file-new"); + if (localChanges.exists()) { + RepoUtils.modifyFileAppend(localChanges, "whatever"); + } else { + RepoUtils.createFile(localChanges, "whatever"); + } + commit(rebaseToRepoLoc, "local change"); + // + final int rebaseRevisionCount = 70; + final CountDownLatch latch = new CountDownLatch(2); + Runnable r1 = new Runnable() { + public void run() { + for (int i = 0; i < rebaseRevisionCount; i++) { + commitPullRebaseNative(rebaseFromRepoLoc, rebaseToRepoLoc, remoteChanges); + sleep(500, 1000); + } + latch.countDown(); + } + }; + Runnable r2 = new Runnable() { + public void run() { + for (int i = 0; i < 100; i++) { + readWithHg4J(hgLookup, rebaseToRepoLoc); + sleep(800, 400); + } + latch.countDown(); + } + }; + new Thread(r1, "pull-rebase-thread").start(); + new Thread(r2, "hg4j-read-thread").start(); + latch.await(); + System.out.println("DONE."); + // now `hg log` in rebaseToRepoLoc shall show + // all rebaseRevisionCount revisions from rebaseFromRepoLoc + 1 more, "local change", on top of them + } + + private static int count = 0; + + private static void commitPullRebaseNative(final File rebaseFromRepoLoc, final File rebaseToRepoLoc, final File rebaseFromChanges) { + try { + OutputParser.Stub p = new OutputParser.Stub(); + final ExecHelper eh = new ExecHelper(p, rebaseToRepoLoc); + RepoUtils.modifyFileAppend(rebaseFromChanges, "Change #" + count++); + commit(rebaseFromRepoLoc, "remote change"); + p.reset(); + eh.run("hg", "--traceback", "pull", rebaseFromRepoLoc.toString()); + if (eh.getExitValue() != 0) { + System.out.println(p.result()); + } + Assert.assertEquals(0, eh.getExitValue()); + p.reset(); + eh.run("hg", "--traceback", "--config", "extensions.hgext.rebase=", "rebase"); + if (eh.getExitValue() != 0) { + System.out.println(p.result()); + } + System.out.print("X"); + Assert.assertEquals(0, eh.getExitValue()); + } catch (RuntimeException ex) { + throw ex; + } catch (Exception ex) { + ex.printStackTrace(); + throw new RuntimeException(null, ex); + } + } + + private static void readWithHg4J(final HgLookup hgLookup, final File repoLoc) { + try { + System.out.print("("); + final long start = System.nanoTime(); + HgRepository hgRepo = hgLookup.detect(repoLoc); + final HgRepositoryLock wcLock = hgRepo.getWorkingDirLock(); + final HgRepositoryLock storeLock = hgRepo.getStoreLock(); + wcLock.acquire(); + System.out.print("."); + storeLock.acquire(); + System.out.print("."); + try { + new HgStatusCommand(hgRepo).execute(new TestStatus.StatusCollector()); + System.out.printf("%d ms)\n", (System.nanoTime() - start) / 1000000); + } finally { + storeLock.release(); + wcLock.release(); + } + } catch (RuntimeException ex) { + throw ex; + } catch (Exception ex) { + ex.printStackTrace(); + throw new RuntimeException(null, ex); + } + } + + private static void commit(File repoLoc, String message) throws Exception { + OutputParser.Stub p = new OutputParser.Stub(); + final ExecHelper eh = new ExecHelper(p, repoLoc); + eh.run("hg", "commit", "--addremove", "-m", "\"" + message + "\""); + if (eh.getExitValue() != 0) { + System.out.println(p.result()); + } + Assert.assertEquals(0, eh.getExitValue()); + } + + private static void sleep(int msBase, int msDelta) { + try { + Thread.sleep(msBase + Math.round(Math.random() * msDelta)); + } catch (InterruptedException ex) { + // IGNORE + } + } }