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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503
  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 static java.nio.charset.StandardCharsets.UTF_8;
  53. import java.text.MessageFormat;
  54. import java.util.ArrayList;
  55. import java.util.Collections;
  56. import java.util.List;
  57. import java.util.Locale;
  58. import java.util.Set;
  59. import java.util.concurrent.TimeUnit;
  60. import java.util.concurrent.atomic.AtomicReference;
  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(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(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(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(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.includedFrom == null && 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.includedFrom == null && 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.includedFrom == null
  859. && e.match(section, subsection, name))
  860. entries.remove(--entryIndex);
  861. }
  862. }
  863. // Insert new Entry objects for additional/new values.
  864. //
  865. if (valueIndex < values.size() && entryIndex == entries.size()) {
  866. if (insertPosition < 0) {
  867. // We didn't find a matching key above, but maybe there
  868. // is already a section available that matches. Insert
  869. // after the last key of that section.
  870. //
  871. insertPosition = findSectionEnd(entries, section, subsection,
  872. true);
  873. }
  874. if (insertPosition < 0) {
  875. // We didn't find any matching section header for this key,
  876. // so we must create a new section header at the end.
  877. //
  878. final ConfigLine e = new ConfigLine();
  879. e.section = section;
  880. e.subsection = subsection;
  881. entries.add(e);
  882. insertPosition = entries.size();
  883. }
  884. while (valueIndex < values.size()) {
  885. final ConfigLine e = new ConfigLine();
  886. e.section = section;
  887. e.subsection = subsection;
  888. e.name = name;
  889. e.value = values.get(valueIndex++);
  890. entries.add(insertPosition++, e);
  891. }
  892. }
  893. return newState(entries);
  894. }
  895. private static List<ConfigLine> copy(final ConfigSnapshot src,
  896. final List<String> values) {
  897. // At worst we need to insert 1 line for each value, plus 1 line
  898. // for a new section header. Assume that and allocate the space.
  899. //
  900. final int max = src.entryList.size() + values.size() + 1;
  901. final ArrayList<ConfigLine> r = new ArrayList<>(max);
  902. r.addAll(src.entryList);
  903. return r;
  904. }
  905. private static int findSectionEnd(final List<ConfigLine> entries,
  906. final String section, final String subsection,
  907. boolean skipIncludedLines) {
  908. for (int i = 0; i < entries.size(); i++) {
  909. ConfigLine e = entries.get(i);
  910. if (e.includedFrom != null && skipIncludedLines) {
  911. continue;
  912. }
  913. if (e.match(section, subsection, null)) {
  914. i++;
  915. while (i < entries.size()) {
  916. e = entries.get(i);
  917. if (e.match(section, subsection, e.name))
  918. i++;
  919. else
  920. break;
  921. }
  922. return i;
  923. }
  924. }
  925. return -1;
  926. }
  927. /**
  928. * Get this configuration, formatted as a Git style text file.
  929. *
  930. * @return this configuration, formatted as a Git style text file.
  931. */
  932. public String toText() {
  933. final StringBuilder out = new StringBuilder();
  934. for (ConfigLine e : state.get().entryList) {
  935. if (e.includedFrom != null)
  936. continue;
  937. if (e.prefix != null)
  938. out.append(e.prefix);
  939. if (e.section != null && e.name == null) {
  940. out.append('[');
  941. out.append(e.section);
  942. if (e.subsection != null) {
  943. out.append(' ');
  944. String escaped = escapeValue(e.subsection);
  945. // make sure to avoid double quotes here
  946. boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
  947. && escaped.endsWith("\""); //$NON-NLS-1$
  948. if (!quoted)
  949. out.append('"');
  950. out.append(escaped);
  951. if (!quoted)
  952. out.append('"');
  953. }
  954. out.append(']');
  955. } else if (e.section != null && e.name != null) {
  956. if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
  957. out.append('\t');
  958. out.append(e.name);
  959. if (MAGIC_EMPTY_VALUE != e.value) {
  960. out.append(" ="); //$NON-NLS-1$
  961. if (e.value != null) {
  962. out.append(' ');
  963. out.append(escapeValue(e.value));
  964. }
  965. }
  966. if (e.suffix != null)
  967. out.append(' ');
  968. }
  969. if (e.suffix != null)
  970. out.append(e.suffix);
  971. out.append('\n');
  972. }
  973. return out.toString();
  974. }
  975. /**
  976. * Clear this configuration and reset to the contents of the parsed string.
  977. *
  978. * @param text
  979. * Git style text file listing configuration properties.
  980. * @throws org.eclipse.jgit.errors.ConfigInvalidException
  981. * the text supplied is not formatted correctly. No changes were
  982. * made to {@code this}.
  983. */
  984. public void fromText(String text) throws ConfigInvalidException {
  985. state.set(newState(fromTextRecurse(text, 1, null)));
  986. }
  987. private List<ConfigLine> fromTextRecurse(String text, int depth,
  988. String includedFrom) throws ConfigInvalidException {
  989. if (depth > MAX_DEPTH) {
  990. throw new ConfigInvalidException(
  991. JGitText.get().tooManyIncludeRecursions);
  992. }
  993. final List<ConfigLine> newEntries = new ArrayList<>();
  994. final StringReader in = new StringReader(text);
  995. ConfigLine last = null;
  996. ConfigLine e = new ConfigLine();
  997. e.includedFrom = includedFrom;
  998. for (;;) {
  999. int input = in.read();
  1000. if (-1 == input) {
  1001. if (e.section != null)
  1002. newEntries.add(e);
  1003. break;
  1004. }
  1005. final char c = (char) input;
  1006. if ('\n' == c) {
  1007. // End of this entry.
  1008. newEntries.add(e);
  1009. if (e.section != null)
  1010. last = e;
  1011. e = new ConfigLine();
  1012. e.includedFrom = includedFrom;
  1013. } else if (e.suffix != null) {
  1014. // Everything up until the end-of-line is in the suffix.
  1015. e.suffix += c;
  1016. } else if (';' == c || '#' == c) {
  1017. // The rest of this line is a comment; put into suffix.
  1018. e.suffix = String.valueOf(c);
  1019. } else if (e.section == null && Character.isWhitespace(c)) {
  1020. // Save the leading whitespace (if any).
  1021. if (e.prefix == null)
  1022. e.prefix = ""; //$NON-NLS-1$
  1023. e.prefix += c;
  1024. } else if ('[' == c) {
  1025. // This is a section header.
  1026. e.section = readSectionName(in);
  1027. input = in.read();
  1028. if ('"' == input) {
  1029. e.subsection = readSubsectionName(in);
  1030. input = in.read();
  1031. }
  1032. if (']' != input)
  1033. throw new ConfigInvalidException(JGitText.get().badGroupHeader);
  1034. e.suffix = ""; //$NON-NLS-1$
  1035. } else if (last != null) {
  1036. // Read a value.
  1037. e.section = last.section;
  1038. e.subsection = last.subsection;
  1039. in.reset();
  1040. e.name = readKeyName(in);
  1041. if (e.name.endsWith("\n")) { //$NON-NLS-1$
  1042. e.name = e.name.substring(0, e.name.length() - 1);
  1043. e.value = MAGIC_EMPTY_VALUE;
  1044. } else
  1045. e.value = readValue(in);
  1046. if (e.section.equalsIgnoreCase("include")) { //$NON-NLS-1$
  1047. addIncludedConfig(newEntries, e, depth);
  1048. }
  1049. } else
  1050. throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
  1051. }
  1052. return newEntries;
  1053. }
  1054. /**
  1055. * Read the included config from the specified (possibly) relative path
  1056. *
  1057. * @param relPath
  1058. * possibly relative path to the included config, as specified in
  1059. * this config
  1060. * @return the read bytes, or null if the included config should be ignored
  1061. * @throws org.eclipse.jgit.errors.ConfigInvalidException
  1062. * if something went wrong while reading the config
  1063. * @since 4.10
  1064. */
  1065. protected byte[] readIncludedConfig(String relPath)
  1066. throws ConfigInvalidException {
  1067. return null;
  1068. }
  1069. private void addIncludedConfig(final List<ConfigLine> newEntries,
  1070. ConfigLine line, int depth) throws ConfigInvalidException {
  1071. if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$
  1072. line.value == null || line.value.equals(MAGIC_EMPTY_VALUE)) {
  1073. throw new ConfigInvalidException(MessageFormat.format(
  1074. JGitText.get().invalidLineInConfigFileWithParam, line));
  1075. }
  1076. byte[] bytes = readIncludedConfig(line.value);
  1077. if (bytes == null) {
  1078. return;
  1079. }
  1080. String decoded;
  1081. if (isUtf8(bytes)) {
  1082. decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
  1083. } else {
  1084. decoded = RawParseUtils.decode(bytes);
  1085. }
  1086. try {
  1087. newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
  1088. } catch (ConfigInvalidException e) {
  1089. throw new ConfigInvalidException(MessageFormat
  1090. .format(JGitText.get().cannotReadFile, line.value), e);
  1091. }
  1092. }
  1093. private ConfigSnapshot newState() {
  1094. return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
  1095. getBaseState());
  1096. }
  1097. private ConfigSnapshot newState(List<ConfigLine> entries) {
  1098. return new ConfigSnapshot(Collections.unmodifiableList(entries),
  1099. getBaseState());
  1100. }
  1101. /**
  1102. * Clear the configuration file
  1103. */
  1104. protected void clear() {
  1105. state.set(newState());
  1106. }
  1107. /**
  1108. * Check if bytes should be treated as UTF-8 or not.
  1109. *
  1110. * @param bytes
  1111. * the bytes to check encoding for.
  1112. * @return true if bytes should be treated as UTF-8, false otherwise.
  1113. * @since 4.4
  1114. */
  1115. protected boolean isUtf8(final byte[] bytes) {
  1116. return bytes.length >= 3 && bytes[0] == (byte) 0xEF
  1117. && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
  1118. }
  1119. private static String readSectionName(StringReader in)
  1120. throws ConfigInvalidException {
  1121. final StringBuilder name = new StringBuilder();
  1122. for (;;) {
  1123. int c = in.read();
  1124. if (c < 0)
  1125. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1126. if (']' == c) {
  1127. in.reset();
  1128. break;
  1129. }
  1130. if (' ' == c || '\t' == c) {
  1131. for (;;) {
  1132. c = in.read();
  1133. if (c < 0)
  1134. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1135. if ('"' == c) {
  1136. in.reset();
  1137. break;
  1138. }
  1139. if (' ' == c || '\t' == c)
  1140. continue; // Skipped...
  1141. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1142. }
  1143. break;
  1144. }
  1145. if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
  1146. name.append((char) c);
  1147. else
  1148. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1149. }
  1150. return name.toString();
  1151. }
  1152. private static String readKeyName(StringReader in)
  1153. throws ConfigInvalidException {
  1154. final StringBuilder name = new StringBuilder();
  1155. for (;;) {
  1156. int c = in.read();
  1157. if (c < 0)
  1158. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1159. if ('=' == c)
  1160. break;
  1161. if (' ' == c || '\t' == c) {
  1162. for (;;) {
  1163. c = in.read();
  1164. if (c < 0)
  1165. throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
  1166. if ('=' == c)
  1167. break;
  1168. if (';' == c || '#' == c || '\n' == c) {
  1169. in.reset();
  1170. break;
  1171. }
  1172. if (' ' == c || '\t' == c)
  1173. continue; // Skipped...
  1174. throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
  1175. }
  1176. break;
  1177. }
  1178. if (Character.isLetterOrDigit((char) c) || c == '-') {
  1179. // From the git-config man page:
  1180. // The variable names are case-insensitive and only
  1181. // alphanumeric characters and - are allowed.
  1182. name.append((char) c);
  1183. } else if ('\n' == c) {
  1184. in.reset();
  1185. name.append((char) c);
  1186. break;
  1187. } else
  1188. throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
  1189. }
  1190. return name.toString();
  1191. }
  1192. private static String readSubsectionName(StringReader in)
  1193. throws ConfigInvalidException {
  1194. StringBuilder r = new StringBuilder();
  1195. for (;;) {
  1196. int c = in.read();
  1197. if (c < 0) {
  1198. break;
  1199. }
  1200. if ('\n' == c) {
  1201. throw new ConfigInvalidException(
  1202. JGitText.get().newlineInQuotesNotAllowed);
  1203. }
  1204. if ('\\' == c) {
  1205. c = in.read();
  1206. switch (c) {
  1207. case -1:
  1208. throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1209. case '\\':
  1210. case '"':
  1211. r.append((char) c);
  1212. continue;
  1213. default:
  1214. // C git simply drops backslashes if the escape sequence is not
  1215. // recognized.
  1216. r.append((char) c);
  1217. continue;
  1218. }
  1219. }
  1220. if ('"' == c) {
  1221. break;
  1222. }
  1223. r.append((char) c);
  1224. }
  1225. return r.toString();
  1226. }
  1227. private static String readValue(StringReader in)
  1228. throws ConfigInvalidException {
  1229. StringBuilder value = new StringBuilder();
  1230. StringBuilder trailingSpaces = null;
  1231. boolean quote = false;
  1232. boolean inLeadingSpace = true;
  1233. for (;;) {
  1234. int c = in.read();
  1235. if (c < 0) {
  1236. break;
  1237. }
  1238. if ('\n' == c) {
  1239. if (quote) {
  1240. throw new ConfigInvalidException(
  1241. JGitText.get().newlineInQuotesNotAllowed);
  1242. }
  1243. in.reset();
  1244. break;
  1245. }
  1246. if (!quote && (';' == c || '#' == c)) {
  1247. if (trailingSpaces != null) {
  1248. trailingSpaces.setLength(0);
  1249. }
  1250. in.reset();
  1251. break;
  1252. }
  1253. char cc = (char) c;
  1254. if (Character.isWhitespace(cc)) {
  1255. if (inLeadingSpace) {
  1256. continue;
  1257. }
  1258. if (trailingSpaces == null) {
  1259. trailingSpaces = new StringBuilder();
  1260. }
  1261. trailingSpaces.append(cc);
  1262. continue;
  1263. } else {
  1264. inLeadingSpace = false;
  1265. if (trailingSpaces != null) {
  1266. value.append(trailingSpaces);
  1267. trailingSpaces.setLength(0);
  1268. }
  1269. }
  1270. if ('\\' == c) {
  1271. c = in.read();
  1272. switch (c) {
  1273. case -1:
  1274. throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1275. case '\n':
  1276. continue;
  1277. case 't':
  1278. value.append('\t');
  1279. continue;
  1280. case 'b':
  1281. value.append('\b');
  1282. continue;
  1283. case 'n':
  1284. value.append('\n');
  1285. continue;
  1286. case '\\':
  1287. value.append('\\');
  1288. continue;
  1289. case '"':
  1290. value.append('"');
  1291. continue;
  1292. default:
  1293. throw new ConfigInvalidException(MessageFormat.format(
  1294. JGitText.get().badEscape,
  1295. Character.valueOf(((char) c))));
  1296. }
  1297. }
  1298. if ('"' == c) {
  1299. quote = !quote;
  1300. continue;
  1301. }
  1302. value.append(cc);
  1303. }
  1304. return value.length() > 0 ? value.toString() : null;
  1305. }
  1306. /**
  1307. * Parses a section of the configuration into an application model object.
  1308. * <p>
  1309. * Instances must implement hashCode and equals such that model objects can
  1310. * be cached by using the {@code SectionParser} as a key of a HashMap.
  1311. * <p>
  1312. * As the {@code SectionParser} itself is used as the key of the internal
  1313. * HashMap applications should be careful to ensure the SectionParser key
  1314. * does not retain unnecessary application state which may cause memory to
  1315. * be held longer than expected.
  1316. *
  1317. * @param <T>
  1318. * type of the application model created by the parser.
  1319. */
  1320. public static interface SectionParser<T> {
  1321. /**
  1322. * Create a model object from a configuration.
  1323. *
  1324. * @param cfg
  1325. * the configuration to read values from.
  1326. * @return the application model instance.
  1327. */
  1328. T parse(Config cfg);
  1329. }
  1330. private static class StringReader {
  1331. private final char[] buf;
  1332. private int pos;
  1333. StringReader(String in) {
  1334. buf = in.toCharArray();
  1335. }
  1336. int read() {
  1337. try {
  1338. return buf[pos++];
  1339. } catch (ArrayIndexOutOfBoundsException e) {
  1340. pos = buf.length;
  1341. return -1;
  1342. }
  1343. }
  1344. void reset() {
  1345. pos--;
  1346. }
  1347. }
  1348. /**
  1349. * Converts enumeration values into configuration options and vice-versa,
  1350. * allowing to match a config option with an enum value.
  1351. *
  1352. */
  1353. public static interface ConfigEnum {
  1354. /**
  1355. * Converts enumeration value into a string to be save in config.
  1356. *
  1357. * @return the enum value as config string
  1358. */
  1359. String toConfigValue();
  1360. /**
  1361. * Checks if the given string matches with enum value.
  1362. *
  1363. * @param in
  1364. * the string to match
  1365. * @return true if the given string matches enum value, false otherwise
  1366. */
  1367. boolean matchConfigValue(String in);
  1368. }
  1369. }