View Javadoc
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 }