diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java index b572e0092..356966ab5 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java @@ -47,41 +47,47 @@ package org.eclipse.jgit.pgm; +import java.io.IOException; import java.io.PrintWriter; -import org.kohsuke.args4j.Option; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.TrackingRefUpdate; import org.eclipse.jgit.transport.Transport; +import org.kohsuke.args4j.Option; abstract class AbstractFetchCommand extends TextBuiltin { @Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beMoreVerbose") private boolean verbose; protected void showFetchResult(final Transport tn, final FetchResult r) { - boolean shownURI = false; - for (final TrackingRefUpdate u : r.getTrackingRefUpdates()) { - if (!verbose && u.getResult() == RefUpdate.Result.NO_CHANGE) - continue; - - final char type = shortTypeOf(u.getResult()); - final String longType = longTypeOf(u); - final String src = abbreviateRef(u.getRemoteName(), false); - final String dst = abbreviateRef(u.getLocalName(), true); - - if (!shownURI) { - out.format(CLIText.get().fromURI, tn.getURI()); + ObjectReader reader = db.newObjectReader(); + try { + boolean shownURI = false; + for (final TrackingRefUpdate u : r.getTrackingRefUpdates()) { + if (!verbose && u.getResult() == RefUpdate.Result.NO_CHANGE) + continue; + + final char type = shortTypeOf(u.getResult()); + final String longType = longTypeOf(reader, u); + final String src = abbreviateRef(u.getRemoteName(), false); + final String dst = abbreviateRef(u.getLocalName(), true); + + if (!shownURI) { + out.format(CLIText.get().fromURI, tn.getURI()); + out.println(); + shownURI = true; + } + + out.format(" %c %-17s %-10s -> %s", type, longType, src, dst); out.println(); - shownURI = true; } - - out.format(" %c %-17s %-10s -> %s", type, longType, src, dst); - out.println(); + } finally { + reader.release(); } - showRemoteMessages(r.getMessages()); } @@ -116,7 +122,7 @@ abstract class AbstractFetchCommand extends TextBuiltin { writer.flush(); } - private String longTypeOf(final TrackingRefUpdate u) { + private String longTypeOf(ObjectReader reader, final TrackingRefUpdate u) { final RefUpdate.Result r = u.getResult(); if (r == RefUpdate.Result.LOCK_FAILURE) return "[lock fail]"; @@ -136,14 +142,14 @@ abstract class AbstractFetchCommand extends TextBuiltin { } if (r == RefUpdate.Result.FORCED) { - final String aOld = u.getOldObjectId().abbreviate(db).name(); - final String aNew = u.getNewObjectId().abbreviate(db).name(); + final String aOld = safeAbbreviate(reader, u.getOldObjectId()); + final String aNew = safeAbbreviate(reader, u.getNewObjectId()); return aOld + "..." + aNew; } if (r == RefUpdate.Result.FAST_FORWARD) { - final String aOld = u.getOldObjectId().abbreviate(db).name(); - final String aNew = u.getNewObjectId().abbreviate(db).name(); + final String aOld = safeAbbreviate(reader, u.getOldObjectId()); + final String aNew = safeAbbreviate(reader, u.getNewObjectId()); return aOld + ".." + aNew; } @@ -152,6 +158,14 @@ abstract class AbstractFetchCommand extends TextBuiltin { return "[" + r.name() + "]"; } + private String safeAbbreviate(ObjectReader reader, ObjectId id) { + try { + return reader.abbreviate(id).name(); + } catch (IOException cannotAbbreviate) { + return id.name(); + } + } + private static char shortTypeOf(final RefUpdate.Result r) { if (r == RefUpdate.Result.LOCK_FAILURE) return '!'; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java index 0274219b7..b1c825462 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java @@ -53,6 +53,7 @@ import java.util.Map.Entry; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; import org.eclipse.jgit.lib.RefRename; @@ -182,9 +183,16 @@ class Branch extends TextBuiltin { addRef("(no branch)", head); addRefs(refs, Constants.R_HEADS, !remote); addRefs(refs, Constants.R_REMOTES, remote); - for (final Entry e : printRefs.entrySet()) { - final Ref ref = e.getValue(); - printHead(e.getKey(), current.equals(ref.getName()), ref); + + ObjectReader reader = db.newObjectReader(); + try { + for (final Entry e : printRefs.entrySet()) { + final Ref ref = e.getValue(); + printHead(reader, e.getKey(), + current.equals(ref.getName()), ref); + } + } finally { + reader.release(); } } } @@ -205,8 +213,8 @@ class Branch extends TextBuiltin { maxNameLength = Math.max(maxNameLength, name.length()); } - private void printHead(final String ref, final boolean isCurrent, - final Ref refObj) throws Exception { + private void printHead(final ObjectReader reader, final String ref, + final boolean isCurrent, final Ref refObj) throws Exception { out.print(isCurrent ? '*' : ' '); out.print(' '); out.print(ref); @@ -214,7 +222,7 @@ class Branch extends TextBuiltin { final int spaces = maxNameLength - ref.length() + 1; out.format("%" + spaces + "s", ""); final ObjectId objectId = refObj.getObjectId(); - out.print(objectId.abbreviate(db).name()); + out.print(reader.abbreviate(objectId).name()); out.print(' '); out.print(rw.parseCommit(objectId).getShortMessage()); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java index 22fd7e38f..05966416c 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java @@ -43,14 +43,15 @@ package org.eclipse.jgit.pgm; +import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.transport.PushResult; @@ -59,6 +60,8 @@ import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_UpdateRemoteRepositoryFromLocalRefs") class Push extends TextBuiltin { @@ -132,11 +135,17 @@ class Push extends TextBuiltin { } finally { transport.close(); } - printPushResult(uri, result); + + ObjectReader reader = db.newObjectReader(); + try { + printPushResult(reader, uri, result); + } finally { + reader.release(); + } } } - private void printPushResult(final URIish uri, + private void printPushResult(final ObjectReader reader, final URIish uri, final PushResult result) { shownURI = false; boolean everythingUpToDate = true; @@ -145,7 +154,7 @@ class Push extends TextBuiltin { for (final RemoteRefUpdate rru : result.getRemoteUpdates()) { if (rru.getStatus() == Status.UP_TO_DATE) { if (verbose) - printRefUpdateResult(uri, result, rru); + printRefUpdateResult(reader, uri, result, rru); } else everythingUpToDate = false; } @@ -153,14 +162,14 @@ class Push extends TextBuiltin { for (final RemoteRefUpdate rru : result.getRemoteUpdates()) { // ...then successful updates... if (rru.getStatus() == Status.OK) - printRefUpdateResult(uri, result, rru); + printRefUpdateResult(reader, uri, result, rru); } for (final RemoteRefUpdate rru : result.getRemoteUpdates()) { // ...finally, others (problematic) if (rru.getStatus() != Status.OK && rru.getStatus() != Status.UP_TO_DATE) - printRefUpdateResult(uri, result, rru); + printRefUpdateResult(reader, uri, result, rru); } AbstractFetchCommand.showRemoteMessages(result.getMessages()); @@ -168,8 +177,8 @@ class Push extends TextBuiltin { out.println(CLIText.get().everythingUpToDate); } - private void printRefUpdateResult(final URIish uri, - final PushResult result, final RemoteRefUpdate rru) { + private void printRefUpdateResult(final ObjectReader reader, + final URIish uri, final PushResult result, final RemoteRefUpdate rru) { if (!shownURI) { shownURI = true; out.println(MessageFormat.format(CLIText.get().pushTo, uri)); @@ -194,10 +203,10 @@ class Push extends TextBuiltin { } else { boolean fastForward = rru.isFastForward(); final char flag = fastForward ? ' ' : '+'; - final String summary = oldRef.getObjectId().abbreviate(db) - .name() + final String summary = safeAbbreviate(reader, oldRef + .getObjectId()) + (fastForward ? ".." : "...") - + rru.getNewObjectId().abbreviate(db).name(); + + safeAbbreviate(reader, rru.getNewObjectId()); final String message = fastForward ? null : CLIText.get().forcedUpdate; printUpdateLine(flag, summary, srcRef, remoteName, message); } @@ -220,8 +229,8 @@ class Push extends TextBuiltin { case REJECTED_REMOTE_CHANGED: final String message = MessageFormat.format( - CLIText.get().remoteRefObjectChangedIsNotExpectedOne - , rru.getExpectedOldObjectId().abbreviate(db).name()); + CLIText.get().remoteRefObjectChangedIsNotExpectedOne, + safeAbbreviate(reader, rru.getExpectedOldObjectId())); printUpdateLine('!', "[rejected]", srcRef, remoteName, message); break; @@ -243,6 +252,14 @@ class Push extends TextBuiltin { } } + private String safeAbbreviate(ObjectReader reader, ObjectId id) { + try { + return reader.abbreviate(id).name(); + } catch (IOException cannotAbbreviate) { + return id.name(); + } + } + private void printUpdateLine(final char flag, final String summary, final String srcRef, final String destRef, final String message) { out.format(" %c %-17s", flag, summary); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java index 996ee35a1..92d4fa114 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java @@ -74,6 +74,7 @@ public class DiffFormatterTest extends RepositoryTestCase { testDb = new TestRepository(db); df = new DiffFormatter(DisabledOutputStream.INSTANCE); df.setRepository(db); + df.setAbbreviationLength(8); } public void testCreateFileHeader_Modify() throws Exception { @@ -159,8 +160,8 @@ public class DiffFormatterTest extends RepositoryTestCase { private String makeDiffHeader(String pathA, String pathB, ObjectId aId, ObjectId bId) { - String a = aId.abbreviate(db).name(); - String b = bId.abbreviate(db).name(); + String a = aId.abbreviate(8).name(); + String b = bId.abbreviate(8).name(); return DIFF + "a/" + pathA + " " + "b/" + pathB + "\n" + // "index " + a + ".." + b + " " + REGULAR_FILE + "\n" + // "--- a/" + pathA + "\n" + // @@ -169,8 +170,8 @@ public class DiffFormatterTest extends RepositoryTestCase { private String makeDiffHeaderModeChange(String pathA, String pathB, ObjectId aId, ObjectId bId, String modeA, String modeB) { - String a = aId.abbreviate(db).name(); - String b = bId.abbreviate(db).name(); + String a = aId.abbreviate(8).name(); + String b = bId.abbreviate(8).name(); return DIFF + "a/" + pathA + " " + "b/" + pathB + "\n" + // "old mode " + modeA + "\n" + // "new mode " + modeB + "\n" + // @@ -182,5 +183,4 @@ public class DiffFormatterTest extends RepositoryTestCase { private ObjectId blob(String content) throws Exception { return testDb.blob(content).copy(); } - } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java index 45f8907da..9430a20e7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java @@ -348,4 +348,30 @@ public class AbbreviatedObjectIdTest extends TestCase { assertTrue(a.prefixCompare(i3) > 0); assertFalse(i3.startsWith(a)); } + + public void testIsId() { + // These are all too short. + assertFalse(AbbreviatedObjectId.isId("")); + assertFalse(AbbreviatedObjectId.isId("a")); + + // These are too long. + assertFalse(AbbreviatedObjectId.isId(ObjectId.fromString( + "7b6e8067ec86acef9a4184b43210d583b6d2f99a").name() + + "0")); + assertFalse(AbbreviatedObjectId.isId(ObjectId.fromString( + "7b6e8067ec86acef9a4184b43210d583b6d2f99a").name() + + "c0ffee")); + + // These contain non-hex characters. + assertFalse(AbbreviatedObjectId.isId("01notahexstring")); + + // These should all work. + assertTrue(AbbreviatedObjectId.isId("ab")); + assertTrue(AbbreviatedObjectId.isId("abc")); + assertTrue(AbbreviatedObjectId.isId("abcd")); + assertTrue(AbbreviatedObjectId.isId("abcd0")); + assertTrue(AbbreviatedObjectId.isId("abcd09")); + assertTrue(AbbreviatedObjectId.isId(ObjectId.fromString( + "7b6e8067ec86acef9a4184b43210d583b6d2f99a").name())); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/AbbreviationTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/AbbreviationTest.java new file mode 100644 index 000000000..5e8f72b5b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/AbbreviationTest.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.storage.file; + +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.transport.PackedObjectInfo; + +public class AbbreviationTest extends LocalDiskRepositoryTestCase { + private FileRepository db; + + private ObjectReader reader; + + private TestRepository test; + + public void setUp() throws Exception { + super.setUp(); + db = createBareRepository(); + reader = db.newObjectReader(); + test = new TestRepository(db); + } + + public void tearDown() throws Exception { + if (reader != null) + reader.release(); + } + + public void testAbbreviateOnEmptyRepository() throws IOException { + ObjectId id = id("9d5b926ed164e8ee88d3b8b1e525d699adda01ba"); + + assertEquals(id.abbreviate(2), reader.abbreviate(id, 2)); + assertEquals(id.abbreviate(7), reader.abbreviate(id, 7)); + assertEquals(id.abbreviate(8), reader.abbreviate(id, 8)); + assertEquals(id.abbreviate(10), reader.abbreviate(id, 10)); + assertEquals(id.abbreviate(16), reader.abbreviate(id, 16)); + + assertEquals(AbbreviatedObjectId.fromObjectId(id), // + reader.abbreviate(id, OBJECT_ID_STRING_LENGTH)); + + Collection matches; + + matches = reader.resolve(reader.abbreviate(id, 8)); + assertNotNull(matches); + assertEquals(0, matches.size()); + + matches = reader.resolve(AbbreviatedObjectId.fromObjectId(id)); + assertNotNull(matches); + assertEquals(1, matches.size()); + assertEquals(id, matches.iterator().next()); + } + + public void testAbbreviateLooseBlob() throws Exception { + ObjectId id = test.blob("test"); + + assertEquals(id.abbreviate(2), reader.abbreviate(id, 2)); + assertEquals(id.abbreviate(7), reader.abbreviate(id, 7)); + assertEquals(id.abbreviate(8), reader.abbreviate(id, 8)); + assertEquals(id.abbreviate(10), reader.abbreviate(id, 10)); + assertEquals(id.abbreviate(16), reader.abbreviate(id, 16)); + + Collection matches = reader.resolve(reader.abbreviate(id, 8)); + assertNotNull(matches); + assertEquals(1, matches.size()); + assertEquals(id, matches.iterator().next()); + + assertEquals(id, db.resolve(reader.abbreviate(id, 8).name())); + } + + public void testAbbreviatePackedBlob() throws Exception { + RevBlob id = test.blob("test"); + test.branch("master").commit().add("test", id).child(); + test.packAndPrune(); + assertTrue(reader.has(id)); + + assertEquals(id.abbreviate(7), reader.abbreviate(id, 7)); + assertEquals(id.abbreviate(8), reader.abbreviate(id, 8)); + assertEquals(id.abbreviate(10), reader.abbreviate(id, 10)); + assertEquals(id.abbreviate(16), reader.abbreviate(id, 16)); + + Collection matches = reader.resolve(reader.abbreviate(id, 8)); + assertNotNull(matches); + assertEquals(1, matches.size()); + assertEquals(id, matches.iterator().next()); + + assertEquals(id, db.resolve(reader.abbreviate(id, 8).name())); + } + + @SuppressWarnings("unchecked") + public void testAbbreviateIsActuallyUnique() throws Exception { + // This test is far more difficult. We have to manually craft + // an input that contains collisions at a particular prefix, + // but this is computationally difficult. Instead we force an + // index file to have what we want. + // + + ObjectId id = id("9d5b926ed164e8ee88d3b8b1e525d699adda01ba"); + byte[] idBuf = toByteArray(id); + List objects = new ArrayList(); + for (int i = 0; i < 256; i++) { + idBuf[9] = (byte) i; + objects.add(new PackedObjectInfo(ObjectId.fromRaw(idBuf))); + } + + String packName = "pack-" + id.name(); + File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); + File idxFile = new File(packDir, packName + ".idx"); + File packFile = new File(packDir, packName + ".pack"); + packDir.mkdir(); + OutputStream dst = new BufferedOutputStream(new FileOutputStream( + idxFile)); + try { + PackIndexWriter writer = new PackIndexWriterV2(dst); + writer.write(objects, new byte[OBJECT_ID_LENGTH]); + } finally { + dst.close(); + } + new FileOutputStream(packFile).close(); + db.openPack(packFile, idxFile); + + assertEquals(id.abbreviate(20), reader.abbreviate(id, 2)); + + Collection matches = reader.resolve(id.abbreviate(8)); + assertNotNull(matches); + assertEquals(objects.size(), matches.size()); + for (PackedObjectInfo info : objects) + assertTrue("contains " + info.name(), matches.contains(info)); + + assertNull("cannot resolve", db.resolve(id.abbreviate(8).name())); + assertEquals(id, db.resolve(id.abbreviate(20).name())); + } + + private static ObjectId id(String name) { + return ObjectId.fromString(name); + } + + private static byte[] toByteArray(ObjectId id) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(OBJECT_ID_LENGTH); + id.copyRawTo(buf); + return buf.toByteArray(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index bb4a77c42..bf49b8e84 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -63,6 +63,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.patch.HunkHeader; @@ -100,7 +101,7 @@ public class DiffFormatter { public DiffFormatter(OutputStream out) { this.out = out; setContext(3); - setAbbreviationLength(8); + setAbbreviationLength(7); } /** @return the stream we are outputting data to. */ @@ -327,10 +328,18 @@ public class DiffFormatter { o.write(encode("+++ " + newName + '\n')); } - private String format(AbbreviatedObjectId oldId) { - if (oldId.isComplete() && db != null) - oldId = oldId.toObjectId().abbreviate(db, abbreviationLength); - return oldId.name(); + private String format(AbbreviatedObjectId id) { + if (id.isComplete() && db != null) { + ObjectReader reader = db.newObjectReader(); + try { + id = reader.abbreviate(id.toObjectId(), abbreviationLength); + } catch (IOException cannotAbbreviate) { + // Ignore this. We'll report the full identity. + } finally { + reader.release(); + } + } + return id.name(); } private static String quotePath(String name) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java index 3f188fe0e..38937410d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java @@ -62,6 +62,27 @@ import org.eclipse.jgit.util.RawParseUtils; * efficient for matching against an object. */ public final class AbbreviatedObjectId { + /** + * Test a string of characters to verify it is a hex format. + *

+ * If true the string can be parsed with {@link #fromString(String)}. + * + * @param id + * the string to test. + * @return true if the string can converted into an AbbreviatedObjectId. + */ + public static final boolean isId(final String id) { + if (id.length() < 2 || Constants.OBJECT_ID_STRING_LENGTH < id.length()) + return false; + try { + for (int i = 0; i < id.length(); i++) + RawParseUtils.parseHexInt4((byte) id.charAt(i)); + return true; + } catch (ArrayIndexOutOfBoundsException e) { + return false; + } + } + /** * Convert an AbbreviatedObjectId from hex characters (US-ASCII). * @@ -205,7 +226,7 @@ public final class AbbreviatedObjectId { * >0 if this abbreviation names an object that is after * other. */ - public int prefixCompare(final AnyObjectId other) { + public final int prefixCompare(final AnyObjectId other) { int cmp; cmp = NB.compareUInt32(w1, mask(1, other.w1)); @@ -227,6 +248,83 @@ public final class AbbreviatedObjectId { return NB.compareUInt32(w5, mask(5, other.w5)); } + /** + * Compare this abbreviation to a network-byte-order ObjectId. + * + * @param bs + * array containing the other ObjectId in network byte order. + * @param p + * position within {@code bs} to start the compare at. At least + * 20 bytes, starting at this position are required. + * @return <0 if this abbreviation names an object that is less than + * other; 0 if this abbreviation exactly matches the + * first {@link #length()} digits of other.name(); + * >0 if this abbreviation names an object that is after + * other. + */ + public final int prefixCompare(final byte[] bs, final int p) { + int cmp; + + cmp = NB.compareUInt32(w1, mask(1, NB.decodeInt32(bs, p))); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt32(w2, mask(2, NB.decodeInt32(bs, p + 4))); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt32(w3, mask(3, NB.decodeInt32(bs, p + 8))); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt32(w4, mask(4, NB.decodeInt32(bs, p + 12))); + if (cmp != 0) + return cmp; + + return NB.compareUInt32(w5, mask(5, NB.decodeInt32(bs, p + 16))); + } + + /** + * Compare this abbreviation to a network-byte-order ObjectId. + * + * @param bs + * array containing the other ObjectId in network byte order. + * @param p + * position within {@code bs} to start the compare at. At least 5 + * ints, starting at this position are required. + * @return <0 if this abbreviation names an object that is less than + * other; 0 if this abbreviation exactly matches the + * first {@link #length()} digits of other.name(); + * >0 if this abbreviation names an object that is after + * other. + */ + public final int prefixCompare(final int[] bs, final int p) { + int cmp; + + cmp = NB.compareUInt32(w1, mask(1, bs[p])); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt32(w2, mask(2, bs[p + 1])); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt32(w3, mask(3, bs[p + 2])); + if (cmp != 0) + return cmp; + + cmp = NB.compareUInt32(w4, mask(4, bs[p + 3])); + if (cmp != 0) + return cmp; + + return NB.compareUInt32(w5, mask(5, bs[p + 4])); + } + + /** @return value for a fan-out style map, only valid of length >= 2. */ + public final int getFirstByte() { + return w1 >>> 24; + } + private int mask(final int word, final int v) { return mask(nibbles, word, v); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java index c6cb433aa..7b30cec46 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java @@ -463,32 +463,17 @@ public abstract class AnyObjectId implements Comparable { } /** - * Return unique abbreviation (prefix) of this object SHA-1. - *

- * This method is a utility for abbreviate(repo, 8). + * Return an abbreviation (prefix) of this object SHA-1. * - * @param repo - * repository for checking uniqueness within. - * @return SHA-1 abbreviation. - */ - public AbbreviatedObjectId abbreviate(final Repository repo) { - return abbreviate(repo, 8); - } - - /** - * Return unique abbreviation (prefix) of this object SHA-1. - *

- * Current implementation is not guaranteeing uniqueness, it just returns - * fixed-length prefix of SHA-1 string. + * This implementation does not guaranteeing uniqueness. Callers should + * instead use {@link ObjectReader#abbreviate(AnyObjectId, int)} to obtain a + * unique abbreviation within the scope of a particular object database. * - * @param repo - * repository for checking uniqueness within. * @param len - * minimum length of the abbreviated string. + * length of the abbreviated string. * @return SHA-1 abbreviation. */ - public AbbreviatedObjectId abbreviate(final Repository repo, final int len) { - // TODO implement checking for uniqueness + public AbbreviatedObjectId abbreviate(final int len) { final int a = AbbreviatedObjectId.mask(len, 1, w1); final int b = AbbreviatedObjectId.mask(len, 2, w2); final int c = AbbreviatedObjectId.mask(len, 3, w3); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index d4e866a22..cd3706bbe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -44,8 +44,10 @@ package org.eclipse.jgit.lib; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; @@ -74,6 +76,104 @@ public abstract class ObjectReader { */ public abstract ObjectReader newReader(); + /** + * Obtain a unique abbreviation (prefix) of an object SHA-1. + * + * This method uses a reasonable default for the minimum length. Callers who + * don't care about the minimum length should prefer this method. + * + * The returned abbreviation would expand back to the argument ObjectId when + * passed to {@link #resolve(AbbreviatedObjectId)}, assuming no new objects + * are added to this repository between calls. + * + * @param objectId + * object identity that needs to be abbreviated. + * @return SHA-1 abbreviation. + * @throws IOException + * the object store cannot be read. + */ + public AbbreviatedObjectId abbreviate(AnyObjectId objectId) + throws IOException { + return abbreviate(objectId, 7); + } + + /** + * Obtain a unique abbreviation (prefix) of an object SHA-1. + * + * The returned abbreviation would expand back to the argument ObjectId when + * passed to {@link #resolve(AbbreviatedObjectId)}, assuming no new objects + * are added to this repository between calls. + * + * The default implementation of this method abbreviates the id to the + * minimum length, then resolves it to see if there are multiple results. + * When multiple results are found, the length is extended by 1 and resolve + * is tried again. + * + * @param objectId + * object identity that needs to be abbreviated. + * @param len + * minimum length of the abbreviated string. Must be in the range + * [2, {@value Constants#OBJECT_ID_STRING_LENGTH}]. + * @return SHA-1 abbreviation. If no matching objects exist in the + * repository, the abbreviation will match the minimum length. + * @throws IOException + * the object store cannot be read. + */ + public AbbreviatedObjectId abbreviate(AnyObjectId objectId, int len) + throws IOException { + if (len == Constants.OBJECT_ID_STRING_LENGTH) + return AbbreviatedObjectId.fromObjectId(objectId); + + AbbreviatedObjectId abbrev = objectId.abbreviate(len); + Collection matches = resolve(abbrev); + while (1 < matches.size() && len < Constants.OBJECT_ID_STRING_LENGTH) { + abbrev = objectId.abbreviate(++len); + List n = new ArrayList(8); + for (ObjectId candidate : matches) { + if (abbrev.prefixCompare(candidate) == 0) + n.add(candidate); + } + if (1 < n.size()) + matches = n; + else + matches = resolve(abbrev); + } + return abbrev; + } + + /** + * Resolve an abbreviated ObjectId to its full form. + * + * This method searches for an ObjectId that begins with the abbreviation, + * and returns at least some matching candidates. + * + * If the returned collection is empty, no objects start with this + * abbreviation. The abbreviation doesn't belong to this repository, or the + * repository lacks the necessary objects to complete it. + * + * If the collection contains exactly one member, the abbreviation is + * (currently) unique within this database. There is a reasonably high + * probability that the returned id is what was previously abbreviated. + * + * If the collection contains 2 or more members, the abbreviation is not + * unique. In this case the implementation is only required to return at + * least 2 candidates to signal the abbreviation has conflicts. User + * friendly implementations should return as many candidates as reasonably + * possible, as the caller may be able to disambiguate further based on + * context. However since databases can be very large (e.g. 10 million + * objects) returning 625,000 candidates for the abbreviation "0" is simply + * unreasonable, so implementors should draw the line at around 256 matches. + * + * @param id + * abbreviated id to resolve to a complete identity. The + * abbreviation must have a length of at least 2. + * @return candidates that begin with the abbreviated identity. + * @throws IOException + * the object store cannot be read. + */ + public abstract Collection resolve(AbbreviatedObjectId id) + throws IOException; + /** * Does the requested object exist in this database? * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index c910889f6..b62f62b34 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -51,6 +51,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -373,6 +374,8 @@ public abstract class Repository { * Currently supported is combinations of these. *

    *
  • SHA-1 - a SHA-1
  • + *
  • SHA-1 abbreviation - a leading prefix of a SHA-1. At least the first + * two bytes must be supplied.
  • *
  • refs/... - a ref name
  • *
  • ref^n - nth parent reference
  • *
  • ref~n - distance via parent reference
  • @@ -383,8 +386,8 @@ public abstract class Repository { * * Not supported is: *
      + *
    • tag-NNN-gcommit - a non tagged revision from git describe
    • *
    • timestamps in reflogs, ref@{full or relative timestamp}
    • - *
    • abbreviated SHA-1's
    • *
    * * @param revstr @@ -569,8 +572,24 @@ public abstract class Repository { private ObjectId resolveSimple(final String revstr) throws IOException { if (ObjectId.isId(revstr)) return ObjectId.fromString(revstr); - final Ref r = getRefDatabase().getRef(revstr); - return r != null ? r.getObjectId() : null; + + Ref r = getRefDatabase().getRef(revstr); + if (r != null) + return r.getObjectId(); + + if (AbbreviatedObjectId.isId(revstr)) { + AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr); + ObjectReader reader = newObjectReader(); + try { + Collection matches = reader.resolve(id); + if (matches.size() == 1) + return matches.iterator().next(); + } finally { + reader.release(); + } + } + + return null; } /** Increment the use counter by one, requiring a matched {@link #close()}. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java index 8ea0b854c..bac8e7b42 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -46,7 +46,9 @@ package org.eclipse.jgit.storage.file; import java.io.File; import java.io.IOException; +import java.util.Set; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectDatabase; @@ -143,6 +145,17 @@ class CachedObjectDirectory extends FileObjectDatabase { return alts; } + @Override + void resolve(Set matches, AbbreviatedObjectId id) + throws IOException { + // In theory we could accelerate the loose object scan using our + // unpackedObjects map, but its not worth the huge code complexity. + // Scanning a single loose directory is fast enough, and this is + // unlikely to be called anyway. + // + wrapped.resolve(matches, id); + } + @Override boolean tryAgain1() { return wrapped.tryAgain1(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java index 250c7cac0..cb1fdb624 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -45,9 +45,12 @@ package org.eclipse.jgit.storage.file; import java.io.File; import java.io.IOException; +import java.util.Set; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.storage.pack.ObjectToPack; @@ -97,6 +100,9 @@ abstract class FileObjectDatabase extends ObjectDatabase { return false; } + abstract void resolve(Set matches, AbbreviatedObjectId id) + throws IOException; + /** * Open an object from this database. *

    diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 6fe4fd754..eb9be3855 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -65,8 +65,10 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.PackMismatchException; import org.eclipse.jgit.events.ConfigChangedEvent; import org.eclipse.jgit.events.ConfigChangedListener; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; @@ -100,6 +102,9 @@ public class ObjectDirectory extends FileObjectDatabase implements ConfigChangedListener { private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); + /** Maximum number of candidates offered as resolutions of abbreviation. */ + private static final int RESOLVE_ABBREV_LIMIT = 256; + private final Config config; private final File objects; @@ -280,6 +285,48 @@ public class ObjectDirectory extends FileObjectDatabase implements return false; } + void resolve(Set matches, AbbreviatedObjectId id) + throws IOException { + PackList pList = packList.get(); + if (pList == null) + pList = scanPacks(pList); + for (PackFile p : pList.packs) { + try { + p.resolve(matches, id, RESOLVE_ABBREV_LIMIT); + } catch (IOException e) { + // Assume the pack is corrupted. + // + removePack(p); + } + if (matches.size() > RESOLVE_ABBREV_LIMIT) + return; + } + + String fanOut = id.name().substring(0, 2); + String[] entries = new File(getDirectory(), fanOut).list(); + if (entries != null) { + for (String e : entries) { + if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) + continue; + try { + ObjectId entId = ObjectId.fromString(fanOut + e); + if (id.prefixCompare(entId) == 0) + matches.add(entId); + } catch (IllegalArgumentException notId) { + continue; + } + if (matches.size() > RESOLVE_ABBREV_LIMIT) + return; + } + } + + for (AlternateHandle alt : myAlternates()) { + alt.db.resolve(matches, id); + if (matches.size() > RESOLVE_ABBREV_LIMIT) + return; + } + } + ObjectLoader openObject1(final WindowCursor curs, final AnyObjectId objectId) throws IOException { PackList pList = packList.get(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java index e74a7c014..99637ee61 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -56,6 +56,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; +import java.util.Set; import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -66,6 +67,7 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackInvalidException; import org.eclipse.jgit.errors.PackMismatchException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -209,6 +211,11 @@ public class PackFile implements Iterable { return 0 < offset && !isCorrupt(offset) ? load(curs, offset) : null; } + void resolve(Set matches, AbbreviatedObjectId id, int matchLimit) + throws IOException { + idx().resolve(matches, id, matchLimit); + } + /** * Close the resources utilized by this repository */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java index 62d1c9d8f..fc1b748f5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java @@ -50,9 +50,11 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.text.MessageFormat; import java.util.Iterator; +import java.util.Set; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -249,6 +251,9 @@ public abstract class PackIndex implements Iterable { */ abstract boolean hasCRC32Support(); + abstract void resolve(Set matches, AbbreviatedObjectId id, + int matchLimit) throws IOException; + /** * Represent mutable entry of pack index consisting of object id and offset * in pack (both mutable). diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java index 3b68edc19..1c682f17d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java @@ -51,8 +51,10 @@ import java.io.InputStream; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Set; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -129,7 +131,7 @@ class PackIndexV1 extends PackIndex { base = levelOne > 0 ? idxHeader[levelOne - 1] : 0; final int p = (int) (nthPosition - base); - final int dataIdx = ((4 + Constants.OBJECT_ID_LENGTH) * p) + 4; + final int dataIdx = idOffset(p); return ObjectId.fromRaw(idxdata[levelOne], dataIdx); } @@ -142,7 +144,7 @@ class PackIndexV1 extends PackIndex { int low = 0; do { final int mid = (low + high) >>> 1; - final int pos = ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4; + final int pos = idOffset(mid); final int cmp = objId.compareTo(data, pos); if (cmp < 0) high = mid; @@ -172,6 +174,41 @@ class PackIndexV1 extends PackIndex { return new IndexV1Iterator(); } + @Override + void resolve(Set matches, AbbreviatedObjectId id, int matchLimit) + throws IOException { + byte[] data = idxdata[id.getFirstByte()]; + if (data == null) + return; + int max = data.length / (4 + Constants.OBJECT_ID_LENGTH); + int high = max; + int low = 0; + do { + int p = (low + high) >>> 1; + final int cmp = id.prefixCompare(data, idOffset(p)); + if (cmp < 0) + high = p; + else if (cmp == 0) { + // We may have landed in the middle of the matches. Move + // backwards to the start of matches, then walk forwards. + // + while (0 < p && id.prefixCompare(data, idOffset(p - 1)) == 0) + p--; + for (; p < max && id.prefixCompare(data, idOffset(p)) == 0; p++) { + matches.add(ObjectId.fromRaw(data, idOffset(p))); + if (matches.size() > matchLimit) + break; + } + return; + } else + low = p + 1; + } while (low < high); + } + + private static int idOffset(int mid) { + return ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4; + } + private class IndexV1Iterator extends EntriesIterator { private int levelOne; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java index cef7cc429..abc876766 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java @@ -48,9 +48,11 @@ import java.io.InputStream; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Set; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -221,6 +223,41 @@ class PackIndexV2 extends PackIndex { return new EntriesIteratorV2(); } + @Override + void resolve(Set matches, AbbreviatedObjectId id, int matchLimit) + throws IOException { + int[] data = names[id.getFirstByte()]; + int max = offset32[id.getFirstByte()].length >>> 2; + int high = max; + if (high == 0) + return; + int low = 0; + do { + int p = (low + high) >>> 1; + final int cmp = id.prefixCompare(data, idOffset(p)); + if (cmp < 0) + high = p; + else if (cmp == 0) { + // We may have landed in the middle of the matches. Move + // backwards to the start of matches, then walk forwards. + // + while (0 < p && id.prefixCompare(data, idOffset(p - 1)) == 0) + p--; + for (; p < max && id.prefixCompare(data, idOffset(p)) == 0; p++) { + matches.add(ObjectId.fromRaw(data, idOffset(p))); + if (matches.size() > matchLimit) + break; + } + return; + } else + low = p + 1; + } while (low < high); + } + + private static int idOffset(int p) { + return (p << 2) + p; // p * 5 + } + private int binarySearchLevelTwo(final AnyObjectId objId, final int levelOne) { final int[] data = names[levelOne]; int high = offset32[levelOne].length >>> 2; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 7bd0dc9f1..09db49e8f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -45,15 +45,20 @@ package org.eclipse.jgit.storage.file; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; @@ -83,6 +88,16 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { return new WindowCursor(db); } + @Override + public Collection resolve(AbbreviatedObjectId id) + throws IOException { + if (id.isComplete()) + return Collections.singleton(id.toObjectId()); + HashSet matches = new HashSet(4); + db.resolve(matches, id); + return matches; + } + public boolean has(AnyObjectId objectId) throws IOException { return db.has(objectId); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java index 37e03fd62..406767f84 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java @@ -353,8 +353,8 @@ public class RemoteRefUpdate { @Override public String toString() { return "RemoteRefUpdate[remoteName=" + remoteName + ", " + status - + ", " + (expectedOldObjectId!=null?expectedOldObjectId.abbreviate(localDb).name() :"(null)") - + "..." + (newObjectId != null ? newObjectId.abbreviate(localDb).name() : "(null)") + + ", " + (expectedOldObjectId!=null ? expectedOldObjectId.name() : "(null)") + + "..." + (newObjectId != null ? newObjectId.name() : "(null)") + (fastForward ? ", fastForward" : "") + ", srcRef=" + srcRef + (forceUpdate ? ", forceUpdate" : "") + ", message=" + (message != null ? "\"" + message + "\"" : "null") + "]";