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; | 
