# HG changeset patch # User Artem Tikhomirov # Date 1325799735 -10800 # Node ID 155c1893bda404548d1c982b1601cf3115d18266 # Parent aa2e589d4e84ef716e6ff603a39a50e098ec6d8b Issue 22: UnsupportedOperationException on empty manifest entry diff -r aa2e589d4e84 -r 155c1893bda4 src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Sat Dec 31 01:04:22 2011 +0300 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Fri Jan 06 00:42:15 2012 +0300 @@ -324,58 +324,61 @@ iterateControl.stop(); return; } - Path fname = null; - Flags flags = null; - Nodeid nid = null; - int i; - byte[] data = da.byteArray(); - for (i = 0; i < actualLen; i++) { - int x = i; - for( ; data[i] != '\n' && i < actualLen; i++) { - if (fname == null && data[i] == 0) { - PathProxy px = fnamePool.unify(new PathProxy(data, x, i - x)); - // if (cached = fnamePool.unify(px))== px then cacheMiss, else cacheHit - // cpython 0..10k: hits: 15 989 152, misses: 3020 - fname = px.freeze(); - x = i+1; + if (!da.isEmpty()) { + // although unlikely, manifest entry may be empty, when all files have been deleted from the repository + Path fname = null; + Flags flags = null; + Nodeid nid = null; + int i; + byte[] data = da.byteArray(); + for (i = 0; i < actualLen; i++) { + int x = i; + for( ; data[i] != '\n' && i < actualLen; i++) { + if (fname == null && data[i] == 0) { + PathProxy px = fnamePool.unify(new PathProxy(data, x, i - x)); + // if (cached = fnamePool.unify(px))== px then cacheMiss, else cacheHit + // cpython 0..10k: hits: 15 989 152, misses: 3020 + fname = px.freeze(); + x = i+1; + } } + if (i < actualLen) { + assert data[i] == '\n'; + int nodeidLen = i - x < 40 ? i-x : 40; // if > 40, there are flags + DigestHelper.ascii2bin(data, x, nodeidLen, nodeidLookupBuffer); // ignore return value as it's unlikely to have NULL in manifest + nid = new Nodeid(nodeidLookupBuffer, false); // this Nodeid is for pool lookup only, mock object + Nodeid cached = nodeidPool.unify(nid); + if (cached == nid) { + // buffer now belongs to the cached nodeid + nodeidLookupBuffer = new byte[20]; + } else { + nid = cached; // use existing version, discard the lookup object + } // for cpython 0..10k, cache hits are 15 973 301, vs 18871 misses. + thisRevPool.record(nid); // memorize revision for the next iteration. + if (nodeidLen + x < i) { + // 'x' and 'l' for executable bits and symlinks? + // hg --debug manifest shows 644 for each regular file in my repo + // for cpython 0..10k, there are 4361062 flag checks, and there's only 1 unique flag + flags = Flags.parse(data, x + nodeidLen, i-x-nodeidLen); + } else { + flags = null; + } + boolean good2go; + if (inspector2 == null) { + String flagString = flags == null ? null : flags.nativeString(); + good2go = inspector.next(nid, fname.toString(), flagString); + } else { + good2go = inspector2.next(nid, fname, flags); + } + if (!good2go) { + iterateControl.stop(); + return; + } + } + nid = null; + fname = null; + flags = null; } - if (i < actualLen) { - assert data[i] == '\n'; - int nodeidLen = i - x < 40 ? i-x : 40; // if > 40, there are flags - DigestHelper.ascii2bin(data, x, nodeidLen, nodeidLookupBuffer); // ignore return value as it's unlikely to have NULL in manifest - nid = new Nodeid(nodeidLookupBuffer, false); // this Nodeid is for pool lookup only, mock object - Nodeid cached = nodeidPool.unify(nid); - if (cached == nid) { - // buffer now belongs to the cached nodeid - nodeidLookupBuffer = new byte[20]; - } else { - nid = cached; // use existing version, discard the lookup object - } // for cpython 0..10k, cache hits are 15 973 301, vs 18871 misses. - thisRevPool.record(nid); // memorize revision for the next iteration. - if (nodeidLen + x < i) { - // 'x' and 'l' for executable bits and symlinks? - // hg --debug manifest shows 644 for each regular file in my repo - // for cpython 0..10k, there are 4361062 flag checks, and there's only 1 unique flag - flags = Flags.parse(data, x + nodeidLen, i-x-nodeidLen); - } else { - flags = null; - } - boolean good2go; - if (inspector2 == null) { - String flagString = flags == null ? null : flags.nativeString(); - good2go = inspector.next(nid, fname.toString(), flagString); - } else { - good2go = inspector2.next(nid, fname, flags); - } - if (!good2go) { - iterateControl.stop(); - return; - } - } - nid = null; - fname = null; - flags = null; } if (!inspector.end(revisionNumber)) { iterateControl.stop(); diff -r aa2e589d4e84 -r 155c1893bda4 test-data/test-repos.jar Binary file test-data/test-repos.jar has changed diff -r aa2e589d4e84 -r 155c1893bda4 test/org/tmatesoft/hg/test/TestStatus.java --- a/test/org/tmatesoft/hg/test/TestStatus.java Sat Dec 31 01:04:22 2011 +0300 +++ b/test/org/tmatesoft/hg/test/TestStatus.java Fri Jan 06 00:42:15 2012 +0300 @@ -458,6 +458,23 @@ } + /** + * Issue 22 + */ + @Test + public void testOnEmptyRepositoryWithAllFilesDeleted() throws Exception { + repo = Configuration.get().find("status-2"); + HgStatusCommand cmd = new HgStatusCommand(repo); + cmd.all(); + StatusCollector sc = new StatusCollector(); + cmd.execute(sc); + // shall pass without exception + assertTrue(sc.getErrors().isEmpty()); + for (HgStatus.Kind k : HgStatus.Kind.values()) { + assertTrue("Kind " + k.name() + " shall be empty",sc.get(k).isEmpty()); + } + } + /* * With warm-up of previous tests, 10 runs, time in milliseconds * 'hg status -A': Native client total 953 (95 per run), Java client 94 (9)