Describe: add support for core.abbrev config option
If core.abbrev is unset or "auto" estimate abbreviation length like C git does: - Estimate repository's object count by only considering packed objects, round up to next power of 2 - With the order of 2^len objects, we expect a collision at 2^(len/2). But we also care about hex chars, not bits, and there are 4 bits per hex. So all together we need to divide by 2; but we also want to round odd numbers up, hence adding one before dividing. - For small repos use at least 7 hexdigits - If object database fails to determine object count use 7 hexdigits as fallback If it is set to "no" do not abbreviate object-ids. Otherwise set it to the configured value capped to the range between 4 and length of an unabbreviated object-id. Change-Id: I425f9724b69813dbb57872466bf2d2e1d6dc72c6
This commit is contained in:
parent
9244c07d73
commit
6f175ea6c4
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||
* https://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
package org.eclipse.jgit.lib;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jgit.api.errors.InvalidConfigurationException;
|
||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||
import org.eclipse.jgit.storage.file.FileBasedConfig;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AbbrevConfigTest extends RepositoryTestCase {
|
||||
|
||||
@Test
|
||||
public void testDefault() throws Exception {
|
||||
assertEquals(7, testCoreAbbrev(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuto() throws Exception {
|
||||
assertEquals(7, testCoreAbbrev("auto"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNo() throws Exception {
|
||||
assertEquals(40, testCoreAbbrev("no"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidMin() throws Exception {
|
||||
assertEquals(4, testCoreAbbrev("4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValid() throws Exception {
|
||||
assertEquals(22, testCoreAbbrev("22"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidMax() throws Exception {
|
||||
assertEquals(40, testCoreAbbrev("40"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalid() {
|
||||
assertThrows(InvalidConfigurationException.class,
|
||||
() -> testCoreAbbrev("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalid2() {
|
||||
assertThrows(InvalidConfigurationException.class,
|
||||
() -> testCoreAbbrev("2k"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidNegative() {
|
||||
assertThrows(InvalidConfigurationException.class,
|
||||
() -> testCoreAbbrev("-1000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidBelowRange() {
|
||||
assertThrows(InvalidConfigurationException.class,
|
||||
() -> testCoreAbbrev("3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidBelowRange2() {
|
||||
assertThrows(InvalidConfigurationException.class,
|
||||
() -> testCoreAbbrev("-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidAboveRange() {
|
||||
assertThrows(InvalidConfigurationException.class,
|
||||
() -> testCoreAbbrev("41"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidAboveRange2() {
|
||||
assertThrows(InvalidConfigurationException.class,
|
||||
() -> testCoreAbbrev("100000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringNo()
|
||||
throws InvalidConfigurationException, IOException {
|
||||
assertEquals("40", setCoreAbbrev("no").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString()
|
||||
throws InvalidConfigurationException, IOException {
|
||||
assertEquals("7", setCoreAbbrev("auto").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString12()
|
||||
throws InvalidConfigurationException, IOException {
|
||||
assertEquals("12", setCoreAbbrev("12").toString());
|
||||
}
|
||||
|
||||
private int testCoreAbbrev(String value)
|
||||
throws InvalidConfigurationException, IOException {
|
||||
return setCoreAbbrev(value).get();
|
||||
}
|
||||
|
||||
private AbbrevConfig setCoreAbbrev(String value)
|
||||
throws IOException, InvalidConfigurationException {
|
||||
FileBasedConfig config = db.getConfig();
|
||||
config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||||
ConfigConstants.CONFIG_KEY_ABBREV, value);
|
||||
config.save();
|
||||
return AbbrevConfig.parseFromConfig(db);
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,22 @@
|
|||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/lib/ObjectDatabase.java" type="org.eclipse.jgit.lib.ObjectDatabase">
|
||||
<filter id="336695337">
|
||||
<message_arguments>
|
||||
<message_argument value="org.eclipse.jgit.lib.ObjectDatabase"/>
|
||||
<message_argument value="getApproximateObjectCount()"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/lib/TypedConfigGetter.java" type="org.eclipse.jgit.lib.TypedConfigGetter">
|
||||
<filter id="403804204">
|
||||
<message_arguments>
|
||||
<message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
|
||||
<message_argument value="getIntInRange(Config, String, String, String, int, int, int)"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/transport/BasePackPushConnection.java" type="org.eclipse.jgit.transport.BasePackPushConnection">
|
||||
<filter id="338792546">
|
||||
<message_arguments>
|
||||
|
|
|
@ -366,6 +366,7 @@ invalidAncestryLength=Invalid ancestry length
|
|||
invalidBooleanValue=Invalid boolean value: {0}.{1}={2}
|
||||
invalidChannel=Invalid channel {0}
|
||||
invalidCommitParentNumber=Invalid commit parent number
|
||||
invalidCoreAbbrev=Invalid value {0} of option core.abbrev
|
||||
invalidDepth=Invalid depth: {0}
|
||||
invalidEncoding=Invalid encoding from git config i18n.commitEncoding: {0}
|
||||
invalidEncryption=Invalid encryption
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
*/
|
||||
package org.eclipse.jgit.api;
|
||||
|
||||
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
|
||||
import static org.eclipse.jgit.lib.Constants.R_REFS;
|
||||
import static org.eclipse.jgit.lib.Constants.R_TAGS;
|
||||
import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
|
@ -34,6 +34,7 @@
|
|||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.fnmatch.FileNameMatcher;
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
import org.eclipse.jgit.lib.AbbrevConfig;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
|
@ -92,7 +93,7 @@ public class DescribeCommand extends GitCommand<String> {
|
|||
/**
|
||||
* The prefix length to use when abbreviating a commit hash.
|
||||
*/
|
||||
private int abbrev = OBJECT_ID_ABBREV_STRING_LENGTH;
|
||||
private int abbrev = UNSET_INT;
|
||||
|
||||
/**
|
||||
* Constructor for DescribeCommand.
|
||||
|
@ -216,12 +217,17 @@ public DescribeCommand setAlways(boolean always) {
|
|||
*
|
||||
* @param abbrev
|
||||
* minimum length of the abbreviated string. Must be in the range
|
||||
* [2, {@value Constants#OBJECT_ID_STRING_LENGTH}].
|
||||
* [{@value AbbrevConfig#MIN_ABBREV},
|
||||
* {@value Constants#OBJECT_ID_STRING_LENGTH}].
|
||||
* @return {@code this}
|
||||
* @since 6.1
|
||||
*/
|
||||
public DescribeCommand setAbbrev(int abbrev) {
|
||||
this.abbrev = abbrev;
|
||||
if (abbrev == 0) {
|
||||
this.abbrev = 0;
|
||||
} else {
|
||||
this.abbrev = AbbrevConfig.capAbbrev(abbrev);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -232,13 +238,7 @@ private String longDescription(Ref tag, int depth, ObjectId tip)
|
|||
}
|
||||
return String.format("%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
|
||||
Integer.valueOf(depth),
|
||||
w.getObjectReader().abbreviate(tip, getCappedAbbrev()).name());
|
||||
}
|
||||
|
||||
private int getCappedAbbrev() {
|
||||
int len = Math.max(abbrev, 4);
|
||||
len = Math.min(len, Constants.OBJECT_ID_STRING_LENGTH);
|
||||
return len;
|
||||
w.getObjectReader().abbreviate(tip, abbrev).name());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -330,6 +330,9 @@ public String call() throws GitAPIException {
|
|||
if (target == null) {
|
||||
setTarget(Constants.HEAD);
|
||||
}
|
||||
if (abbrev == UNSET_INT) {
|
||||
abbrev = AbbrevConfig.parseFromConfig(repo).get();
|
||||
}
|
||||
|
||||
Collection<Ref> tagList = repo.getRefDatabase()
|
||||
.getRefsByPrefix(useAll ? R_REFS : R_TAGS);
|
||||
|
@ -443,7 +446,8 @@ String describe(ObjectId tip) throws IOException {
|
|||
if (candidates.isEmpty()) {
|
||||
return always
|
||||
? w.getObjectReader()
|
||||
.abbreviate(target, getCappedAbbrev())
|
||||
.abbreviate(target,
|
||||
AbbrevConfig.capAbbrev(abbrev))
|
||||
.name()
|
||||
: null;
|
||||
}
|
||||
|
|
|
@ -394,6 +394,7 @@ public static JGitText get() {
|
|||
/***/ public String invalidBooleanValue;
|
||||
/***/ public String invalidChannel;
|
||||
/***/ public String invalidCommitParentNumber;
|
||||
/***/ public String invalidCoreAbbrev;
|
||||
/***/ public String invalidDepth;
|
||||
/***/ public String invalidEncoding;
|
||||
/***/ public String invalidEncryption;
|
||||
|
|
|
@ -165,6 +165,15 @@ public void flush() {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getApproximateObjectCount() {
|
||||
long count = 0;
|
||||
for (DfsPackDescription p : packs) {
|
||||
count += p.getObjectCount();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MemPack extends DfsPackDescription {
|
||||
|
|
|
@ -263,4 +263,17 @@ private static class UnpackedObjectId extends ObjectIdOwnerMap.Entry {
|
|||
private AlternateHandle.Id getAlternateId() {
|
||||
return wrapped.getAlternateId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getApproximateObjectCount() {
|
||||
long count = 0;
|
||||
for (Pack p : getPacks()) {
|
||||
try {
|
||||
count += p.getObjectCount();
|
||||
} catch (IOException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -212,6 +212,20 @@ public Collection<Pack> getPacks() {
|
|||
return packed.getPacks();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public long getApproximateObjectCount() {
|
||||
long count = 0;
|
||||
for (Pack p : getPacks()) {
|
||||
try {
|
||||
count += p.getIndex().getObjectCount();
|
||||
} catch (IOException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||
* https://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
package org.eclipse.jgit.lib;
|
||||
|
||||
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
|
||||
import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import org.eclipse.jgit.api.errors.InvalidConfigurationException;
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
|
||||
/**
|
||||
* Git configuration option <a
|
||||
* href=https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev">
|
||||
* core.abbrev</a>
|
||||
*
|
||||
* @since 6.1
|
||||
*/
|
||||
public final class AbbrevConfig {
|
||||
private static final String VALUE_NO = "no"; //$NON-NLS-1$
|
||||
|
||||
private static final String VALUE_AUTO = "auto"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* The minimum value of abbrev
|
||||
*/
|
||||
public static final int MIN_ABBREV = 4;
|
||||
|
||||
/**
|
||||
* Cap configured core.abbrev to range between minimum of 4 and number of
|
||||
* hex-digits of a full object id.
|
||||
*
|
||||
* @param len
|
||||
* configured number of hex-digits to abbreviate object ids to
|
||||
* @return core.abbrev capped to range between minimum of 4 and number of
|
||||
* hex-digits of a full object id
|
||||
*/
|
||||
public static int capAbbrev(int len) {
|
||||
return Math.min(Math.max(MIN_ABBREV, len),
|
||||
Constants.OBJECT_ID_STRING_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* No abbreviation
|
||||
*/
|
||||
public final static AbbrevConfig NO = new AbbrevConfig(
|
||||
Constants.OBJECT_ID_STRING_LENGTH);
|
||||
|
||||
/**
|
||||
* Parse string value of core.abbrev git option for a given repository
|
||||
*
|
||||
* @param repo
|
||||
* repository
|
||||
* @return the parsed AbbrevConfig
|
||||
* @throws InvalidConfigurationException
|
||||
* if value of core.abbrev is invalid
|
||||
*/
|
||||
public static AbbrevConfig parseFromConfig(Repository repo)
|
||||
throws InvalidConfigurationException {
|
||||
Config config = repo.getConfig();
|
||||
String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
|
||||
null, ConfigConstants.CONFIG_KEY_ABBREV);
|
||||
if (value == null || value.equalsIgnoreCase(VALUE_AUTO)) {
|
||||
return auto(repo);
|
||||
}
|
||||
if (value.equalsIgnoreCase(VALUE_NO)) {
|
||||
return NO;
|
||||
}
|
||||
try {
|
||||
int len = config.getIntInRange(ConfigConstants.CONFIG_CORE_SECTION,
|
||||
ConfigConstants.CONFIG_KEY_ABBREV, MIN_ABBREV,
|
||||
Constants.OBJECT_ID_STRING_LENGTH, UNSET_INT);
|
||||
if (len == UNSET_INT) {
|
||||
// Unset was checked above. If we get UNSET_INT here, then
|
||||
// either the value was UNSET_INT, or it was an invalid value
|
||||
// (not an integer, or out of range), and EGit's
|
||||
// ReportingTypedGetter caught the exception and has logged a
|
||||
// warning. In either case we should fall back to some sane
|
||||
// default.
|
||||
len = OBJECT_ID_ABBREV_STRING_LENGTH;
|
||||
}
|
||||
return new AbbrevConfig(len);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidConfigurationException(MessageFormat
|
||||
.format(JGitText.get().invalidCoreAbbrev, value), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An appropriate value is computed based on the approximate number of
|
||||
* packed objects in a repository, which hopefully is enough for abbreviated
|
||||
* object names to stay unique for some time.
|
||||
*
|
||||
* @param repo
|
||||
* @return appropriate value computed based on the approximate number of
|
||||
* packed objects in a repository
|
||||
*/
|
||||
private static AbbrevConfig auto(Repository repo) {
|
||||
long count = repo.getObjectDatabase().getApproximateObjectCount();
|
||||
if (count == -1) {
|
||||
return new AbbrevConfig(OBJECT_ID_ABBREV_STRING_LENGTH);
|
||||
}
|
||||
// find msb, round to next power of 2
|
||||
int len = 63 - Long.numberOfLeadingZeros(count) + 1;
|
||||
// With the order of 2^len objects, we expect a collision at
|
||||
// 2^(len/2). But we also care about hex chars, not bits, and
|
||||
// there are 4 bits per hex. So all together we need to divide
|
||||
// by 2; but we also want to round odd numbers up, hence adding
|
||||
// one before dividing.
|
||||
len = (len + 1) / 2;
|
||||
// for small repos use at least fallback length
|
||||
return new AbbrevConfig(Math.max(len, OBJECT_ID_ABBREV_STRING_LENGTH));
|
||||
}
|
||||
|
||||
/**
|
||||
* All other possible abbreviation lengths. Valid range 4 to number of
|
||||
* hex-digits of an unabbreviated object id (40 for SHA1 object ids, jgit
|
||||
* doesn't support SHA256 yet).
|
||||
*/
|
||||
private int abbrev;
|
||||
|
||||
/**
|
||||
* @param abbrev
|
||||
*/
|
||||
private AbbrevConfig(int abbrev) {
|
||||
this.abbrev = capAbbrev(abbrev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured abbreviation length for object ids.
|
||||
*
|
||||
* @return the configured abbreviation length for object ids
|
||||
*/
|
||||
public int get() {
|
||||
return abbrev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(abbrev);
|
||||
}
|
||||
}
|
|
@ -277,6 +277,54 @@ public int getInt(final String section, String subsection,
|
|||
defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an integer value from the configuration which must be inside given
|
||||
* range.
|
||||
*
|
||||
* @param section
|
||||
* section the key is grouped within.
|
||||
* @param name
|
||||
* name of the key to get.
|
||||
* @param minValue
|
||||
* minimum value
|
||||
* @param maxValue
|
||||
* maximum value
|
||||
* @param defaultValue
|
||||
* default value to return if no value was present.
|
||||
* @return an integer value from the configuration, or defaultValue.
|
||||
* @since 6.1
|
||||
*/
|
||||
public int getIntInRange(String section, String name, int minValue,
|
||||
int maxValue, int defaultValue) {
|
||||
return typedGetter.getIntInRange(this, section, null, name, minValue,
|
||||
maxValue, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an integer value from the configuration which must be inside given
|
||||
* range.
|
||||
*
|
||||
* @param section
|
||||
* section the key is grouped within.
|
||||
* @param subsection
|
||||
* subsection name, such a remote or branch name.
|
||||
* @param name
|
||||
* name of the key to get.
|
||||
* @param minValue
|
||||
* minimum value
|
||||
* @param maxValue
|
||||
* maximum value
|
||||
* @param defaultValue
|
||||
* default value to return if no value was present.
|
||||
* @return an integer value from the configuration, or defaultValue.
|
||||
* @since 6.1
|
||||
*/
|
||||
public int getIntInRange(String section, String subsection, String name,
|
||||
int minValue, int maxValue, int defaultValue) {
|
||||
return typedGetter.getIntInRange(this, section, subsection, name,
|
||||
minValue, maxValue, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an integer value from the configuration.
|
||||
*
|
||||
|
|
|
@ -836,4 +836,11 @@ public final class ConfigConstants {
|
|||
*/
|
||||
public static final String CONFIG_KEY_DEFAULT = "default";
|
||||
|
||||
/**
|
||||
* The "abbrev" key
|
||||
*
|
||||
* @since 6.1
|
||||
*/
|
||||
public static final String CONFIG_KEY_ABBREV = "abbrev";
|
||||
|
||||
}
|
||||
|
|
|
@ -155,4 +155,14 @@ public ObjectLoader open(AnyObjectId objectId, int typeHint)
|
|||
public ObjectDatabase newCachedDatabase() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a quick, rough count of objects in this repository. Ignores loose
|
||||
* objects. Returns {@code -1} if an exception occurs.
|
||||
*
|
||||
* @return quick, rough count of objects in this repository, {@code -1} if
|
||||
* an exception occurs
|
||||
* @since 6.1
|
||||
*/
|
||||
public abstract long getApproximateObjectCount();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue