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