You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Config.java 42KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489
  1. /*
  2. * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
  3. * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
  4. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  5. * Copyright (C) 2008-2010, Google Inc.
  6. * Copyright (C) 2009, Google, Inc.
  7. * Copyright (C) 2009, JetBrains s.r.o.
  8. * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  9. * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
  10. * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>
  11. * and other copyright owners as documented in the project's IP log.
  12. *
  13. * This program and the accompanying materials are made available
  14. * under the terms of the Eclipse Distribution License v1.0 which
  15. * accompanies this distribution, is reproduced below, and is
  16. * available at http://www.eclipse.org/org/documents/edl-v10.php
  17. *
  18. * All rights reserved.
  19. *
  20. * Redistribution and use in source and binary forms, with or
  21. * without modification, are permitted provided that the following
  22. * conditions are met:
  23. *
  24. * - Redistributions of source code must retain the above copyright
  25. * notice, this list of conditions and the following disclaimer.
  26. *
  27. * - Redistributions in binary form must reproduce the above
  28. * copyright notice, this list of conditions and the following
  29. * disclaimer in the documentation and/or other materials provided
  30. * with the distribution.
  31. *
  32. * - Neither the name of the Eclipse Foundation, Inc. nor the
  33. * names of its contributors may be used to endorse or promote
  34. * products derived from this software without specific prior
  35. * written permission.
  36. *
  37. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  38. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  39. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  40. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  41. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  42. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  43. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  44. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  45. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  46. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  47. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  48. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  49. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  50. */
  51. package org.eclipse.jgit.lib;
  52. import java.text.MessageFormat;
  53. import java.util.ArrayList;
  54. import java.util.Collections;
  55. import java.util.List;
  56. import java.util.Locale;
  57. import java.util.Set;
  58. import java.util.concurrent.TimeUnit;
  59. import java.util.concurrent.atomic.AtomicReference;
  60. import org.eclipse.jgit.annotations.Nullable;
  61. import org.eclipse.jgit.errors.ConfigInvalidException;
  62. import org.eclipse.jgit.events.ConfigChangedEvent;
  63. import org.eclipse.jgit.events.ConfigChangedListener;
  64. import org.eclipse.jgit.events.ListenerHandle;
  65. import org.eclipse.jgit.events.ListenerList;
  66. import org.eclipse.jgit.internal.JGitText;
  67. import org.eclipse.jgit.transport.RefSpec;
  68. import org.eclipse.jgit.util.RawParseUtils;
  69. /**
  70. * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
  71. */
  72. public class Config {
  73. private static final String[] EMPTY_STRING_ARRAY = {};
  74. static final long KiB = 1024;
  75. static final long MiB = 1024 * KiB;
  76. static final long GiB = 1024 * MiB;
  77. private static final int MAX_DEPTH = 10;
  78. private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter();
  79. private static TypedConfigGetter typedGetter = DEFAULT_GETTER;
  80. /** the change listeners */
  81. private final ListenerList listeners = new ListenerList();
  82. /**
  83. * Immutable current state of the configuration data.
  84. * <p>
  85. * This state is copy-on-write. It should always contain an immutable list
  86. * of the configuration keys/values.
  87. */
  88. private final AtomicReference<ConfigSnapshot> state;
  89. private final Config baseConfig;
  90. /**
  91. * Magic value indicating a missing entry.
  92. * <p>
  93. * This value is tested for reference equality in some contexts, so we
  94. * must ensure it is a special copy of the empty string. It also must
  95. * be treated like the empty string.
  96. */
  97. static final String MAGIC_EMPTY_VALUE = new String();
  98. /**
  99. * Create a configuration with no default fallback.
  100. */
  101. public Config() {
  102. this(null);
  103. }
  104. /**
  105. * Create an empty configuration with a fallback for missing keys.
  106. *
  107. * @param defaultConfig
  108. * the base configuration to be consulted when a key is missing
  109. * from this configuration instance.
  110. */
  111. public Config(Config defaultConfig) {
  112. baseConfig = defaultConfig;
  113. state = new AtomicReference<>(newState());
  114. }
  115. /**
  116. * Globally sets a {@link org.eclipse.jgit.lib.TypedConfigGetter} that is
  117. * subsequently used to read typed values from all git configs.
  118. *
  119. * @param getter
  120. * to use; if {@code null} use the default getter.
  121. * @since 4.9
  122. */
  123. public static void setTypedConfigGetter(TypedConfigGetter getter) {
  124. typedGetter = getter == null ? DEFAULT_GETTER : getter;
  125. }
  126. /**
  127. * Escape the value before saving
  128. *
  129. * @param x
  130. * the value to escape
  131. * @return the escaped value
  132. */
  133. static String escapeValue(String x) {
  134. if (x.isEmpty()) {
  135. return ""; //$NON-NLS-1$
  136. }
  137. boolean needQuote = x.charAt(0) == ' ' || x.charAt(x.length() - 1) == ' ';
  138. StringBuilder r = new StringBuilder(x.length());
  139. for (int k = 0; k < x.length(); k++) {
  140. char c = x.charAt(k);
  141. // git-config(1) lists the limited set of supported escape sequences, but
  142. // the documentation is otherwise not especially normative. In particular,
  143. // which ones of these produce and/or require escaping and/or quoting
  144. // around them is not documented and was discovered by trial and error.
  145. // In summary:
  146. //
  147. // * Quotes are only required if there is leading/trailing whitespace or a
  148. // comment character.
  149. // * Bytes that have a supported escape sequence are escaped, except for
  150. // \b for some reason which isn't.
  151. // * Needing an escape sequence is not sufficient reason to quote the
  152. // value.
  153. switch (c) {
  154. case '\0':
  155. // Unix command line calling convention cannot pass a '\0' as an
  156. // argument, so there is no equivalent way in C git to store a null byte
  157. // in a config value.
  158. throw new IllegalArgumentException(
  159. JGitText.get().configValueContainsNullByte);
  160. case '\n':
  161. r.append('\\').append('n');
  162. break;
  163. case '\t':
  164. r.append('\\').append('t');
  165. break;
  166. case '\b':
  167. // Doesn't match `git config foo.bar $'x\by'`, which doesn't escape the
  168. // \x08, but since both escaped and unescaped forms are readable, we'll
  169. // prefer internal consistency here.
  170. r.append('\\').append('b');
  171. break;
  172. case '\\':
  173. r.append('\\').append('\\');
  174. break;
  175. case '"':
  176. r.append('\\').append('"');
  177. break;
  178. case '#':
  179. case ';':
  180. needQuote = true;
  181. r.append(c);
  182. break;
  183. default:
  184. r.append(c);
  185. break;
  186. }
  187. }
  188. return needQuote ? '"' + r.toString() + '"' : r.toString();
  189. }
  190. static String escapeSubsection(String x) {
  191. if (x.isEmpty()) {
  192. return "\"\""; //$NON-NLS-1$
  193. }
  194. StringBuilder r = new StringBuilder(x.length() + 2).append('"');
  195. for (int k = 0; k < x.length(); k++) {
  196. char c = x.charAt(k);
  197. // git-config(1) lists the limited set of supported escape sequences
  198. // (which is even more limited for subsection names than for values).
  199. switch (c) {
  200. case '\0':
  201. throw new IllegalArgumentException(
  202. JGitText.get().configSubsectionContainsNullByte);
  203. case '\n':
  204. throw new IllegalArgumentException(
  205. JGitText.get().configSubsectionContainsNewline);
  206. case '\\':
  207. case '"':
  208. r.append('\\').append(c);
  209. break;
  210. default:
  211. r.append(c);
  212. break;
  213. }
  214. }
  215. return r.append('"').toString();
  216. }
  217. /**
  218. * Obtain an integer value from the configuration.
  219. *
  220. * @param section
  221. * section the key is grouped within.
  222. * @param name
  223. * name of the key to get.
  224. * @param defaultValue
  225. * default value to return if no value was present.
  226. * @return an integer value from the configuration, or defaultValue.
  227. */
  228. public int getInt(final String section, final String name,
  229. final int defaultValue) {
  230. return typedGetter.getInt(this, section, null, name, defaultValue);
  231. }
  232. /**
  233. * Obtain an integer value from the configuration.
  234. *
  235. * @param section
  236. * section the key is grouped within.
  237. * @param subsection
  238. * subsection name, such a remote or branch name.
  239. * @param name
  240. * name of the key to get.
  241. * @param defaultValue
  242. * default value to return if no value was present.
  243. * @return an integer value from the configuration, or defaultValue.
  244. */
  245. public int getInt(final String section, String subsection,
  246. final String name, final int defaultValue) {
  247. return typedGetter.getInt(this, section, subsection, name,
  248. defaultValue);
  249. }
  250. /**
  251. * Obtain an integer value from the configuration.
  252. *
  253. * @param section
  254. * section the key is grouped within.
  255. * @param name
  256. * name of the key to get.
  257. * @param defaultValue
  258. * default value to return if no value was present.
  259. * @return an integer value from the configuration, or defaultValue.
  260. */
  261. public long getLong(String section, String name, long defaultValue) {
  262. return typedGetter.getLong(this, section, null, name, defaultValue);
  263. }
  264. /**
  265. * Obtain an integer value from the configuration.
  266. *
  267. * @param section
  268. * section the key is grouped within.
  269. * @param subsection
  270. * subsection name, such a remote or branch name.
  271. * @param name
  272. * name of the key to get.
  273. * @param defaultValue
  274. * default value to return if no value was present.
  275. * @return an integer value from the configuration, or defaultValue.
  276. */
  277. public long getLong(final String section, String subsection,
  278. final String name, final long defaultValue) {
  279. return typedGetter.getLong(this, section, subsection, name,
  280. defaultValue);
  281. }
  282. /**
  283. * Get a boolean value from the git config
  284. *
  285. * @param section
  286. * section the key is grouped within.
  287. * @param name
  288. * name of the key to get.
  289. * @param defaultValue
  290. * default value to return if no value was present.
  291. * @return true if any value or defaultValue is true, false for missing or
  292. * explicit false
  293. */
  294. public boolean getBoolean(final String section, final String name,
  295. final boolean defaultValue) {
  296. return typedGetter.getBoolean(this, section, null, name, defaultValue);
  297. }
  298. /**
  299. * Get a boolean value from the git config
  300. *
  301. * @param section
  302. * section the key is grouped within.
  303. * @param subsection
  304. * subsection name, such a remote or branch name.
  305. * @param name
  306. * name of the key to get.
  307. * @param defaultValue
  308. * default value to return if no value was present.
  309. * @return true if any value or defaultValue is true, false for missing or
  310. * explicit false
  311. */
  312. public boolean getBoolean(final String section, String subsection,
  313. final String name, final boolean defaultValue) {
  314. return typedGetter.getBoolean(this, section, subsection, name,
  315. defaultValue);
  316. }
  317. /**
  318. * Parse an enumeration from the configuration.
  319. *
  320. * @param section
  321. * section the key is grouped within.
  322. * @param subsection
  323. * subsection name, such a remote or branch name.
  324. * @param name
  325. * name of the key to get.
  326. * @param defaultValue
  327. * default value to return if no value was present.
  328. * @return the selected enumeration value, or {@code defaultValue}.
  329. */
  330. public <T extends Enum<?>> T getEnum(final String section,
  331. final String subsection, final String name, final T defaultValue) {
  332. final T[] all = allValuesOf(defaultValue);
  333. return typedGetter.getEnum(this, all, section, subsection, name,
  334. defaultValue);
  335. }
  336. @SuppressWarnings("unchecked")
  337. private static <T> T[] allValuesOf(final T value) {
  338. try {
  339. return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$
  340. } catch (Exception err) {
  341. String typeName = value.getClass().getName();
  342. String msg = MessageFormat.format(
  343. JGitText.get().enumValuesNotAvailable, typeName);
  344. throw new IllegalArgumentException(msg, err);
  345. }
  346. }
  347. /**
  348. * Parse an enumeration from the configuration.
  349. *
  350. * @param all
  351. * all possible values in the enumeration which should be
  352. * recognized. Typically {@code EnumType.values()}.
  353. * @param section
  354. * section the key is grouped within.
  355. * @param subsection
  356. * subsection name, such a remote or branch name.
  357. * @param name
  358. * name of the key to get.
  359. * @param defaultValue
  360. * default value to return if no value was present.
  361. * @return the selected enumeration value, or {@code defaultValue}.
  362. */
  363. public <T extends Enum<?>> T getEnum(final T[] all, final String section,
  364. final String subsection, final String name, final T defaultValue) {
  365. return typedGetter.getEnum(this, all, section, subsection, name,
  366. defaultValue);
  367. }
  368. /**
  369. * Get string value or null if not found.
  370. *
  371. * @param section
  372. * the section
  373. * @param subsection
  374. * the subsection for the value
  375. * @param name
  376. * the key name
  377. * @return a String value from the config, <code>null</code> if not found
  378. */
  379. public String getString(final String section, String subsection,
  380. final String name) {
  381. return getRawString(section, subsection, name);
  382. }
  383. /**
  384. * Get a list of string values
  385. * <p>
  386. * If this instance was created with a base, the base's values are returned
  387. * first (if any).
  388. *
  389. * @param section
  390. * the section
  391. * @param subsection
  392. * the subsection for the value
  393. * @param name
  394. * the key name
  395. * @return array of zero or more values from the configuration.
  396. */
  397. public String[] getStringList(final String section, String subsection,
  398. final String name) {
  399. String[] base;
  400. if (baseConfig != null)
  401. base = baseConfig.getStringList(section, subsection, name);
  402. else
  403. base = EMPTY_STRING_ARRAY;
  404. String[] self = getRawStringList(section, subsection, name);
  405. if (self == null)
  406. return base;
  407. if (base.length == 0)
  408. return self;
  409. String[] res = new String[base.length + self.length];
  410. int n = base.length;
  411. System.arraycopy(base, 0, res, 0, n);
  412. System.arraycopy(self, 0, res, n, self.length);
  413. return res;
  414. }
  415. /**
  416. * Parse a numerical time unit, such as "1 minute", from the configuration.
  417. *
  418. * @param section
  419. * section the key is in.
  420. * @param subsection
  421. * subsection the key is in, or null if not in a subsection.
  422. * @param name
  423. * the key name.
  424. * @param defaultValue
  425. * default value to return if no value was present.
  426. * @param wantUnit
  427. * the units of {@code defaultValue} and the return value, as
  428. * well as the units to assume if the value does not contain an
  429. * indication of the units.
  430. * @return the value, or {@code defaultValue} if not set, expressed in
  431. * {@code units}.
  432. * @since 4.5
  433. */
  434. public long getTimeUnit(String section, String subsection, String name,
  435. long defaultValue, TimeUnit wantUnit) {
  436. return typedGetter.getTimeUnit(this, section, subsection, name,
  437. defaultValue, wantUnit);
  438. }
  439. /**
  440. * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the
  441. * configuration.
  442. *
  443. * @param section
  444. * section the key is in.
  445. * @param subsection
  446. * subsection the key is in, or null if not in a subsection.
  447. * @param name
  448. * the key name.
  449. * @return a possibly empty list of
  450. * {@link org.eclipse.jgit.transport.RefSpec}s
  451. * @since 4.9
  452. */
  453. public List<RefSpec> getRefSpecs(String section, String subsection,
  454. String name) {
  455. return typedGetter.getRefSpecs(this, section, subsection, name);
  456. }
  457. /**
  458. * Get set of all subsections of specified section within this configuration
  459. * and its base configuration
  460. *
  461. * @param section
  462. * section to search for.
  463. * @return set of all subsections of specified section within this
  464. * configuration and its base configuration; may be empty if no
  465. * subsection exists. The set's iterator returns sections in the
  466. * order they are declared by the configuration starting from this
  467. * instance and progressing through the base.
  468. */
  469. public Set<String> getSubsections(final String section) {
  470. return getState().getSubsections(section);
  471. }
  472. /**
  473. * Get the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  474. *
  475. * @return the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  476. * The set's iterator returns sections in the order they are
  477. * declared by the configuration starting from this instance and
  478. * progressing through the base.
  479. */
  480. public Set<String> getSections() {
  481. return getState().getSections();
  482. }
  483. /**
  484. * Get the list of names defined for this section
  485. *
  486. * @param section
  487. * the section
  488. * @return the list of names defined for this section
  489. */
  490. public Set<String> getNames(String section) {
  491. return getNames(section, null);
  492. }
  493. /**
  494. * Get the list of names defined for this subsection
  495. *
  496. * @param section
  497. * the section
  498. * @param subsection
  499. * the subsection
  500. * @return the list of names defined for this subsection
  501. */
  502. public Set<String> getNames(String section, String subsection) {
  503. return getState().getNames(section, subsection);
  504. }
  505. /**
  506. * Get the list of names defined for this section
  507. *
  508. * @param section
  509. * the section
  510. * @param recursive
  511. * if {@code true} recursively adds the names defined in all base
  512. * configurations
  513. * @return the list of names defined for this section
  514. * @since 3.2
  515. */
  516. public Set<String> getNames(String section, boolean recursive) {
  517. return getState().getNames(section, null, recursive);
  518. }
  519. /**
  520. * Get the list of names defined for this section
  521. *
  522. * @param section
  523. * the section
  524. * @param subsection
  525. * the subsection
  526. * @param recursive
  527. * if {@code true} recursively adds the names defined in all base
  528. * configurations
  529. * @return the list of names defined for this subsection
  530. * @since 3.2
  531. */
  532. public Set<String> getNames(String section, String subsection,
  533. boolean recursive) {
  534. return getState().getNames(section, subsection, recursive);
  535. }
  536. /**
  537. * Obtain a handle to a parsed set of configuration values.
  538. *
  539. * @param <T>
  540. * type of configuration model to return.
  541. * @param parser
  542. * parser which can create the model if it is not already
  543. * available in this configuration file. The parser is also used
  544. * as the key into a cache and must obey the hashCode and equals
  545. * contract in order to reuse a parsed model.
  546. * @return the parsed object instance, which is cached inside this config.
  547. */
  548. @SuppressWarnings("unchecked")
  549. public <T> T get(final SectionParser<T> parser) {
  550. final ConfigSnapshot myState = getState();
  551. T obj = (T) myState.cache.get(parser);
  552. if (obj == null) {
  553. obj = parser.parse(this);
  554. myState.cache.put(parser, obj);
  555. }
  556. return obj;
  557. }
  558. /**
  559. * Remove a cached configuration object.
  560. * <p>
  561. * If the associated configuration object has not yet been cached, this
  562. * method has no effect.
  563. *
  564. * @param parser
  565. * parser used to obtain the configuration object.
  566. * @see #get(SectionParser)
  567. */
  568. public void uncache(final SectionParser<?> parser) {
  569. state.get().cache.remove(parser);
  570. }
  571. /**
  572. * Adds a listener to be notified about changes.
  573. * <p>
  574. * Clients are supposed to remove the listeners after they are done with
  575. * them using the {@link org.eclipse.jgit.events.ListenerHandle#remove()}
  576. * method
  577. *
  578. * @param listener
  579. * the listener
  580. * @return the handle to the registered listener
  581. */
  582. public ListenerHandle addChangeListener(ConfigChangedListener listener) {
  583. return listeners.addConfigChangedListener(listener);
  584. }
  585. /**
  586. * Determine whether to issue change events for transient changes.
  587. * <p>
  588. * If <code>true</code> is returned (which is the default behavior),
  589. * {@link #fireConfigChangedEvent()} will be called upon each change.
  590. * <p>
  591. * Subclasses that override this to return <code>false</code> are
  592. * responsible for issuing {@link #fireConfigChangedEvent()} calls
  593. * themselves.
  594. *
  595. * @return <code></code>
  596. */
  597. protected boolean notifyUponTransientChanges() {
  598. return true;
  599. }
  600. /**
  601. * Notifies the listeners
  602. */
  603. protected void fireConfigChangedEvent() {
  604. listeners.dispatch(new ConfigChangedEvent());
  605. }
  606. String getRawString(final String section, final String subsection,
  607. final String name) {
  608. String[] lst = getRawStringList(section, subsection, name);
  609. if (lst != null) {
  610. return lst[lst.length - 1];
  611. } else if (baseConfig != null) {
  612. return baseConfig.getRawString(section, subsection, name);
  613. } else {
  614. return null;
  615. }
  616. }
  617. private String[] getRawStringList(String section, String subsection,
  618. String name) {
  619. return state.get().get(section, subsection, name);
  620. }
  621. private ConfigSnapshot getState() {
  622. ConfigSnapshot cur, upd;
  623. do {
  624. cur = state.get();
  625. final ConfigSnapshot base = getBaseState();
  626. if (cur.baseState == base)
  627. return cur;
  628. upd = new ConfigSnapshot(cur.entryList, base);
  629. } while (!state.compareAndSet(cur, upd));
  630. return upd;
  631. }
  632. private ConfigSnapshot getBaseState() {
  633. return baseConfig != null ? baseConfig.getState() : null;
  634. }
  635. /**
  636. * Add or modify a configuration value. The parameters will result in a
  637. * configuration entry like this.
  638. *
  639. * <pre>
  640. * [section &quot;subsection&quot;]
  641. * name = value
  642. * </pre>
  643. *
  644. * @param section
  645. * section name, e.g "branch"
  646. * @param subsection
  647. * optional subsection value, e.g. a branch name
  648. * @param name
  649. * parameter name, e.g. "filemode"
  650. * @param value
  651. * parameter value
  652. */
  653. public void setInt(final String section, final String subsection,
  654. final String name, final int value) {
  655. setLong(section, subsection, name, value);
  656. }
  657. /**
  658. * Add or modify a configuration value. The parameters will result in a
  659. * configuration entry like this.
  660. *
  661. * <pre>
  662. * [section &quot;subsection&quot;]
  663. * name = value
  664. * </pre>
  665. *
  666. * @param section
  667. * section name, e.g "branch"
  668. * @param subsection
  669. * optional subsection value, e.g. a branch name
  670. * @param name
  671. * parameter name, e.g. "filemode"
  672. * @param value
  673. * parameter value
  674. */
  675. public void setLong(final String section, final String subsection,
  676. final String name, final long value) {
  677. final String s;
  678. if (value >= GiB && (value % GiB) == 0)
  679. s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$
  680. else if (value >= MiB && (value % MiB) == 0)
  681. s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$
  682. else if (value >= KiB && (value % KiB) == 0)
  683. s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$
  684. else
  685. s = String.valueOf(value);
  686. setString(section, subsection, name, s);
  687. }
  688. /**
  689. * Add or modify a configuration value. The parameters will result in a
  690. * configuration entry like this.
  691. *
  692. * <pre>
  693. * [section &quot;subsection&quot;]
  694. * name = value
  695. * </pre>
  696. *
  697. * @param section
  698. * section name, e.g "branch"
  699. * @param subsection
  700. * optional subsection value, e.g. a branch name
  701. * @param name
  702. * parameter name, e.g. "filemode"
  703. * @param value
  704. * parameter value
  705. */
  706. public void setBoolean(final String section, final String subsection,
  707. final String name, final boolean value) {
  708. setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
  709. }
  710. /**
  711. * Add or modify a configuration value. The parameters will result in a
  712. * configuration entry like this.
  713. *
  714. * <pre>
  715. * [section &quot;subsection&quot;]
  716. * name = value
  717. * </pre>
  718. *
  719. * @param section
  720. * section name, e.g "branch"
  721. * @param subsection
  722. * optional subsection value, e.g. a branch name
  723. * @param name
  724. * parameter name, e.g. "filemode"
  725. * @param value
  726. * parameter value
  727. */
  728. public <T extends Enum<?>> void setEnum(final String section,
  729. final String subsection, final String name, final T value) {
  730. String n;
  731. if (value instanceof ConfigEnum)
  732. n = ((ConfigEnum) value).toConfigValue();
  733. else
  734. n = value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
  735. setString(section, subsection, name, n);
  736. }
  737. /**
  738. * Add or modify a configuration value. The parameters will result in a
  739. * configuration entry like this.
  740. *
  741. * <pre>
  742. * [section &quot;subsection&quot;]
  743. * name = value
  744. * </pre>
  745. *
  746. * @param section
  747. * section name, e.g "branch"
  748. * @param subsection
  749. * optional subsection value, e.g. a branch name
  750. * @param name
  751. * parameter name, e.g. "filemode"
  752. * @param value
  753. * parameter value, e.g. "true"
  754. */
  755. public void setString(final String section, final String subsection,
  756. final String name, final String value) {
  757. setStringList(section, subsection, name, Collections
  758. .singletonList(value));
  759. }
  760. /**
  761. * Remove a configuration value.
  762. *
  763. * @param section
  764. * section name, e.g "branch"
  765. * @param subsection
  766. * optional subsection value, e.g. a branch name
  767. * @param name
  768. * parameter name, e.g. "filemode"
  769. */
  770. public void unset(final String section, final String subsection,
  771. final String name) {
  772. setStringList(section, subsection, name, Collections
  773. .<String> emptyList());
  774. }
  775. /**
  776. * Remove all configuration values under a single section.
  777. *
  778. * @param section
  779. * section name, e.g "branch"
  780. * @param subsection
  781. * optional subsection value, e.g. a branch name
  782. */
  783. public void unsetSection(String section, String subsection) {
  784. ConfigSnapshot src, res;
  785. do {
  786. src = state.get();
  787. res = unsetSection(src, section, subsection);
  788. } while (!state.compareAndSet(src, res));
  789. }
  790. private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
  791. final String section,
  792. final String subsection) {
  793. final int max = srcState.entryList.size();
  794. final ArrayList<ConfigLine> r = new ArrayList<>(max);
  795. boolean lastWasMatch = false;
  796. for (ConfigLine e : srcState.entryList) {
  797. if (e.match(section, subsection)) {
  798. // Skip this record, it's for the section we are removing.
  799. lastWasMatch = true;
  800. continue;
  801. }
  802. if (lastWasMatch && e.section == null && e.subsection == null)
  803. continue; // skip this padding line in the section.
  804. r.add(e);
  805. }
  806. return newState(r);
  807. }
  808. /**
  809. * Set a configuration value.
  810. *
  811. * <pre>
  812. * [section &quot;subsection&quot;]
  813. * name = value1
  814. * name = value2
  815. * </pre>
  816. *
  817. * @param section
  818. * section name, e.g "branch"
  819. * @param subsection
  820. * optional subsection value, e.g. a branch name
  821. * @param name
  822. * parameter name, e.g. "filemode"
  823. * @param values
  824. * list of zero or more values for this key.
  825. */
  826. public void setStringList(final String section, final String subsection,
  827. final String name, final List<String> values) {
  828. ConfigSnapshot src, res;
  829. do {
  830. src = state.get();
  831. res = replaceStringList(src, section, subsection, name, values);
  832. } while (!state.compareAndSet(src, res));
  833. if (notifyUponTransientChanges())
  834. fireConfigChangedEvent();
  835. }
  836. private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
  837. final String section, final String subsection, final String name,
  838. final List<String> values) {
  839. final List<ConfigLine> entries = copy(srcState, values);
  840. int entryIndex = 0;
  841. int valueIndex = 0;
  842. int insertPosition = -1;
  843. // Reset the first n Entry objects that match this input name.
  844. //
  845. while (entryIndex < entries.size() && valueIndex < values.size()) {
  846. final ConfigLine e = entries.get(entryIndex);
  847. if (e.match(section, subsection, name)) {
  848. entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
  849. insertPosition = entryIndex + 1;
  850. }
  851. entryIndex++;
  852. }
  853. // Remove any extra Entry objects that we no longer need.
  854. //
  855. if (valueIndex == values.size() && entryIndex < entries.size()) {
  856. while (entryIndex < entries.size()) {
  857. final ConfigLine e = entries.get(entryIndex++);
  858. if (e.match(section, subsection, name))
  859. entries.remove(--entryIndex);
  860. }
  861. }
  862. // Insert new Entry objects for additional/new values.
  863. //
  864. if (valueIndex < values.size() && entryIndex == entries.size()) {
  865. if (insertPosition < 0) {
  866. // We didn't find a matching key above, but maybe there
  867. // is already a section available that matches. Insert
  868. // after the last key of that section.
  869. //
  870. insertPosition = findSectionEnd(entries, section, subsection);
  871. }
  872. if (insertPosition < 0) {
  873. // We didn't find any matching section header for this key,
  874. // so we must create a new section header at the end.
  875. //
  876. final ConfigLine e = new ConfigLine();
  877. e.section = section;
  878. e.subsection = subsection;
  879. entries.add(e);
  880. insertPosition = entries.size();
  881. }
  882. while (valueIndex < values.size()) {
  883. final ConfigLine e = new ConfigLine();
  884. e.section = section;
  885. e.subsection = subsection;
  886. e.name = name;
  887. e.value = values.get(valueIndex++);
  888. entries.add(insertPosition++, e);
  889. }
  890. }
  891. return newState(entries);
  892. }
  893. private static List<ConfigLine> copy(final ConfigSnapshot src,
  894. final List<String> values) {
  895. // At worst we need to insert 1 line for each value, plus 1 line
  896. // for a new section header. Assume that and allocate the space.
  897. //
  898. final int max = src.entryList.size() + values.size() + 1;
  899. final ArrayList<ConfigLine> r = new ArrayList<>(max);
  900. r.addAll(src.entryList);
  901. return r;
  902. }
  903. private static int findSectionEnd(final List<ConfigLine> entries,
  904. final String section, final String subsection) {
  905. for (int i = 0; i < entries.size(); i++) {
  906. ConfigLine e = entries.get(i);
  907. if (e.match(section, subsection, null)) {
  908. i++;
  909. while (i < entries.size()) {
  910. e = entries.get(i);
  911. if (e.match(section, subsection, e.name))
  912. i++;
  913. else
  914. break;
  915. }
  916. return i;
  917. }
  918. }
  919. return -1;
  920. }
  921. /**
  922. * Get this configuration, formatted as a Git style text file.
  923. *
  924. * @return this configuration, formatted as a Git style text file.
  925. */
  926. public String toText() {
  927. final StringBuilder out = new StringBuilder();
  928. for (final ConfigLine e : state.get().entryList) {
  929. if (e.prefix != null)
  930. out.append(e.prefix);
  931. if (e.section != null && e.name == null) {
  932. out.append('[');
  933. out.append(e.section);
  934. if (e.subsection != null) {
  935. out.append(' ');
  936. String escaped = escapeValue(e.subsection);
  937. // make sure to avoid double quotes here
  938. boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
  939. && escaped.endsWith("\""); //$NON-NLS-1$
  940. if (!quoted)
  941. out.append('"');
  942. out.append(escaped);
  943. if (!quoted)
  944. out.append('"');
  945. }
  946. out.append(']');
  947. } else if (e.section != null && e.name != null) {
  948. if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
  949. out.append('\t');
  950. out.append(e.name);
  951. if (MAGIC_EMPTY_VALUE != e.value) {
  952. out.append(" ="); //$NON-NLS-1$
  953. if (e.value != null) {
  954. out.append(' ');
  955. out.append(escapeValue(e.value));
  956. }
  957. }
  958. if (e.suffix != null)
  959. out.append(' ');
  960. }
  961. if (e.suffix != null)
  962. out.append(e.suffix);
  963. out.append('\n');
  964. }
  965. return out.toString();
  966. }
  967. /**
  968. * Clear this configuration and reset to the contents of the parsed string.
  969. *
  970. * @param text
  971. * Git style text file listing configuration properties.
  972. * @throws org.eclipse.jgit.errors.ConfigInvalidException
  973. * the text supplied is not formatted correctly. No changes were
  974. * made to {@code this}.
  975. */
  976. public void fromText(final String text) throws ConfigInvalidException {
  977. state.set(newState(fromTextRecurse(text, 1)));
  978. }
  979. private List<ConfigLine> fromTextRecurse(final String text, int depth)
  980. throws ConfigInvalidException {
  981. if (depth > MAX_DEPTH) {
  982. throw new ConfigInvalidException(
  983. JGitText.get().tooManyIncludeRecursions);
  984. }
  985. final List<ConfigLine> newEntries = new ArrayList<>();
  986. final StringReader in = new StringReader(text);
  987. ConfigLine last = null;
  988. ConfigLine e = new ConfigLine();
  989. for (;;) {
  990. int input = in.read();
  991. if (-1 == input) {
  992. if (e.section != null)
  993. newEntries.add(e);
  994. break;
  995. }
  996. final char c = (char) input;
  997. if ('\n' == c) {
  998. // End of this entry.
  999. newEntries.add(e);
  1000. if (e.section != null)
  1001. last = e;
  1002. e = new ConfigLine();
  1003. } else if (e.suffix != null) {
  1004. // Everything up until the end-of-line is in the suffix.
  1005. e.suffix += c;
  1006. } else if (';' == c || '#' == c) {
  1007. // The rest of this line is a comment; put into suffix.
  1008. e.suffix = String.valueOf(c);
  1009. } else if (e.section == null && Character.isWhitespace(c)) {
  1010. // Save the leading whitespace (if any).
  1011. if (e.prefix == null)
  1012. e.prefix = ""; //$NON-NLS-1$
  1013. e.prefix += c;
  1014. } else if ('[' == c) {
  1015. // This is a section header.
  1016. e.section = readSectionName(in);
  1017. input = in.read();
  1018. if ('"' == input) {
  1019. e.subsection = readSubsectionName(in);
  1020. input = in.read();
  1021. }
  1022. if (']' != input)
  1023. throw new ConfigInvalidException(JGitText.get().badGroupHeader);
  1024. e.suffix = ""; //$NON-NLS-1$
  1025. } else if (last != null) {
  1026. // Read a value.
  1027. e.section = last.section;
  1028. e.subsection = last.subsection;
  1029. in.reset();
  1030. e.name = readKeyName(in);
  1031. if (e.name.endsWith("\n")) { //$NON-NLS-1$
  1032. e.name = e.name.substring(0, e.name.length() - 1);
  1033. e.value = MAGIC_EMPTY_VALUE;
  1034. } else
  1035. e.value = readValue(in);
  1036. if (e.section.equals("include")) { //$NON-NLS-1$
  1037. addIncludedConfig(newEntries, e, depth);
  1038. }
  1039. } else
  1040. throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
  1041. }
  1042. return newEntries;
  1043. }
  1044. /**
  1045. * Read the included config from the specified (possibly) relative path
  1046. *
  1047. * @param relPath
  1048. * possibly relative path to the included config, as specified in
  1049. * this config
  1050. * @return the read bytes, or null if the included config should be ignored
  1051. * @throws org.eclipse.jgit.errors.ConfigInvalidException
  1052. * if something went wrong while reading the config
  1053. * @since 4.10
  1054. */
  1055. @Nullable
  1056. protected byte[] readIncludedConfig(String relPath)
  1057. throws ConfigInvalidException {
  1058. return null;
  1059. }
  1060. private void addIncludedConfig(final List<ConfigLine> newEntries,
  1061. ConfigLine line, int depth) throws ConfigInvalidException {
  1062. if (!line.name.equals("path") || //$NON-NLS-1$
  1063. line.value == null || line.value.equals(MAGIC_EMPTY_VALUE)) {
  1064. throw new ConfigInvalidException(
  1065. JGitText.get().invalidLineInConfigFile);
  1066. }
  1067. byte[] bytes = readIncludedConfig(line.value);
  1068. if (bytes == null) {
  1069. return;
  1070. }
  1071. String decoded;
  1072. if (isUtf8(bytes)) {
  1073. decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET, bytes, 3,
  1074. bytes.length);
  1075. } else {
  1076. decoded = RawParseUtils.decode(bytes);
  1077. }
  1078. newEntries.addAll(fromTextRecurse(decoded, depth + 1));
  1079. }
  1080. private ConfigSnapshot newState() {
  1081. return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
  1082. getBaseState());
  1083. }
  1084. private ConfigSnapshot newState(final List<ConfigLine> entries) {
  1085. return new ConfigSnapshot(Collections.unmodifiableList(entries),
  1086. getBaseState());
  1087. }
  1088. /**
  1089. * Clear the configuration file
  1090. */
  1091. protected void clear() {
  1092. state.set(newState());
  1093. }
  1094. /**
  1095. * Check if bytes should be treated as UTF-8 or not.
  1096. *
  1097. * @param bytes
  1098. * the bytes to check encoding for.
  1099. * @return true if bytes should be treated as UTF-8, false otherwise.
  1100. * @since 4.4
  1101. */
  1102. protected boolean isUtf8(final byte[] bytes) {
  1103. return bytes.length >= 3 && bytes[0] == (byte) 0xEF
  1104. && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
  1105. }
  1106. private static String readSectionName(final StringReader in)
  1107. throws ConfigInvalidException {
  1108. final StringBuilder name = new StringBuilder();
  1109. for (;;) {
  1110. int c = in.read();
  1111. if (c < 0)
  1112. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1113. if (']' == c) {
  1114. in.reset();
  1115. break;
  1116. }
  1117. if (' ' == c || '\t' == c) {
  1118. for (;;) {
  1119. c = in.read();
  1120. if (c < 0)
  1121. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1122. if ('"' == c) {
  1123. in.reset();
  1124. break;
  1125. }
  1126. if (' ' == c || '\t' == c)
  1127. continue; // Skipped...
  1128. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1129. }
  1130. break;
  1131. }
  1132. if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
  1133. name.append((char) c);
  1134. else
  1135. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1136. }
  1137. return name.toString();
  1138. }
  1139. private static String readKeyName(final StringReader in)
  1140. throws ConfigInvalidException {
  1141. final StringBuilder name = new StringBuilder();
  1142. for (;;) {
  1143. int c = in.read();
  1144. if (c < 0)
  1145. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1146. if ('=' == c)
  1147. break;
  1148. if (' ' == c || '\t' == c) {
  1149. for (;;) {
  1150. c = in.read();
  1151. if (c < 0)
  1152. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1153. if ('=' == c)
  1154. break;
  1155. if (';' == c || '#' == c || '\n' == c) {
  1156. in.reset();
  1157. break;
  1158. }
  1159. if (' ' == c || '\t' == c)
  1160. continue; // Skipped...
  1161. throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
  1162. }
  1163. break;
  1164. }
  1165. if (Character.isLetterOrDigit((char) c) || c == '-') {
  1166. // From the git-config man page:
  1167. // The variable names are case-insensitive and only
  1168. // alphanumeric characters and - are allowed.
  1169. name.append((char) c);
  1170. } else if ('\n' == c) {
  1171. in.reset();
  1172. name.append((char) c);
  1173. break;
  1174. } else
  1175. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
  1176. }
  1177. return name.toString();
  1178. }
  1179. private static String readSubsectionName(StringReader in)
  1180. throws ConfigInvalidException {
  1181. StringBuilder r = new StringBuilder();
  1182. for (;;) {
  1183. int c = in.read();
  1184. if (c < 0) {
  1185. break;
  1186. }
  1187. if ('\n' == c) {
  1188. throw new ConfigInvalidException(
  1189. JGitText.get().newlineInQuotesNotAllowed);
  1190. }
  1191. if ('\\' == c) {
  1192. c = in.read();
  1193. switch (c) {
  1194. case -1:
  1195. throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1196. case '\\':
  1197. case '"':
  1198. r.append((char) c);
  1199. continue;
  1200. default:
  1201. // C git simply drops backslashes if the escape sequence is not
  1202. // recognized.
  1203. r.append((char) c);
  1204. continue;
  1205. }
  1206. }
  1207. if ('"' == c) {
  1208. break;
  1209. }
  1210. r.append((char) c);
  1211. }
  1212. return r.toString();
  1213. }
  1214. private static String readValue(final StringReader in)
  1215. throws ConfigInvalidException {
  1216. StringBuilder value = new StringBuilder();
  1217. StringBuilder trailingSpaces = null;
  1218. boolean quote = false;
  1219. boolean inLeadingSpace = true;
  1220. for (;;) {
  1221. int c = in.read();
  1222. if (c < 0) {
  1223. break;
  1224. }
  1225. if ('\n' == c) {
  1226. if (quote) {
  1227. throw new ConfigInvalidException(
  1228. JGitText.get().newlineInQuotesNotAllowed);
  1229. }
  1230. in.reset();
  1231. break;
  1232. }
  1233. if (!quote && (';' == c || '#' == c)) {
  1234. if (trailingSpaces != null) {
  1235. trailingSpaces.setLength(0);
  1236. }
  1237. in.reset();
  1238. break;
  1239. }
  1240. char cc = (char) c;
  1241. if (Character.isWhitespace(cc)) {
  1242. if (inLeadingSpace) {
  1243. continue;
  1244. }
  1245. if (trailingSpaces == null) {
  1246. trailingSpaces = new StringBuilder();
  1247. }
  1248. trailingSpaces.append(cc);
  1249. continue;
  1250. } else {
  1251. inLeadingSpace = false;
  1252. if (trailingSpaces != null) {
  1253. value.append(trailingSpaces);
  1254. trailingSpaces.setLength(0);
  1255. }
  1256. }
  1257. if ('\\' == c) {
  1258. c = in.read();
  1259. switch (c) {
  1260. case -1:
  1261. throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1262. case '\n':
  1263. continue;
  1264. case 't':
  1265. value.append('\t');
  1266. continue;
  1267. case 'b':
  1268. value.append('\b');
  1269. continue;
  1270. case 'n':
  1271. value.append('\n');
  1272. continue;
  1273. case '\\':
  1274. value.append('\\');
  1275. continue;
  1276. case '"':
  1277. value.append('"');
  1278. continue;
  1279. default:
  1280. throw new ConfigInvalidException(MessageFormat.format(
  1281. JGitText.get().badEscape,
  1282. Character.valueOf(((char) c))));
  1283. }
  1284. }
  1285. if ('"' == c) {
  1286. quote = !quote;
  1287. continue;
  1288. }
  1289. value.append(cc);
  1290. }
  1291. return value.length() > 0 ? value.toString() : null;
  1292. }
  1293. /**
  1294. * Parses a section of the configuration into an application model object.
  1295. * <p>
  1296. * Instances must implement hashCode and equals such that model objects can
  1297. * be cached by using the {@code SectionParser} as a key of a HashMap.
  1298. * <p>
  1299. * As the {@code SectionParser} itself is used as the key of the internal
  1300. * HashMap applications should be careful to ensure the SectionParser key
  1301. * does not retain unnecessary application state which may cause memory to
  1302. * be held longer than expected.
  1303. *
  1304. * @param <T>
  1305. * type of the application model created by the parser.
  1306. */
  1307. public static interface SectionParser<T> {
  1308. /**
  1309. * Create a model object from a configuration.
  1310. *
  1311. * @param cfg
  1312. * the configuration to read values from.
  1313. * @return the application model instance.
  1314. */
  1315. T parse(Config cfg);
  1316. }
  1317. private static class StringReader {
  1318. private final char[] buf;
  1319. private int pos;
  1320. StringReader(final String in) {
  1321. buf = in.toCharArray();
  1322. }
  1323. int read() {
  1324. try {
  1325. return buf[pos++];
  1326. } catch (ArrayIndexOutOfBoundsException e) {
  1327. pos = buf.length;
  1328. return -1;
  1329. }
  1330. }
  1331. void reset() {
  1332. pos--;
  1333. }
  1334. }
  1335. /**
  1336. * Converts enumeration values into configuration options and vice-versa,
  1337. * allowing to match a config option with an enum value.
  1338. *
  1339. */
  1340. public static interface ConfigEnum {
  1341. /**
  1342. * Converts enumeration value into a string to be save in config.
  1343. *
  1344. * @return the enum value as config string
  1345. */
  1346. String toConfigValue();
  1347. /**
  1348. * Checks if the given string matches with enum value.
  1349. *
  1350. * @param in
  1351. * the string to match
  1352. * @return true if the given string matches enum value, false otherwise
  1353. */
  1354. boolean matchConfigValue(String in);
  1355. }
  1356. }