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());
}
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]";
}
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;
}
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 '!';
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;
addRef("(no branch)", head);
addRefs(refs, Constants.R_HEADS, !remote);
addRefs(refs, Constants.R_REMOTES, remote);
- for (final Entry<String, Ref> e : printRefs.entrySet()) {
- final Ref ref = e.getValue();
- printHead(e.getKey(), current.equals(ref.getName()), ref);
+
+ ObjectReader reader = db.newObjectReader();
+ try {
+ for (final Entry<String, Ref> e : printRefs.entrySet()) {
+ final Ref ref = e.getValue();
+ printHead(reader, e.getKey(),
+ current.equals(ref.getName()), ref);
+ }
+ } finally {
+ reader.release();
}
}
}
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);
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());
}
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;
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 {
} 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;
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;
}
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());
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));
} 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);
}
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;
}
}
+ 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);
testDb = new TestRepository(db);
df = new DiffFormatter(DisabledOutputStream.INSTANCE);
df.setRepository(db);
+ df.setAbbreviationLength(8);
}
public void testCreateFileHeader_Modify() throws Exception {
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" + //
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" + //
private ObjectId blob(String content) throws Exception {
return testDb.blob(content).copy();
}
-
}
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()));
+ }
}
--- /dev/null
+/*
+ * 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<FileRepository> test;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ db = createBareRepository();
+ reader = db.newObjectReader();
+ test = new TestRepository<FileRepository>(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<ObjectId> 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<ObjectId> 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<ObjectId> 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<PackedObjectInfo> objects = new ArrayList<PackedObjectInfo>();
+ 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<ObjectId> 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();
+ }
+}
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;
public DiffFormatter(OutputStream out) {
this.out = out;
setContext(3);
- setAbbreviationLength(8);
+ setAbbreviationLength(7);
}
/** @return the stream we are outputting data to. */
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) {
* efficient for matching against an object.
*/
public final class AbbreviatedObjectId {
+ /**
+ * Test a string of characters to verify it is a hex format.
+ * <p>
+ * 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).
*
* >0 if this abbreviation names an object that is after
* <code>other</code>.
*/
- public int prefixCompare(final AnyObjectId other) {
+ public final int prefixCompare(final AnyObjectId other) {
int cmp;
cmp = NB.compareUInt32(w1, mask(1, other.w1));
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
+ * <code>other</code>; 0 if this abbreviation exactly matches the
+ * first {@link #length()} digits of <code>other.name()</code>;
+ * >0 if this abbreviation names an object that is after
+ * <code>other</code>.
+ */
+ 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
+ * <code>other</code>; 0 if this abbreviation exactly matches the
+ * first {@link #length()} digits of <code>other.name()</code>;
+ * >0 if this abbreviation names an object that is after
+ * <code>other</code>.
+ */
+ 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);
}
}
/**
- * Return unique abbreviation (prefix) of this object SHA-1.
- * <p>
- * This method is a utility for <code>abbreviate(repo, 8)</code>.
+ * 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.
- * <p>
- * 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);
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;
*/
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<ObjectId> matches = resolve(abbrev);
+ while (1 < matches.size() && len < Constants.OBJECT_ID_STRING_LENGTH) {
+ abbrev = objectId.abbreviate(++len);
+ List<ObjectId> n = new ArrayList<ObjectId>(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<ObjectId> resolve(AbbreviatedObjectId id)
+ throws IOException;
+
/**
* Does the requested object exist in this database?
*
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;
* Currently supported is combinations of these.
* <ul>
* <li>SHA-1 - a SHA-1</li>
+ * <li>SHA-1 abbreviation - a leading prefix of a SHA-1. At least the first
+ * two bytes must be supplied.</li>
* <li>refs/... - a ref name</li>
* <li>ref^n - nth parent reference</li>
* <li>ref~n - distance via parent reference</li>
*
* Not supported is:
* <ul>
+ * <li>tag-NNN-gcommit - a non tagged revision from git describe</li>
* <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
- * <li>abbreviated SHA-1's</li>
* </ul>
*
* @param revstr
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<ObjectId> 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()}. */
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;
return alts;
}
+ @Override
+ void resolve(Set<ObjectId> 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();
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;
return false;
}
+ abstract void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
+ throws IOException;
+
/**
* Open an object from this database.
* <p>
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;
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;
return false;
}
+ void resolve(Set<ObjectId> 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();
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;
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;
return 0 < offset && !isCorrupt(offset) ? load(curs, offset) : null;
}
+ void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit)
+ throws IOException {
+ idx().resolve(matches, id, matchLimit);
+ }
+
/**
* Close the resources utilized by this repository
*/
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;
*/
abstract boolean hasCRC32Support();
+ abstract void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
+ int matchLimit) throws IOException;
+
/**
* Represent mutable entry of pack index consisting of object id and offset
* in pack (both mutable).
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;
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);
}
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;
return new IndexV1Iterator();
}
+ @Override
+ void resolve(Set<ObjectId> 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;
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;
return new EntriesIteratorV2();
}
+ @Override
+ void resolve(Set<ObjectId> 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;
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;
return new WindowCursor(db);
}
+ @Override
+ public Collection<ObjectId> resolve(AbbreviatedObjectId id)
+ throws IOException {
+ if (id.isComplete())
+ return Collections.singleton(id.toObjectId());
+ HashSet<ObjectId> matches = new HashSet<ObjectId>(4);
+ db.resolve(matches, id);
+ return matches;
+ }
+
public boolean has(AnyObjectId objectId) throws IOException {
return db.has(objectId);
}
@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") + "]";