Mercurial > hg4j
comparison src/org/tmatesoft/hg/internal/RevlogStream.java @ 397:5e95b0da26f2 smartgit3
Issue 24: IAE, Underflow in FilterDataAccess. Issue 26:UnsupportedOperationException when patching empty base revision. Tests
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Thu, 23 Feb 2012 15:31:57 +0100 |
parents | 86f049e6bcae |
children | c76c57f6b961 |
comparison
equal
deleted
inserted
replaced
393:728708de3597 | 397:5e95b0da26f2 |
---|---|
1 /* | 1 /* |
2 * Copyright (c) 2010-2011 TMate Software Ltd | 2 * Copyright (c) 2010-2012 TMate Software Ltd |
3 * | 3 * |
4 * This program is free software; you can redistribute it and/or modify | 4 * This program is free software; you can redistribute it and/or modify |
5 * it under the terms of the GNU General Public License as published by | 5 * it under the terms of the GNU General Public License as published by |
6 * the Free Software Foundation; version 2 of the License. | 6 * the Free Software Foundation; version 2 of the License. |
7 * | 7 * |
462 streamOffset = (int) offset; | 462 streamOffset = (int) offset; |
463 streamDataAccess = daData; | 463 streamDataAccess = daData; |
464 daData.seek(streamOffset); | 464 daData.seek(streamOffset); |
465 } | 465 } |
466 final boolean patchToPrevious = baseRevision != i; // the only way I found to tell if it's a patch | 466 final boolean patchToPrevious = baseRevision != i; // the only way I found to tell if it's a patch |
467 if (streamDataAccess.isEmpty()) { | 467 if (streamDataAccess.isEmpty() || compressedLen == 0) { |
468 userDataAccess = new DataAccess(); // empty | 468 userDataAccess = new DataAccess(); // empty |
469 } else { | 469 } else { |
470 final byte firstByte = streamDataAccess.readByte(); | 470 final byte firstByte = streamDataAccess.readByte(); |
471 if (firstByte == 0x78 /* 'x' */) { | 471 if (firstByte == 0x78 /* 'x' */) { |
472 inflater.reset(); | 472 inflater.reset(); |
473 userDataAccess = new InflaterDataAccess(streamDataAccess, streamOffset, compressedLen, patchToPrevious ? -1 : actualLen, inflater, inflaterBuffer); | 473 userDataAccess = new InflaterDataAccess(streamDataAccess, streamOffset, compressedLen, patchToPrevious ? -1 : actualLen, inflater, inflaterBuffer); |
474 } else if (firstByte == 0x75 /* 'u' */) { | 474 } else if (firstByte == 0x75 /* 'u' */) { |
475 userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset+1, compressedLen-1); | 475 userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset+1, compressedLen-1); |
476 } else { | 476 } else { |
477 // XXX Python impl in fact throws exception when there's not 'x', 'u' or '0' | 477 // XXX Python impl in fact throws exception when there's not 'x', 'u' or '0' but I don't see reason not to return data as is |
478 // but I don't see reason not to return data as is | 478 // |
479 // although firstByte is already read from the streamDataAccess, FilterDataAccess#readByte would seek to | |
480 // initial offset before first attempt to read a byte | |
479 userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset, compressedLen); | 481 userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset, compressedLen); |
480 } | 482 } |
481 } | 483 } |
482 // XXX | 484 // userDataAccess is revision content, either complete revision, patch of a previous content, or an empty patch |
483 if (patchToPrevious && !userDataAccess.isEmpty() /* Issue 22, empty patch to an empty base revision*/) { | 485 if (patchToPrevious) { |
484 // this is a patch | 486 // this is a patch |
485 patch.read(userDataAccess); | 487 if (userDataAccess.isEmpty()) { |
486 userDataAccess.done(); | 488 // Issue 22, empty patch to an empty base revision |
487 // | 489 // Issue 24, empty patch to non-empty base revision |
488 // it shall be reset at the end of prev iteration, when it got assigned from userDataAccess | 490 // empty patch modifies nothing, use content of a previous revision (shall present - it's a patch here) |
489 // however, actual userDataAccess and lastUserData may share Inflater object, which needs to be reset | 491 // |
490 // Alternatively, userDataAccess.done() above may be responsible to reset Inflater (if it's InflaterDataAccess) | 492 assert lastUserData.length() == actualLen; // with no patch, data size shall be the same |
491 lastUserData.reset(); | 493 userDataAccess = lastUserData; |
492 // final long startMeasuring = System.currentTimeMillis(); // TIMING | 494 } else { |
493 byte[] userData = patch.apply(lastUserData, actualLen); | 495 patch.read(userDataAccess); |
494 // applyTime += (System.currentTimeMillis() - startMeasuring); // TIMING | 496 userDataAccess.done(); |
495 patch.clear(); // do not keep any reference, allow byte[] data to be gc'd | 497 // |
496 userDataAccess = new ByteArrayDataAccess(userData); | 498 // it shall be reset at the end of prev iteration, when it got assigned from userDataAccess |
499 // however, actual userDataAccess and lastUserData may share Inflater object, which needs to be reset | |
500 // Alternatively, userDataAccess.done() above may be responsible to reset Inflater (if it's InflaterDataAccess) | |
501 lastUserData.reset(); | |
502 // final long startMeasuring = System.currentTimeMillis(); // TIMING | |
503 byte[] userData = patch.apply(lastUserData, actualLen); | |
504 // applyTime += (System.currentTimeMillis() - startMeasuring); // TIMING | |
505 patch.clear(); // do not keep any reference, allow byte[] data to be gc'd | |
506 userDataAccess = new ByteArrayDataAccess(userData); | |
507 } | |
497 } | 508 } |
498 } else { | 509 } else { |
499 if (inline) { | 510 if (inline) { |
500 daIndex.skip(compressedLen); | 511 daIndex.skip(compressedLen); |
501 } | 512 } |
511 } | 522 } |
512 } | 523 } |
513 if (userDataAccess != null) { | 524 if (userDataAccess != null) { |
514 userDataAccess.reset(); // not sure this is necessary here, as lastUserData would get reset anyway before next use. | 525 userDataAccess.reset(); // not sure this is necessary here, as lastUserData would get reset anyway before next use. |
515 } | 526 } |
516 if (lastUserData != null) { | 527 if (lastUserData != null && lastUserData != userDataAccess /* empty patch case, reuse of recent data in actual revision */) { |
528 // release lastUserData only if we didn't reuse it in actual revision due to empty patch: | |
529 // empty patch means we have previous revision and didn't alter it with a patch, hence use lastUserData for userDataAccess above | |
517 lastUserData.done(); | 530 lastUserData.done(); |
518 } | 531 } |
519 lastUserData = userDataAccess; | 532 lastUserData = userDataAccess; |
520 } | 533 } |
521 lastRevisionRead = end; | 534 lastRevisionRead = end; |