1 /*
2 * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others
3 *
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Distribution License v. 1.0 which is available at
6 * https://www.eclipse.org/org/documents/edl-v10.php.
7 *
8 * SPDX-License-Identifier: BSD-3-Clause
9 */
10 package org.eclipse.jgit.lib;
11
12 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
13 import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
14
15 import java.text.MessageFormat;
16
17 import org.eclipse.jgit.api.errors.InvalidConfigurationException;
18 import org.eclipse.jgit.internal.JGitText;
19
20 /**
21 * Git configuration option <a
22 * href=https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev">
23 * core.abbrev</a>
24 *
25 * @since 6.1
26 */
27 public final class AbbrevConfig {
28 private static final String VALUE_NO = "no"; //$NON-NLS-1$
29
30 private static final String VALUE_AUTO = "auto"; //$NON-NLS-1$
31
32 /**
33 * The minimum value of abbrev
34 */
35 public static final int MIN_ABBREV = 4;
36
37 /**
38 * Cap configured core.abbrev to range between minimum of 4 and number of
39 * hex-digits of a full object id.
40 *
41 * @param len
42 * configured number of hex-digits to abbreviate object ids to
43 * @return core.abbrev capped to range between minimum of 4 and number of
44 * hex-digits of a full object id
45 */
46 public static int capAbbrev(int len) {
47 return Math.min(Math.max(MIN_ABBREV, len),
48 Constants.OBJECT_ID_STRING_LENGTH);
49 }
50
51 /**
52 * No abbreviation
53 */
54 public final static AbbrevConfig NO = new AbbrevConfig(
55 Constants.OBJECT_ID_STRING_LENGTH);
56
57 /**
58 * Parse string value of core.abbrev git option for a given repository
59 *
60 * @param repo
61 * repository
62 * @return the parsed AbbrevConfig
63 * @throws InvalidConfigurationException
64 * if value of core.abbrev is invalid
65 */
66 public static AbbrevConfig parseFromConfig(Repository repo)
67 throws InvalidConfigurationException {
68 Config config = repo.getConfig();
69 String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
70 null, ConfigConstants.CONFIG_KEY_ABBREV);
71 if (value == null || value.equalsIgnoreCase(VALUE_AUTO)) {
72 return auto(repo);
73 }
74 if (value.equalsIgnoreCase(VALUE_NO)) {
75 return NO;
76 }
77 try {
78 int len = config.getIntInRange(ConfigConstants.CONFIG_CORE_SECTION,
79 ConfigConstants.CONFIG_KEY_ABBREV, MIN_ABBREV,
80 Constants.OBJECT_ID_STRING_LENGTH, UNSET_INT);
81 if (len == UNSET_INT) {
82 // Unset was checked above. If we get UNSET_INT here, then
83 // either the value was UNSET_INT, or it was an invalid value
84 // (not an integer, or out of range), and EGit's
85 // ReportingTypedGetter caught the exception and has logged a
86 // warning. In either case we should fall back to some sane
87 // default.
88 len = OBJECT_ID_ABBREV_STRING_LENGTH;
89 }
90 return new AbbrevConfig(len);
91 } catch (IllegalArgumentException e) {
92 throw new InvalidConfigurationException(MessageFormat
93 .format(JGitText.get().invalidCoreAbbrev, value), e);
94 }
95 }
96
97 /**
98 * An appropriate value is computed based on the approximate number of
99 * packed objects in a repository, which hopefully is enough for abbreviated
100 * object names to stay unique for some time.
101 *
102 * @param repo
103 * @return appropriate value computed based on the approximate number of
104 * packed objects in a repository
105 */
106 private static AbbrevConfig auto(Repository repo) {
107 long count = repo.getObjectDatabase().getApproximateObjectCount();
108 if (count == -1) {
109 return new AbbrevConfig(OBJECT_ID_ABBREV_STRING_LENGTH);
110 }
111 // find msb, round to next power of 2
112 int len = 63 - Long.numberOfLeadingZeros(count) + 1;
113 // With the order of 2^len objects, we expect a collision at
114 // 2^(len/2). But we also care about hex chars, not bits, and
115 // there are 4 bits per hex. So all together we need to divide
116 // by 2; but we also want to round odd numbers up, hence adding
117 // one before dividing.
118 len = (len + 1) / 2;
119 // for small repos use at least fallback length
120 return new AbbrevConfig(Math.max(len, OBJECT_ID_ABBREV_STRING_LENGTH));
121 }
122
123 /**
124 * All other possible abbreviation lengths. Valid range 4 to number of
125 * hex-digits of an unabbreviated object id (40 for SHA1 object ids, jgit
126 * doesn't support SHA256 yet).
127 */
128 private int abbrev;
129
130 /**
131 * @param abbrev
132 */
133 private AbbrevConfig(int abbrev) {
134 this.abbrev = capAbbrev(abbrev);
135 }
136
137 /**
138 * Get the configured abbreviation length for object ids.
139 *
140 * @return the configured abbreviation length for object ids
141 */
142 public int get() {
143 return abbrev;
144 }
145
146 @Override
147 public String toString() {
148 return Integer.toString(abbrev);
149 }
150 }