2
This code is derived from jgit (http://eclipse.org/jgit).
3
Copyright owners are documented in jgit's IP log.
5
This program and the accompanying materials are made available
6
under the terms of the Eclipse Distribution License v1.0 which
7
accompanies this distribution, is reproduced below, and is
8
available at http://www.eclipse.org/org/documents/edl-v10.php
12
Redistribution and use in source and binary forms, with or
13
without modification, are permitted provided that the following
16
- Redistributions of source code must retain the above copyright
17
notice, this list of conditions and the following disclaimer.
19
- Redistributions in binary form must reproduce the above
20
copyright notice, this list of conditions and the following
21
disclaimer in the documentation and/or other materials provided
22
with the distribution.
24
- Neither the name of the Eclipse Foundation, Inc. nor the
25
names of its contributors may be used to endorse or promote
26
products derived from this software without specific prior
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45
using System.Collections.Generic;
58
/// <code>.config</code>
60
/// <code>.gitconfig</code>
62
/// <code>.gitmodules</code>
67
private static readonly string[] EMPTY_STRING_ARRAY = new string[] { };
69
private const long KiB = 1024;
71
private const long MiB = 1024 * KiB;
73
private const long GiB = 1024 * MiB;
75
/// <summary>the change listeners</summary>
76
private readonly ListenerList listeners = new ListenerList();
78
/// <summary>Immutable current state of the configuration data.</summary>
80
/// Immutable current state of the configuration data.
82
/// This state is copy-on-write. It should always contain an immutable list
83
/// of the configuration keys/values.
85
private readonly AtomicReference<ConfigSnapshot> state;
87
private readonly NGit.Config baseConfig;
89
/// <summary>Magic value indicating a missing entry.</summary>
91
/// Magic value indicating a missing entry.
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.
97
private static readonly string MAGIC_EMPTY_VALUE = string.Empty;
99
/// <summary>Create a configuration with no default fallback.</summary>
100
/// <remarks>Create a configuration with no default fallback.</remarks>
101
public Config() : this(null)
105
/// <summary>Create an empty configuration with a fallback for missing keys.</summary>
106
/// <remarks>Create an empty configuration with a fallback for missing keys.</remarks>
107
/// <param name="defaultConfig">
108
/// the base configuration to be consulted when a key is missing
109
/// from this configuration instance.
111
public Config(NGit.Config defaultConfig)
113
baseConfig = defaultConfig;
114
state = new AtomicReference<ConfigSnapshot>(NewState());
117
/// <summary>Escape the value before saving</summary>
118
/// <param name="x">the value to escape</param>
119
/// <returns>the escaped value</returns>
120
private static string EscapeValue(string x)
122
bool inquote = false;
124
StringBuilder r = new StringBuilder(x.Length);
125
for (int k = 0; k < x.Length; k++)
138
lineStart = r.Length;
171
r.Insert(lineStart, '"');
180
if (!inquote && r.Length > 0 && r[r.Length - 1] == ' ')
182
r.Insert(lineStart, '"');
204
/// <summary>Obtain an integer value from the configuration.</summary>
205
/// <remarks>Obtain an integer value from the configuration.</remarks>
206
/// <param name="section">section the key is grouped within.</param>
207
/// <param name="name">name of the key to get.</param>
208
/// <param name="defaultValue">default value to return if no value was present.</param>
209
/// <returns>an integer value from the configuration, or defaultValue.</returns>
210
public virtual int GetInt(string section, string name, int defaultValue)
212
return GetInt(section, null, name, defaultValue);
215
/// <summary>Obtain an integer value from the configuration.</summary>
216
/// <remarks>Obtain an integer value from the configuration.</remarks>
217
/// <param name="section">section the key is grouped within.</param>
218
/// <param name="subsection">subsection name, such a remote or branch name.</param>
219
/// <param name="name">name of the key to get.</param>
220
/// <param name="defaultValue">default value to return if no value was present.</param>
221
/// <returns>an integer value from the configuration, or defaultValue.</returns>
222
public virtual int GetInt(string section, string subsection, string name, int defaultValue
225
long val = GetLong(section, subsection, name, defaultValue);
226
if (int.MinValue <= val && val <= int.MaxValue)
230
throw new ArgumentException(MessageFormat.Format(JGitText.Get().integerValueOutOfRange
234
/// <summary>Obtain an integer value from the configuration.</summary>
235
/// <remarks>Obtain an integer value from the configuration.</remarks>
236
/// <param name="section">section the key is grouped within.</param>
237
/// <param name="name">name of the key to get.</param>
238
/// <param name="defaultValue">default value to return if no value was present.</param>
239
/// <returns>an integer value from the configuration, or defaultValue.</returns>
240
public virtual long GetLong(string section, string name, long defaultValue)
242
return GetLong(section, null, name, defaultValue);
245
/// <summary>Obtain an integer value from the configuration.</summary>
246
/// <remarks>Obtain an integer value from the configuration.</remarks>
247
/// <param name="section">section the key is grouped within.</param>
248
/// <param name="subsection">subsection name, such a remote or branch name.</param>
249
/// <param name="name">name of the key to get.</param>
250
/// <param name="defaultValue">default value to return if no value was present.</param>
251
/// <returns>an integer value from the configuration, or defaultValue.</returns>
252
public virtual long GetLong(string section, string subsection, string name, long
255
string str = GetString(section, subsection, name);
260
string n = str.Trim();
266
switch (StringUtils.ToLowerCase(n[n.Length - 1]))
288
n = Sharpen.Runtime.Substring(n, 0, n.Length - 1).Trim();
296
return mul * long.Parse(n);
298
catch (FormatException)
300
throw new ArgumentException(MessageFormat.Format(JGitText.Get().invalidIntegerValue
301
, section, name, str));
305
/// <summary>Get a boolean value from the git config</summary>
306
/// <param name="section">section the key is grouped within.</param>
307
/// <param name="name">name of the key to get.</param>
308
/// <param name="defaultValue">default value to return if no value was present.</param>
310
/// true if any value or defaultValue is true, false for missing or
313
public virtual bool GetBoolean(string section, string name, bool defaultValue)
315
return GetBoolean(section, null, name, defaultValue);
318
/// <summary>Get a boolean value from the git config</summary>
319
/// <param name="section">section the key is grouped within.</param>
320
/// <param name="subsection">subsection name, such a remote or branch name.</param>
321
/// <param name="name">name of the key to get.</param>
322
/// <param name="defaultValue">default value to return if no value was present.</param>
324
/// true if any value or defaultValue is true, false for missing or
327
public virtual bool GetBoolean(string section, string subsection, string name, bool
330
string n = GetRawString(section, subsection, name);
335
if (MAGIC_EMPTY_VALUE == n)
341
return StringUtils.ToBoolean(n);
343
catch (ArgumentException)
345
throw new ArgumentException(MessageFormat.Format(JGitText.Get().invalidBooleanValue
346
, section, name, n));
350
/// <summary>Parse an enumeration from the configuration.</summary>
351
/// <remarks>Parse an enumeration from the configuration.</remarks>
353
/// <param name="section">section the key is grouped within.</param>
354
/// <param name="subsection">subsection name, such a remote or branch name.</param>
355
/// <param name="name">name of the key to get.</param>
356
/// <param name="defaultValue">default value to return if no value was present.</param>
358
/// the selected enumeration value, or
359
/// <code>defaultValue</code>
362
public virtual T GetEnum<T>(string section, string subsection, string name, T defaultValue
365
Array all = AllValuesOf(defaultValue);
366
return GetEnum(all, section, subsection, name, defaultValue);
369
private static Array AllValuesOf<T>(T value)
373
return Enum.GetValues (typeof(T));
375
catch (Exception err)
377
string typeName = value.GetType().FullName;
378
string msg = MessageFormat.Format(JGitText.Get().enumValuesNotAvailable, typeName
380
throw new ArgumentException(msg, err);
384
/// <summary>Parse an enumeration from the configuration.</summary>
385
/// <remarks>Parse an enumeration from the configuration.</remarks>
387
/// <param name="all">
388
/// all possible values in the enumeration which should be
389
/// recognized. Typically
390
/// <code>EnumType.values()</code>
393
/// <param name="section">section the key is grouped within.</param>
394
/// <param name="subsection">subsection name, such a remote or branch name.</param>
395
/// <param name="name">name of the key to get.</param>
396
/// <param name="defaultValue">default value to return if no value was present.</param>
398
/// the selected enumeration value, or
399
/// <code>defaultValue</code>
402
public virtual T GetEnum<T>(Array all, string section, string subsection, string name
405
string value = GetString(section, subsection, name);
410
string n = value.Replace(' ', '_');
411
object trueState = null;
412
object falseState = null;
413
foreach (object e in all)
415
if (StringUtils.EqualsIgnoreCase(e.ToString(), n))
421
if (StringUtils.EqualsIgnoreCase(e.ToString(), "TRUE"))
427
if (StringUtils.EqualsIgnoreCase(e.ToString(), "FALSE"))
434
// This is an odd little fallback. C Git sometimes allows boolean
435
// values in a tri-state with other things. If we have both a true
436
// and a false value in our enumeration, assume its one of those.
438
if (trueState != null && falseState != null)
442
return StringUtils.ToBoolean(n) ? (T)trueState : (T)falseState;
444
catch (ArgumentException)
448
// Fall through and use our custom error below.
449
if (subsection != null)
451
throw new ArgumentException(MessageFormat.Format(JGitText.Get().enumValueNotSupported3
452
, section, name, value));
456
throw new ArgumentException(MessageFormat.Format(JGitText.Get().enumValueNotSupported2
457
, section, name, value));
461
/// <summary>Get string value</summary>
462
/// <param name="section">the section</param>
463
/// <param name="subsection">the subsection for the value</param>
464
/// <param name="name">the key name</param>
465
/// <returns>a String value from git config.</returns>
466
public virtual string GetString(string section, string subsection, string name)
468
return GetRawString(section, subsection, name);
472
/// Get a list of string values
474
/// If this instance was created with a base, the base's values are returned
478
/// Get a list of string values
480
/// If this instance was created with a base, the base's values are returned
483
/// <param name="section">the section</param>
484
/// <param name="subsection">the subsection for the value</param>
485
/// <param name="name">the key name</param>
486
/// <returns>array of zero or more values from the configuration.</returns>
487
public virtual string[] GetStringList(string section, string subsection, string name
491
if (baseConfig != null)
493
@base = baseConfig.GetStringList(section, subsection, name);
497
@base = EMPTY_STRING_ARRAY;
499
string[] self = GetRawStringList(section, subsection, name);
504
if (@base.Length == 0)
508
string[] res = new string[@base.Length + self.Length];
509
int n = @base.Length;
510
System.Array.Copy(@base, 0, res, 0, n);
511
System.Array.Copy(self, 0, res, n, self.Length);
515
/// <param name="section">section to search for.</param>
517
/// set of all subsections of specified section within this
518
/// configuration and its base configuration; may be empty if no
519
/// subsection exists. The set's iterator returns sections in the
520
/// order they are declared by the configuration starting from this
521
/// instance and progressing through the base.
523
public virtual ICollection<string> GetSubsections(string section)
525
return GetState().GetSubsections(section);
529
/// the sections defined in this
530
/// <see cref="Config">Config</see>
531
/// . The set's iterator
532
/// returns sections in the order they are declared by the
533
/// configuration starting from this instance and progressing through
536
public virtual ICollection<string> GetSections()
538
return GetState().GetSections();
541
/// <param name="section">the section</param>
542
/// <returns>the list of names defined for this section</returns>
543
public virtual ICollection<string> GetNames(string section)
545
return GetNames(section, null);
548
/// <param name="section">the section</param>
549
/// <param name="subsection">the subsection</param>
550
/// <returns>the list of names defined for this subsection</returns>
551
public virtual ICollection<string> GetNames(string section, string subsection)
553
return GetState().GetNames(section, subsection);
556
/// <summary>Obtain a handle to a parsed set of configuration values.</summary>
557
/// <remarks>Obtain a handle to a parsed set of configuration values.</remarks>
559
/// <param name="parser">
560
/// parser which can create the model if it is not already
561
/// available in this configuration file. The parser is also used
562
/// as the key into a cache and must obey the hashCode and equals
563
/// contract in order to reuse a parsed model.
565
/// <returns>the parsed object instance, which is cached inside this config.</returns>
566
public virtual T Get<T>(Config.SectionParser<T> parser)
568
ConfigSnapshot myState = GetState();
569
T obj = (T)myState.cache.Get(parser);
572
obj = parser.Parse(this);
573
myState.cache.Put(parser, obj);
578
/// <summary>Remove a cached configuration object.</summary>
580
/// Remove a cached configuration object.
582
/// If the associated configuration object has not yet been cached, this
583
/// method has no effect.
585
/// <param name="parser">parser used to obtain the configuration object.</param>
586
/// <seealso cref="Get{T}(SectionParser{T})">Get<T>(SectionParser<T>)</seealso>
587
public virtual void Uncache<_T0>(Config.SectionParser<_T0> parser)
589
Sharpen.Collections.Remove(state.Get().cache, parser);
592
/// <summary>Adds a listener to be notified about changes.</summary>
594
/// Adds a listener to be notified about changes.
596
/// Clients are supposed to remove the listeners after they are done with
598
/// <see cref="NGit.Events.ListenerHandle.Remove()">NGit.Events.ListenerHandle.Remove()
602
/// <param name="listener">the listener</param>
603
/// <returns>the handle to the registered listener</returns>
604
public virtual ListenerHandle AddChangeListener(ConfigChangedListener listener)
606
return listeners.AddConfigChangedListener(listener);
609
/// <summary>Determine whether to issue change events for transient changes.</summary>
611
/// Determine whether to issue change events for transient changes.
613
/// If <code>true</code> is returned (which is the default behavior),
614
/// <see cref="FireConfigChangedEvent()">FireConfigChangedEvent()</see>
615
/// will be called upon each change.
617
/// Subclasses that override this to return <code>false</code> are
618
/// responsible for issuing
619
/// <see cref="FireConfigChangedEvent()">FireConfigChangedEvent()</see>
623
/// <returns><code></code></returns>
624
protected internal virtual bool NotifyUponTransientChanges()
629
/// <summary>Notifies the listeners</summary>
630
protected internal virtual void FireConfigChangedEvent()
632
listeners.Dispatch(new ConfigChangedEvent());
635
private string GetRawString(string section, string subsection, string name)
637
string[] lst = GetRawStringList(section, subsection, name);
644
if (baseConfig != null)
646
return baseConfig.GetRawString(section, subsection, name);
655
private string[] GetRawStringList(string section, string subsection, string name)
657
return state.Get().Get(section, subsection, name);
660
private ConfigSnapshot GetState()
667
ConfigSnapshot @base = GetBaseState();
668
if (cur.baseState == @base)
672
upd = new ConfigSnapshot(cur.entryList, @base);
674
while (!state.CompareAndSet(cur, upd));
678
private ConfigSnapshot GetBaseState()
680
return baseConfig != null ? baseConfig.GetState() : null;
683
/// <summary>Add or modify a configuration value.</summary>
685
/// Add or modify a configuration value. The parameters will result in a
686
/// configuration entry like this.
688
/// [section "subsection"]
692
/// <param name="section">section name, e.g "branch"</param>
693
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
694
/// <param name="name">parameter name, e.g. "filemode"</param>
695
/// <param name="value">parameter value</param>
696
public virtual void SetInt(string section, string subsection, string name, int value
699
SetLong(section, subsection, name, value);
702
/// <summary>Add or modify a configuration value.</summary>
704
/// Add or modify a configuration value. The parameters will result in a
705
/// configuration entry like this.
707
/// [section "subsection"]
711
/// <param name="section">section name, e.g "branch"</param>
712
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
713
/// <param name="name">parameter name, e.g. "filemode"</param>
714
/// <param name="value">parameter value</param>
715
public virtual void SetLong(string section, string subsection, string name, long
719
if (value >= GiB && (value % GiB) == 0)
721
s = (value / GiB).ToString() + " g";
725
if (value >= MiB && (value % MiB) == 0)
727
s = (value / MiB).ToString() + " m";
731
if (value >= KiB && (value % KiB) == 0)
733
s = (value / KiB).ToString() + " k";
737
s = value.ToString();
741
SetString(section, subsection, name, s);
744
/// <summary>Add or modify a configuration value.</summary>
746
/// Add or modify a configuration value. The parameters will result in a
747
/// configuration entry like this.
749
/// [section "subsection"]
753
/// <param name="section">section name, e.g "branch"</param>
754
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
755
/// <param name="name">parameter name, e.g. "filemode"</param>
756
/// <param name="value">parameter value</param>
757
public virtual void SetBoolean(string section, string subsection, string name, bool
760
SetString(section, subsection, name, value ? "true" : "false");
763
/// <summary>Add or modify a configuration value.</summary>
765
/// Add or modify a configuration value. The parameters will result in a
766
/// configuration entry like this.
768
/// [section "subsection"]
773
/// <param name="section">section name, e.g "branch"</param>
774
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
775
/// <param name="name">parameter name, e.g. "filemode"</param>
776
/// <param name="value">parameter value</param>
777
public virtual void SetEnum<T>(string section, string subsection, string name, T
780
string n = value.ToString().ToLower().Replace('_', ' ');
781
SetString(section, subsection, name, n);
784
/// <summary>Add or modify a configuration value.</summary>
786
/// Add or modify a configuration value. The parameters will result in a
787
/// configuration entry like this.
789
/// [section "subsection"]
793
/// <param name="section">section name, e.g "branch"</param>
794
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
795
/// <param name="name">parameter name, e.g. "filemode"</param>
796
/// <param name="value">parameter value, e.g. "true"</param>
797
public virtual void SetString(string section, string subsection, string name, string
800
SetStringList(section, subsection, name, Sharpen.Collections.SingletonList(value)
804
/// <summary>Remove a configuration value.</summary>
805
/// <remarks>Remove a configuration value.</remarks>
806
/// <param name="section">section name, e.g "branch"</param>
807
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
808
/// <param name="name">parameter name, e.g. "filemode"</param>
809
public virtual void Unset(string section, string subsection, string name)
811
SetStringList(section, subsection, name, Sharpen.Collections.EmptyList<string>());
814
/// <summary>Remove all configuration values under a single section.</summary>
815
/// <remarks>Remove all configuration values under a single section.</remarks>
816
/// <param name="section">section name, e.g "branch"</param>
817
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
818
public virtual void UnsetSection(string section, string subsection)
825
res = UnsetSection(src, section, subsection);
827
while (!state.CompareAndSet(src, res));
830
private ConfigSnapshot UnsetSection(ConfigSnapshot srcState, string section, string
833
int max = srcState.entryList.Count;
834
AList<ConfigLine> r = new AList<ConfigLine>(max);
835
bool lastWasMatch = false;
836
foreach (ConfigLine e in srcState.entryList)
838
if (e.Match(section, subsection))
840
// Skip this record, it's for the section we are removing.
844
if (lastWasMatch && e.section == null && e.subsection == null)
848
// skip this padding line in the section.
854
/// <summary>Set a configuration value.</summary>
856
/// Set a configuration value.
858
/// [section "subsection"]
862
/// <param name="section">section name, e.g "branch"</param>
863
/// <param name="subsection">optional subsection value, e.g. a branch name</param>
864
/// <param name="name">parameter name, e.g. "filemode"</param>
865
/// <param name="values">list of zero or more values for this key.</param>
866
public virtual void SetStringList(string section, string subsection, string name,
867
IList<string> values)
874
res = ReplaceStringList(src, section, subsection, name, values);
876
while (!state.CompareAndSet(src, res));
877
if (NotifyUponTransientChanges())
879
FireConfigChangedEvent();
883
private ConfigSnapshot ReplaceStringList(ConfigSnapshot srcState, string section,
884
string subsection, string name, IList<string> values)
886
IList<ConfigLine> entries = Copy(srcState, values);
889
int insertPosition = -1;
890
// Reset the first n Entry objects that match this input name.
892
while (entryIndex < entries.Count && valueIndex < values.Count)
894
ConfigLine e = entries[entryIndex];
895
if (e.Match(section, subsection, name))
897
entries.Set(entryIndex, e.ForValue(values[valueIndex++]));
898
insertPosition = entryIndex + 1;
902
// Remove any extra Entry objects that we no longer need.
904
if (valueIndex == values.Count && entryIndex < entries.Count)
906
while (entryIndex < entries.Count)
908
ConfigLine e = entries[entryIndex++];
909
if (e.Match(section, subsection, name))
911
entries.Remove(--entryIndex);
915
// Insert new Entry objects for additional/new values.
917
if (valueIndex < values.Count && entryIndex == entries.Count)
919
if (insertPosition < 0)
921
// We didn't find a matching key above, but maybe there
922
// is already a section available that matches. Insert
923
// after the last key of that section.
925
insertPosition = FindSectionEnd(entries, section, subsection);
927
if (insertPosition < 0)
929
// We didn't find any matching section header for this key,
930
// so we must create a new section header at the end.
932
ConfigLine e = new ConfigLine();
934
e.subsection = subsection;
936
insertPosition = entries.Count;
938
while (valueIndex < values.Count)
940
ConfigLine e = new ConfigLine();
942
e.subsection = subsection;
944
e.value = values[valueIndex++];
945
entries.Add(insertPosition++, e);
948
return NewState(entries);
951
private static IList<ConfigLine> Copy(ConfigSnapshot src, IList<string> values)
953
// At worst we need to insert 1 line for each value, plus 1 line
954
// for a new section header. Assume that and allocate the space.
956
int max = src.entryList.Count + values.Count + 1;
957
AList<ConfigLine> r = new AList<ConfigLine>(max);
958
Sharpen.Collections.AddAll(r, src.entryList);
962
private static int FindSectionEnd(IList<ConfigLine> entries, string section, string
965
for (int i = 0; i < entries.Count; i++)
967
ConfigLine e = entries[i];
968
if (e.Match(section, subsection, null))
971
while (i < entries.Count)
974
if (e.Match(section, subsection, e.name))
989
/// <returns>this configuration, formatted as a Git style text file.</returns>
990
public virtual string ToText()
992
StringBuilder @out = new StringBuilder();
993
foreach (ConfigLine e in state.Get().entryList)
995
if (e.prefix != null)
997
@out.Append(e.prefix);
999
if (e.section != null && e.name == null)
1002
@out.Append(e.section);
1003
if (e.subsection != null)
1006
string escaped = EscapeValue(e.subsection);
1007
// make sure to avoid double quotes here
1008
bool quoted = escaped.StartsWith("\"") && escaped.EndsWith("\"");
1013
@out.Append(escaped);
1023
if (e.section != null && e.name != null)
1025
if (e.prefix == null || string.Empty.Equals(e.prefix))
1029
@out.Append(e.name);
1030
if (MAGIC_EMPTY_VALUE != e.value)
1033
if (e.value != null)
1036
@out.Append(EscapeValue(e.value));
1039
if (e.suffix != null)
1045
if (e.suffix != null)
1047
@out.Append(e.suffix);
1051
return @out.ToString();
1054
/// <summary>Clear this configuration and reset to the contents of the parsed string.
1056
/// <remarks>Clear this configuration and reset to the contents of the parsed string.
1058
/// <param name="text">Git style text file listing configuration properties.</param>
1059
/// <exception cref="NGit.Errors.ConfigInvalidException">
1060
/// the text supplied is not formatted correctly. No changes were
1062
/// <code>this</code>
1065
public virtual void FromText(string text)
1067
IList<ConfigLine> newEntries = new AList<ConfigLine>();
1068
Config.StringReader @in = new Config.StringReader(text);
1069
ConfigLine last = null;
1070
ConfigLine e = new ConfigLine();
1073
int input = @in.Read();
1078
char c = (char)input;
1081
// End of this entry.
1082
newEntries.AddItem(e);
1083
if (e.section != null)
1087
e = new ConfigLine();
1091
if (e.suffix != null)
1093
// Everything up until the end-of-line is in the suffix.
1098
if (';' == c || '#' == c)
1100
// The rest of this line is a comment; put into suffix.
1101
e.suffix = c.ToString();
1105
if (e.section == null && char.IsWhiteSpace(c))
1107
// Save the leading whitespace (if any).
1108
if (e.prefix == null)
1110
e.prefix = string.Empty;
1118
// This is a section header.
1119
e.section = ReadSectionName(@in);
1123
e.subsection = ReadValue(@in, true, '"');
1128
throw new ConfigInvalidException(JGitText.Get().badGroupHeader);
1130
e.suffix = string.Empty;
1137
e.section = last.section;
1138
e.subsection = last.subsection;
1140
e.name = ReadKeyName(@in);
1141
if (e.name.EndsWith("\n"))
1143
e.name = Sharpen.Runtime.Substring(e.name, 0, e.name.Length - 1);
1144
e.value = MAGIC_EMPTY_VALUE;
1148
e.value = ReadValue(@in, false, -1);
1153
throw new ConfigInvalidException(JGitText.Get().invalidLineInConfigFile);
1161
state.Set(NewState(newEntries));
1164
private ConfigSnapshot NewState()
1166
return new ConfigSnapshot(Sharpen.Collections.EmptyList<ConfigLine>(), GetBaseState
1170
private ConfigSnapshot NewState(IList<ConfigLine> entries)
1172
return new ConfigSnapshot(Sharpen.Collections.UnmodifiableList(entries), GetBaseState
1176
/// <summary>Clear the configuration file</summary>
1177
protected internal virtual void Clear()
1179
state.Set(NewState());
1182
/// <exception cref="NGit.Errors.ConfigInvalidException"></exception>
1183
private static string ReadSectionName(Config.StringReader @in)
1185
StringBuilder name = new StringBuilder();
1191
throw new ConfigInvalidException(JGitText.Get().unexpectedEndOfConfigFile);
1198
if (' ' == c || '\t' == c)
1205
throw new ConfigInvalidException(JGitText.Get().unexpectedEndOfConfigFile);
1212
if (' ' == c || '\t' == c)
1217
throw new ConfigInvalidException(MessageFormat.Format(JGitText.Get().badSectionEntry
1222
if (char.IsLetterOrDigit((char)c) || '.' == c || '-' == c)
1224
name.Append((char)c);
1228
throw new ConfigInvalidException(MessageFormat.Format(JGitText.Get().badSectionEntry
1232
return name.ToString();
1235
/// <exception cref="NGit.Errors.ConfigInvalidException"></exception>
1236
private static string ReadKeyName(Config.StringReader @in)
1238
StringBuilder name = new StringBuilder();
1244
throw new ConfigInvalidException(JGitText.Get().unexpectedEndOfConfigFile);
1250
if (' ' == c || '\t' == c)
1257
throw new ConfigInvalidException(JGitText.Get().unexpectedEndOfConfigFile);
1263
if (';' == c || '#' == c || '\n' == c)
1268
if (' ' == c || '\t' == c)
1273
throw new ConfigInvalidException(JGitText.Get().badEntryDelimiter);
1277
if (char.IsLetterOrDigit((char)c) || c == '-')
1279
// From the git-config man page:
1280
// The variable names are case-insensitive and only
1281
// alphanumeric characters and - are allowed.
1282
name.Append((char)c);
1289
name.Append((char)c);
1294
throw new ConfigInvalidException(MessageFormat.Format(JGitText.Get().badEntryName
1299
return name.ToString();
1302
/// <exception cref="NGit.Errors.ConfigInvalidException"></exception>
1303
private static string ReadValue(Config.StringReader @in, bool quote, int eol)
1305
StringBuilder value = new StringBuilder();
1312
if (value.Length == 0)
1314
throw new ConfigInvalidException(JGitText.Get().unexpectedEndOfConfigFile);
1322
throw new ConfigInvalidException(JGitText.Get().newlineInQuotesNotAllowed);
1333
if (char.IsWhiteSpace((char)c))
1338
if (';' == c || '#' == c)
1346
if (value.Length > 0)
1359
throw new ConfigInvalidException(JGitText.Get().endOfFileInEscape);
1405
throw new ConfigInvalidException(MessageFormat.Format(JGitText.Get().badEscape, (
1415
value.Append((char)c);
1417
return value.Length > 0 ? value.ToString() : null;
1420
/// <summary>Parses a section of the configuration into an application model object.</summary>
1422
/// Parses a section of the configuration into an application model object.
1424
/// Instances must implement hashCode and equals such that model objects can
1425
/// be cached by using the
1426
/// <code>SectionParser</code>
1427
/// as a key of a HashMap.
1430
/// <code>SectionParser</code>
1431
/// itself is used as the key of the internal
1432
/// HashMap applications should be careful to ensure the SectionParser key
1433
/// does not retain unnecessary application state which may cause memory to
1434
/// be held longer than expected.
1437
public interface SectionParser<T>
1439
/// <summary>Create a model object from a configuration.</summary>
1440
/// <remarks>Create a model object from a configuration.</remarks>
1441
/// <param name="cfg">the configuration to read values from.</param>
1442
/// <returns>the application model instance.</returns>
1443
T Parse(Config cfg);
1446
private class StringReader
1448
private readonly char[] buf;
1452
internal StringReader(string @in)
1454
buf = @in.ToCharArray();
1457
internal virtual int Read()
1463
catch (IndexOutOfRangeException)
1470
internal virtual void Reset()