1 /*
2  *             Copyright Andrej Mitrovic 2013.
3  *  Distributed under the Boost Software License, Version 1.0.
4  *     (See accompanying file LICENSE_1_0.txt or copy at
5  *           http://www.boost.org/LICENSE_1_0.txt)
6  */
7 module git.common;
8 
9 // todo: port more of these from git.c.common
10 
11 import std.array;
12 import std.conv;
13 import std.exception;
14 import std.stdio;
15 import std.string;
16 import std.traits;
17 
18 import deimos.git2.common;
19 import deimos.git2.types;
20 
21 import git.config;
22 import git.exception;
23 import git.types;
24 import git.util;
25 
26 package
27 {
28     version(Windows)
29         enum GitPathSep = ";";
30     else
31         enum GitPathSep = ":";
32 }
33 
34 /**
35  * The maximum length of a valid git path.
36  */
37 enum MaxGitPathLen = GIT_PATH_MAX;
38 
39 /**
40  * The string representation of the null object ID.
41  */
42 enum GitOid_HexZero = GIT_OID_HEX_ZERO;
43 
44 /**
45     The capabilities of libgit2.
46 */
47 struct GitFeatures
48 {
49     /**
50         Libgit2 was compiled with thread support. Note that thread support is
51         still to be seen as a 'work in progress' - basic object lookups are
52         believed to be threadsafe, but other operations may not be.
53     */
54     bool usesThreads;
55 
56     /**
57         Libgit2 supports the https:// protocol. This requires the openssl
58         library to be found when compiling libgit2.
59     */
60     bool usesSSL;
61 }
62 
63 /**
64     Get the capabilities of the runtime $(D libgit2) library.
65 */
66 GitFeatures getLibGitFeatures()
67 {
68     typeof(return) result;
69 
70     int flags = git_libgit2_capabilities();
71 
72     if (flags | GIT_CAP_THREADS)
73         result.usesThreads = true;
74 
75     if (flags | GIT_CAP_HTTPS)
76         result.usesSSL = true;
77 
78     return result;
79 }
80 
81 ///
82 unittest
83 {
84     auto features = getLibGitFeatures();
85     if (features.usesSSL) { }
86 }
87 
88 /** Memory caching mode for libgit2. */
89 enum CacheMode
90 {
91     ///
92     disabled,
93 
94     ///
95     enabled
96 }
97 
98 /**
99     Static functions with which to query or set global libgit2 options.
100 */
101 struct globalOptions
102 {
103 static:
104     /// Get the maximum mmap window size.
105     @property size_t mwindowSize()
106     {
107         typeof(return) result;
108         git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &result);
109         return result;
110     }
111 
112     /// Set the maximum mmap window size.
113     @property void mwindowSize(size_t size)
114     {
115         git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, size);
116     }
117 
118     ///
119     unittest
120     {
121         auto oldSize = globalOptions.mwindowSize;
122         scope(exit) globalOptions.mwindowSize = oldSize;
123 
124         globalOptions.mwindowSize = 1;
125         assert(globalOptions.mwindowSize == 1);
126     }
127 
128     /// Get the maximum memory in bytes that will be mapped in total by the library.
129     @property size_t mwindowMappedLimit()
130     {
131         typeof(return) result;
132         git_libgit2_opts(GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, &result);
133         return result;
134     }
135 
136     /// Set the maximum amount of memory in bytes that can be mapped at any time by the library.
137     @property void mwindowMappedLimit(size_t limit)
138     {
139         git_libgit2_opts(GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, limit);
140     }
141 
142     ///
143     unittest
144     {
145         auto oldLimit = globalOptions.mwindowMappedLimit;
146         scope(exit) globalOptions.mwindowMappedLimit = oldLimit;
147 
148         globalOptions.mwindowMappedLimit = 1;
149         assert(globalOptions.mwindowMappedLimit == 1);
150     }
151 
152     /**
153         Get the search paths for a given level of config data.
154 
155         $(B Note:) $(D configLevel) must be one of $(D GitConfigLevel.system),
156         $(D GitConfigLevel.xdg), or $(D GitConfigLevel.global).
157     */
158     string[] getSearchPaths(GitConfigLevel configLevel)
159     {
160         int level = cast(int)configLevel;
161         char[MaxGitPathLen] buffer;
162 
163         require(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, &buffer, buffer.length) == 0);
164         return to!string(buffer.ptr).split(GitPathSep);
165     }
166 
167     /**
168         Set the search paths for a given level of config data.
169 
170         $(B Note:) Use the magic path $(B "$PATH") to include the old value
171         of the path. This is useful for prepending or appending paths.
172 
173         $(B Note:) Passing a null or empty array of paths will reset the
174         paths to their defaults (based on environment variables).
175 
176         $(B Note:) $(D configLevel) must be one of $(D GitConfigLevel.system),
177         $(D GitConfigLevel.xdg), or $(D GitConfigLevel.global).
178     */
179     void setSearchPaths(GitConfigLevel configLevel, string[] paths)
180     {
181         int level = cast(int)configLevel;
182         const(char)* cPaths = paths.join(GitPathSep).gitStr();
183         require(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, cPaths) == 0);
184     }
185 
186     ///
187     unittest
188     {
189         foreach (config; EnumMembers!GitConfigLevel)
190         {
191             if (config != GitConfigLevel.system
192                 && config != GitConfigLevel.xdg
193                 && config != GitConfigLevel.global)
194             {
195                 assertThrown!GitException(getSearchPaths(config));
196                 assertThrown!GitException(setSearchPaths(config, []));
197                 continue;
198             }
199             else
200             {
201                 auto oldPaths = getSearchPaths(config);
202                 scope(exit) setSearchPaths(config, oldPaths);
203 
204                 auto newPaths = ["/foo", "$PATH", "/foo/bar"];
205                 setSearchPaths(config, newPaths);
206 
207                 auto chained = newPaths[0] ~ oldPaths ~ newPaths[2];
208                 assert(getSearchPaths(config) == chained);
209 
210                 setSearchPaths(config, []);
211                 assert(getSearchPaths(config) == oldPaths);
212             }
213         }
214     }
215 
216     /**
217         Set the maximum data size for the given type of object to be
218         considered eligible for caching in memory.  Setting to value to
219         zero means that that type of object will not be cached.
220 
221         Defaults to 0 for $(D GitType.blob) (i.e. won't cache blobs) and 4k
222         for $(D GitType.commit), $(D GitType.tree), and $(D GitType.tag).
223     */
224     void setCacheObjectLimit(GitType type, size_t size)
225     {
226         require(git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, cast(git_otype)type, size) == 0);
227     }
228 
229     ///
230     unittest
231     {
232         setCacheObjectLimit(GitType.commit, 4096);
233     }
234 
235     /**
236         Set the maximum total data size that will be cached in memory
237         across all repositories before libgit2 starts evicting objects
238         from the cache.  This is a soft limit, in that the library might
239         briefly exceed it, but will start aggressively evicting objects
240         from cache when that happens.
241 
242         The default cache size is 256Mb.
243     */
244     void setCacheMaxSize(ptrdiff_t maxStorageBytes)
245     {
246         require(git_libgit2_opts(GIT_OPT_SET_CACHE_MAX_SIZE, maxStorageBytes) == 0);
247     }
248 
249     /** Return the default cache size - 256Mb. */
250     @property ptrdiff_t defaultCacheMaxSize()
251     {
252         return 256 * 1024 * 1024;
253     }
254 
255     ///
256     unittest
257     {
258         setCacheMaxSize(defaultCacheMaxSize);
259     }
260 
261     /**
262         Enable or disable caching completely.
263 
264         Since caches are repository-specific, disabling the cache
265         cannot immediately clear all the cached objects, but each cache
266         will be cleared on the next attempt to update anything in it.
267     */
268     void setCacheMode(CacheMode mode)
269     {
270         require(git_libgit2_opts(GIT_OPT_ENABLE_CACHING, cast(int)mode) == 0);
271     }
272 
273     ///
274     unittest
275     {
276         setCacheMode(CacheMode.disabled);
277         setCacheMode(CacheMode.enabled);
278     }
279 
280     /** The cache status of libgit2. */
281     struct CacheMemory
282     {
283         /// current bytes in the cache.
284         ptrdiff_t currentSize;
285 
286         /// the maximum bytes allowed in the cache.
287         ptrdiff_t maxSize;
288     }
289 
290     /** Get the current status of the cache. */
291     CacheMemory getCacheMemory()
292     {
293         ptrdiff_t current;
294         ptrdiff_t allowed;
295         require(git_libgit2_opts(GIT_OPT_GET_CACHED_MEMORY, &current, &allowed) == 0);
296 
297         return CacheMemory(current, allowed);
298     }
299 
300     ///
301     unittest
302     {
303         auto cache = getCacheMemory();
304     }
305 
306     /// alternate spelling
307     alias getCachedMemory = getCacheMemory;
308 }