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.repository;
8 
9 import core.exception;
10 
11 import std.algorithm;
12 import std.conv;
13 import std.exception;
14 import std.file;
15 import std.path;
16 import std.process;
17 import std.range;
18 import std.stdio;
19 import std..string;
20 import std.traits;
21 import std.typecons;
22 import std.typetuple;
23 
24 import deimos.git2.common;
25 import deimos.git2.errors;
26 import deimos.git2.ignore;
27 import deimos.git2.oid;
28 import deimos.git2.repository;
29 import deimos.git2.types;
30 import deimos.git2.branch;
31 
32 import git.common;
33 import git.exception;
34 import git.index;
35 import git.oid;
36 import git.reference;
37 import git.types;
38 import git.util;
39 import git.version_;
40 import deimos.git2.refs;
41 import deimos.git2.strarray;
42 
43 version(unittest)
44 {
45     enum _baseTestDir = "test";
46     enum _testRepo = "test/repo/.git";
47     string _userRepo = buildPath(_baseTestDir, "_myTestRepo");
48 }
49 
50 
51 GitRepo openRepository(string path)
52 {
53     git_repository* dst;
54     require(git_repository_open(&dst, path.gitStr) == 0);
55     return GitRepo(dst);
56 }
57 
58 GitRepo openRepositoryExt(string path, GitRepositoryOpenFlags flags, string ceiling_dirs)
59 {
60     git_repository* dst;
61     require(git_repository_open_ext(&dst, path.gitStr, flags, ceiling_dirs.gitStr) == 0);
62     return GitRepo(dst);
63 }
64 
65 GitRepo openBareRepository(string bare_path)
66 {
67     git_repository* dst;
68     require(git_repository_open_bare(&dst, bare_path.toStringz()) == 0);
69     return GitRepo(dst);
70 }
71 
72 /**
73     Create a new Git repository in the given folder.
74 
75     Parameters:
76 
77     $(D path): the path to the git repository.
78 
79     $(D openBare): if equal to $(D OpenBare.yes), a Git
80     repository without a working directory is created at the
81     pointed path.
82 
83     If equal to $(D OpenBare.no), the provided path will be
84     considered as the working directory into which the .git
85     directory will be created.
86 */
87 GitRepo initRepository(in char[] path, OpenBare openBare)
88 {
89     git_repository* repo;
90     require(git_repository_init(&repo, path.gitStr, cast(bool)openBare) == 0);
91     return GitRepo(repo);
92 }
93 alias initRepo = initRepository;
94 
95 ///
96 unittest
97 {
98     // create a bare test repository and ensure the HEAD file exists
99     auto repo = initRepo(_userRepo, OpenBare.yes);
100     scope(exit) rmdirRecurse(_userRepo);
101     assert(buildPath(_userRepo, "HEAD").exists);
102 }
103 
104 ///
105 unittest
106 {
107     // create a non-bare test repository and ensure the .git/HEAD file exists
108     auto repo = initRepo(_userRepo, OpenBare.no);
109     scope(exit) rmdirRecurse(_userRepo);
110     assert(buildPath(_userRepo, ".git/HEAD").exists);
111 }
112 
113 GitRepo initRepository(string path, GitRepositoryInitOptions options)
114 {
115     git_repository* ret;
116     git_repository_init_options copts;
117     copts.flags = cast(git_repository_init_flag_t)options.flags;
118     copts.mode = cast(git_repository_init_mode_t)options.mode;
119     copts.workdir_path = options.workdirPath.gitStr;
120     copts.description = options.description.gitStr;
121     copts.template_path = options.templatePath.gitStr;
122     copts.initial_head = options.initialHead.gitStr;
123     copts.origin_url = options.originURL.gitStr;
124     require(git_repository_init_ext(&ret, path.toStringz(), &copts) == 0);
125     return GitRepo(ret);
126 }
127 
128 /**
129     Discover a git repository and return its path if found.
130 
131     The lookup starts from $(D startPath) and continues searching across
132     parent directories. The lookup stops when one of the following
133     becomes true:
134 
135     $(LI a git repository is found.)
136     $(LI a directory referenced in $(D ceilingDirs) has been reached.)
137     $(LI the filesystem changed (if acrossFS is equal to $(D AcrossFS.no).))
138 
139     Parameters:
140 
141     $(D startPath): The base path where the lookup starts.
142 
143     $(D ceilingDirs): An array of absolute paths which are symbolic-link-free.
144     If any of these paths are reached a $(D GitException) will be thrown.
145 
146     $(D acrossFS): If equal to $(D AcrossFS.yes) the lookup will
147     continue when a filesystem device change is detected while exploring
148     parent directories, otherwise $(D GitException) is thrown.
149 
150     $(B Note:) The lookup always performs on $(D startPath) even if
151     $(D startPath) is listed in $(D ceilingDirs).
152  */
153 string discoverRepo(in char[] startPath, string[] ceilingDirs = null, AcrossFS acrossFS = AcrossFS.yes)
154 {
155     char[MaxGitPathLen] buffer;
156     const c_ceilDirs = ceilingDirs.join(GitPathSep).gitStr;
157 
158     version(assert)
159     {
160         foreach (path; ceilingDirs)
161             assert(path.isAbsolute, format("Error: Path in ceilingDirs is not absolute: '%s'", path));
162     }
163 
164     require(git_repository_discover(buffer.ptr, buffer.length, startPath.gitStr, cast(bool)acrossFS, c_ceilDirs) == 0);
165 
166     return to!string(buffer.ptr);
167 }
168 
169 ///
170 unittest
171 {
172     /**
173         look for the .git repo in "test/repo/a/".
174         The .git directory will be found one dir up, and will
175         contain the line 'gitdir: ../../.git/modules/test/repo'.
176         The function will expand this line and return the true
177         repository location.
178     */
179     string path = buildPath(_testRepo.dirName, "a");
180     string repoPath = discoverRepo(path);
181     assert(repoPath.relativePath.toPosixPath == ".git/modules/test/repo");
182 
183     // verify the repo can be opened
184     auto repo = openRepository(repoPath);
185 }
186 
187 ///
188 unittest
189 {
190     // ceiling dir is found before any git repository
191     string path = buildPath(_testRepo.dirName, "a");
192     string[] ceils = [_testRepo.dirName.absolutePath.buildNormalizedPath];
193     assertThrown!GitException(discoverRepo(path, ceils));
194 }
195 
196 ///
197 unittest
198 {
199     // all ceiling paths must be absolute
200     string[] ceils = ["../.."];
201     assertThrown!AssertError(discoverRepo(_testRepo.dirName, ceils));
202 }
203 
204 
205 /**
206     The structure representing a git repository.
207 */
208 struct GitRepo
209 {
210     /// Default-construction is disabled
211     //@disable this();
212 
213     ///
214     unittest
215     {
216         //static assert(!__traits(compiles, GitRepo()));
217     }
218 
219     // internal
220     package this(git_repository* payload)
221     {
222         _data = Data(payload);
223     }
224 
225     /**
226         Open a git repository.
227 
228         Parameters:
229 
230         $(D path) must either be a path to the .git directory or
231         the base path of the .git directory.
232 
233         If $(D path) does not exist or if the .git directory is not
234         found in $(D path), a $(D GitException) is thrown.
235      */
236     deprecated("Please use openRepository instead.") this(in char[] path)
237     {
238         git_repository* repo;
239         require(git_repository_open(&repo, path.gitStr) == 0);
240         _data = Data(repo);
241     }
242 
243     ///
244     unittest
245     {
246         // throw when path does not exist
247         assertThrown!GitException(openRepository(r".\invalid\path\.git"));
248 
249         // open using the path of the .git directory
250         auto repo1 = openRepository(_testRepo);
251 
252         // open using the base path of the .git directory
253         auto repo2 = openRepository(_testRepo.dirName);
254     }
255 
256     @property GitReference head()
257     {
258         git_reference* ret;
259         require(git_repository_head(&ret, this.cHandle) == 0);
260         return GitReference(this, ret);
261     }
262 
263     /**
264         Check if this repository's HEAD is detached.
265 
266         A repository's HEAD is detached when it
267         points directly to a commit instead of a branch.
268     */
269     @property bool isHeadDetached()
270     {
271         return requireBool(git_repository_head_detached(_data._payload));
272     }
273 
274     ///
275     unittest
276     {
277         // by default test repo's HEAD is pointing to a commit
278         auto repo1 = openRepository(_testRepo);
279         assert(repo1.isHeadDetached);
280 
281         // new repo does not have a detached head
282         auto repo2 = initRepo(_userRepo, OpenBare.no);
283         scope(exit) rmdirRecurse(_userRepo);
284         assert(!repo2.isHeadDetached);
285     }
286 
287     /**
288         Check if the current branch is an orphan.
289 
290         An orphan branch is one named from HEAD but which doesn't exist in
291         the refs namespace, because it doesn't have any commit to point to.
292     */
293     @property bool isHeadOrphan()
294     {
295         static if (targetLibGitVersion == VersionInfo(0, 19, 0)) {
296             return requireBool(git_repository_head_orphan(_data._payload));
297         } else {
298             return requireBool(git_repository_head_unborn(_data._payload));
299         }
300     }
301 
302     ///
303     unittest
304     {
305         auto repo1 = openRepository(_testRepo);
306         assert(!repo1.isHeadOrphan);
307 
308         // new repo has orphan branch
309         auto repo2 = initRepo(_userRepo, OpenBare.no);
310         scope(exit) rmdirRecurse(_userRepo);
311         assert(repo2.isHeadOrphan);
312     }
313 
314     /**
315         Check if this repository is empty.
316 
317         An empty repository is one which has just been
318         initialized and contains no references.
319     */
320     @property bool isEmpty()
321     {
322         return requireBool(git_repository_is_empty(_data._payload));
323     }
324 
325     ///
326     unittest
327     {
328         // existing repo is non-empty
329         auto repo1 = openRepository(_testRepo);
330         assert(!repo1.isEmpty);
331 
332         // new repo is empty
333         auto repo2 = initRepo(_userRepo, OpenBare.no);
334         scope(exit) rmdirRecurse(_userRepo);
335         assert(repo2.isEmpty);
336     }
337 
338     /**
339         Check if this repository is a bare repository.
340     */
341     @property bool isBare()
342     {
343         return requireBool(git_repository_is_bare(_data._payload));
344     }
345 
346     ///
347     unittest
348     {
349         // existing repo is not bare
350         auto repo = openRepository(_testRepo);
351         assert(!repo.isBare);
352     }
353 
354     ///
355     unittest
356     {
357         // new bare repo is bare
358         auto repo = initRepo(_userRepo, OpenBare.yes);
359         scope(exit) rmdirRecurse(_userRepo);
360         assert(repo.isBare);
361     }
362 
363     ///
364     unittest
365     {
366         // new non-bare repo is not bare
367         auto repo = initRepo(_userRepo, OpenBare.no);
368         scope(exit) rmdirRecurse(_userRepo);
369         assert(!repo.isBare);
370     }
371 
372     /**
373         Get the path of this repository.
374 
375         This is the path of the `.git` folder for normal repositories,
376         or of the repository itself for bare repositories.
377 
378         $(B Note:) Submodule repositories will have their path set
379         by the $(B gitdir) option in the `.git` file.
380     */
381     @property string path()
382     {
383         return to!string(git_repository_path(_data._payload));
384     }
385 
386     ///
387     unittest
388     {
389         // existing repo path
390         auto repo = openRepository(_testRepo);
391         assert(repo.path.relativePath.toPosixPath == ".git/modules/test/repo");
392     }
393 
394     ///
395     unittest
396     {
397         // new bare repo path is the path of the repo itself
398         auto repo = initRepo(_userRepo, OpenBare.yes);
399         scope(exit) rmdirRecurse(_userRepo);
400         assert(repo.path.relativePath.toPosixPath == "test/_myTestRepo");
401     }
402 
403     ///
404     unittest
405     {
406         // new non-bare repo path is the path of the .git directory
407         auto repo = initRepo(_userRepo, OpenBare.no);
408         scope(exit) rmdirRecurse(_userRepo);
409         assert(repo.path.relativePath.toPosixPath == "test/_myTestRepo/.git");
410     }
411 
412     /**
413         Get the path of the working directory of this repository.
414 
415         If the repository is bare, this function will return $(D null).
416 
417         $(B Note): Unlike $(D path), this function is not affected
418         by whether this repository is a submodule of another repository.
419     */
420     @property string workPath()
421     {
422         return to!string(git_repository_workdir(_data._payload));
423     }
424 
425     ///
426     unittest
427     {
428         // existing repo work path is different to the path of the .git directory,
429         // since this repo is a submodule
430         auto repo = openRepository(_testRepo);
431         assert(repo.workPath.relativePath.toPosixPath == "test/repo");
432     }
433 
434     ///
435     unittest
436     {
437         // new bare repo work path is empty
438         auto repo = initRepo(_userRepo, OpenBare.yes);
439         scope(exit) rmdirRecurse(_userRepo);
440         assert(repo.workPath.relativePath.toPosixPath is null);
441     }
442 
443     ///
444     unittest
445     {
446         // new non-bare repo work path is by default the directory path of the .git directory
447         auto repo = initRepo(_userRepo, OpenBare.no);
448         scope(exit) rmdirRecurse(_userRepo);
449         assert(repo.workPath.relativePath.toPosixPath == "test/_myTestRepo");
450     }
451 
452     /**
453         Set the work path of this repository.
454 
455         The work path doesn't need to be the same one
456         that contains the `.git` folder for this repository.
457 
458         If this repository is bare, setting its work path
459         will turn it into a normal repository capable of performing
460         all the common workdir operations (checkout, status, index
461         manipulation, etc).
462 
463         If $(D updateGitlink) equals $(D UpdateGitlink.yes), gitlink
464         will be created or updated in the work path. Additionally if
465         the work path is not the parent of the $(B .git) directory
466         the $(B core.worktree) option will be set in the configuration.
467     */
468     void setWorkPath(in char[] newWorkPath, UpdateGitlink updateGitlink = UpdateGitlink.no)
469     {
470         require(git_repository_set_workdir(_data._payload, newWorkPath.gitStr, cast(int)updateGitlink) == 0);
471     }
472 
473     ///
474     unittest
475     {
476         // new bare repo work path is empty
477         auto repo = initRepo(_userRepo, OpenBare.yes);
478         scope(exit) rmdirRecurse(_userRepo);
479         assert(repo.workPath.relativePath.toPosixPath is null);
480         assert(repo.isBare);
481 
482         // set a new work path for the bare repo, verify it's set, and also
483         // verify repo is no longer a bare repo
484         repo.setWorkPath(_testRepo);
485         assert(repo.workPath.relativePath.toPosixPath == _testRepo);
486         assert(!repo.isBare);
487     }
488 
489     @property GitIndex index()
490     {
491         git_index* dst;
492         require(git_repository_index(&dst, this.cHandle) == 0);
493         return GitIndex(this, dst);
494     }
495 
496     /**
497         Check if the merge message file exists for this repository.
498     */
499     @property bool mergeMsgExists()
500     {
501         auto result = git_repository_message(null, 0, _data._payload);
502 
503         if (result == GIT_ENOTFOUND)
504             return false;
505 
506         require(result >= 0);
507         return true;
508     }
509 
510     ///
511     unittest
512     {
513         // write a merge message file and verify it can be read
514         auto repo = initRepo(_userRepo, OpenBare.yes);
515         scope(exit) rmdirRecurse(_userRepo);
516         assert(!repo.mergeMsgExists);
517 
518         string msgPath = buildPath(repo.path, "MERGE_MSG");
519         std.file.write(msgPath, "");
520         assert(repo.mergeMsgExists);
521         assert(repo.mergeMsg !is null && repo.mergeMsg.length == 0);
522     }
523 
524     /**
525         Retrieve the merge message for this repository.
526 
527         Operations such as git revert/cherry-pick/merge with the -n option
528         stop just short of creating a commit with the changes and save
529         their prepared message in .git/MERGE_MSG so the next git-commit
530         execution can present it to the user for them to amend if they
531         wish.
532 
533         Use this function to get the contents of this file.
534 
535         $(B Note:) Remember to remove the merge message file after you
536         create the commit, by calling $(D removeMergeMsg).
537         Use $(D mergeMsgExists) if you want to explicitly check for the
538         existence of the merge message file.
539 
540         $(B Note:) This function returns an empty string when the message
541         file is empty, but returns $(D null) if the message file cannot be found.
542     */
543     @property string mergeMsg()
544     {
545         char[MaxGitPathLen] buffer;
546         auto result = git_repository_message(buffer.ptr, buffer.length, _data._payload);
547 
548         if (result == GIT_ENOTFOUND)
549             return null;
550 
551         require(result >= 0);
552 
553         string msg = to!string(buffer.ptr);
554         if (msg is null)
555             msg = "";  // null signals a missing file
556 
557         return msg;
558     }
559 
560     ///
561     unittest
562     {
563         // write a merge message file and verify it can be read
564         auto repo = initRepo(_userRepo, OpenBare.yes);
565         scope(exit) rmdirRecurse(_userRepo);
566 
567         string msgPath = buildPath(repo.path, "MERGE_MSG");
568 
569         string msg = "merge this";
570         std.file.write(msgPath, msg);
571         assert(repo.mergeMsg == msg);
572 
573         msg = "";
574         std.file.write(msgPath, msg);
575         assert(repo.mergeMsg !is null && repo.mergeMsg == msg);
576     }
577 
578     /**
579         Remove the merge message file for this repository.
580         If the message file does not exist $(D GitException) is thrown.
581         Use $(D mergeMsgExists) to check whether the merge message
582         file exists.
583     */
584     void removeMergeMsg()
585     {
586         require(git_repository_message_remove(_data._payload) == 0);
587     }
588 
589     ///
590     unittest
591     {
592         // write a merge message file and verify it can be read
593         auto repo = initRepo(_userRepo, OpenBare.yes);
594         scope(exit) rmdirRecurse(_userRepo);
595 
596         string msgPath = buildPath(repo.path, "MERGE_MSG");
597         string msg = "merge this";
598         std.file.write(msgPath, msg);
599 
600         assert(repo.mergeMsg == msg);
601 
602         // verify removal of merge message
603         repo.removeMergeMsg();
604         assert(repo.mergeMsg is null);
605 
606         // verify throwing when removing file which doesn't exist
607         assertThrown!GitException(repo.removeMergeMsg());
608     }
609 
610     /**
611         Call the $(D callback) function for each entry in the $(B FETCH_HEAD) file in this repository.
612 
613         The $(D callback) type must be either $(D FetchHeadFunction) or $(D FetchHeadDelegate).
614 
615         This function will return when either all entries are exhausted or when the $(D callback)
616         returns $(D ContinueWalk.no).
617     */
618     void walkFetchHead(FetchHeadFunction callback)
619     {
620         walkFetchHeadImpl(callback);
621     }
622 
623     /// ditto
624     void walkFetchHead(scope FetchHeadDelegate callback)
625     {
626         walkFetchHeadImpl(callback);
627     }
628 
629     /// Walk the $(B FETCH_HEAD) file with a function.
630     unittest
631     {
632         auto repo = initRepo(_userRepo, OpenBare.yes);
633         scope(exit) rmdirRecurse(_userRepo);
634 
635         string[] fetchHeadItems = [
636             "23c3c6add8162693f85b3b41c9bf6550a71a57d3		branch 'master' of git://github.com/D-Programming-Language/dmd\n",
637             "aaf64112624abab1f6cc8f610223f6e12b525e09		branch 'master' of git://github.com/D-Programming-Language/dmd\n"
638         ];
639 
640         std.file.write(buildPath(repo.path, "FETCH_HEAD"), fetchHeadItems.join());
641 
642         static ContinueWalk walkFunc(in char[] refName, in char[] remoteURL, GitOid oid, bool isMerge)
643         {
644             static int count;
645             count++;
646 
647             assert(count != 2);  // we're stopping after the first iteration
648 
649             assert(refName == "refs/heads/master");
650             assert(remoteURL == "git://github.com/D-Programming-Language/dmd");
651             assert(oid == GitOid("23C3C6ADD8162693F85B3B41C9BF6550A71A57D3"));
652 
653             return ContinueWalk.no;
654         }
655 
656         repo.walkFetchHead(&walkFunc);
657     }
658 
659     /// Walk the $(B FETCH_HEAD) file with a delegate.
660     unittest
661     {
662         auto repo = initRepo(_userRepo, OpenBare.yes);
663         scope(exit) rmdirRecurse(_userRepo);
664 
665         string[] fetchHeadItems = [
666             "23c3c6add8162693f85b3b41c9bf6550a71a57d3		branch 'master' of git://github.com/D-Programming-Language/dmd\n",
667             "aaf64112624abab1f6cc8f610223f6e12b525e09		branch 'master' of git://github.com/D-Programming-Language/dmd\n"
668         ];
669 
670         std.file.write(buildPath(repo.path, "FETCH_HEAD"), fetchHeadItems.join());
671 
672         struct S
673         {
674             size_t count;
675 
676             // delegate walker
677             ContinueWalk walker(in char[] refName, in char[] remoteURL, GitOid oid, bool isMerge)
678             {
679                 string line = fetchHeadItems[count++];
680                 string commitHex = line.split[0];
681 
682                 assert(refName == "refs/heads/master");
683                 assert(remoteURL == "git://github.com/D-Programming-Language/dmd");
684                 assert(oid == GitOid(commitHex));
685 
686                 return ContinueWalk.yes;
687             }
688 
689             ~this()
690             {
691                 assert(count == 2);  // verify we've walked through all the items
692             }
693         }
694 
695         S s;
696         repo.walkFetchHead(&s.walker);
697     }
698 
699     /** Walk the $(B FETCH_HEAD) file in a foreach loop. */
700     auto walkFetchHead()
701     {
702         static struct S
703         {
704             GitRepo repo;
705 
706             int opApply(int delegate(in char[] refName, in char[] remoteURL, GitOid oid, bool isMerge) dg)
707             {
708                 repo.walkFetchHeadImpl(dg);
709                 return 1;
710             }
711         }
712 
713         return S(this);
714     }
715 
716     /// Walk the $(B FETCH_HEAD) using a foreach loop.
717     unittest
718     {
719         auto repo = initRepo(_userRepo, OpenBare.yes);
720         scope(exit) rmdirRecurse(_userRepo);
721 
722         string[] fetchHeadItems = [
723             "23c3c6add8162693f85b3b41c9bf6550a71a57d3		branch 'master' of git://github.com/D-Programming-Language/dmd\n",
724             "aaf64112624abab1f6cc8f610223f6e12b525e09		branch 'master' of git://github.com/D-Programming-Language/dmd\n"
725         ];
726 
727         std.file.write(buildPath(repo.path, "FETCH_HEAD"), fetchHeadItems.join());
728 
729         size_t count;
730         foreach (refName, remoteURL, oid, isMerge; repo.walkFetchHead)
731         {
732             string line = fetchHeadItems[count++];
733             string commitHex = line.split[0];
734 
735             assert(refName == "refs/heads/master");
736             assert(remoteURL == "git://github.com/D-Programming-Language/dmd");
737             assert(oid == GitOid(commitHex));
738         }
739 
740         // ensure we've iterated all itmes
741         assert(count == 2);
742 
743         count = 0;
744         foreach (refName, remoteURL, oid, isMerge; repo.walkFetchHead)
745         {
746             string line = fetchHeadItems[count++];
747             string commitHex = line.split[0];
748 
749             assert(refName == "refs/heads/master");
750             assert(remoteURL == "git://github.com/D-Programming-Language/dmd");
751             assert(oid == GitOid(commitHex));
752             break;
753         }
754 
755         // ensure 'break' works
756         assert(count == 1);
757     }
758 
759     private void walkFetchHeadImpl(Callback)(Callback callback)
760         if (is(Callback == FetchHeadFunction) || is(Callback == FetchHeadDelegate) || is(Callback == FetchHeadOpApply))
761     {
762         // return 1 to stop iteration
763         static extern(C) int c_callback(
764             const(char)* refName,
765             const(char)* remoteURL,
766             const(git_oid)* oid,
767             uint isMerge,
768             void *payload)
769         {
770             Callback callback = *cast(Callback*)payload;
771 
772             auto result = callback(toSlice(refName), toSlice(remoteURL), GitOid(*oid), isMerge == 1);
773 
774             static if (is(Callback == FetchHeadOpApply))
775                 return result;
776             else
777                 return result == ContinueWalk.no;
778         }
779 
780         auto result = git_repository_fetchhead_foreach(_data._payload, &c_callback, &callback);
781         require(result == GIT_EUSER || result == 0);
782     }
783 
784     /**
785         Return the list of items in the $(B FETCH_HEAD) file as
786         an array of $(D FetchHeadItem)'s.
787     */
788     FetchHeadItem[] getFetchHeadItems()
789     {
790         auto buffer = appender!(typeof(return));
791         getFetchHeadItems(buffer);
792         return buffer.data;
793     }
794 
795     ///
796     unittest
797     {
798         auto repo = initRepo(_userRepo, OpenBare.yes);
799         scope(exit) rmdirRecurse(_userRepo);
800 
801         string[] fetchHeadItems = [
802             "23c3c6add8162693f85b3b41c9bf6550a71a57d3		branch 'master' of git://github.com/D-Programming-Language/dmd\n",
803             "aaf64112624abab1f6cc8f610223f6e12b525e09		branch 'master' of git://github.com/D-Programming-Language/dmd\n"
804         ];
805 
806         std.file.write(buildPath(repo.path, "FETCH_HEAD"), fetchHeadItems.join());
807 
808         foreach (string line, FetchHeadItem item; lockstep(fetchHeadItems, repo.getFetchHeadItems()))
809         {
810             string commitHex = line.split[0];
811 
812             assert(item.refName == "refs/heads/master");
813             assert(item.remoteURL == "git://github.com/D-Programming-Language/dmd");
814             assert(item.oid == GitOid(commitHex));
815         }
816     }
817 
818     /**
819         Read each item in the $(B FETCH_HEAD) file to
820         the output range $(D sink), and return the $(D sink).
821     */
822     Range getFetchHeadItems(Range)(Range sink)
823         if (isOutputRange!(Range, FetchHeadItem))
824     {
825         alias Params = ParameterTypeTuple!FetchHeadFunction;
826         walkFetchHead( (Params params) { sink.put(FetchHeadItem(params)); return ContinueWalk.yes; } );
827         return sink;
828     }
829 
830     ///
831     unittest
832     {
833         auto repo = initRepo(_userRepo, OpenBare.yes);
834         scope(exit) rmdirRecurse(_userRepo);
835 
836         string[] fetchHeadItems = [
837             "23c3c6add8162693f85b3b41c9bf6550a71a57d3		branch 'master' of git://github.com/D-Programming-Language/dmd\n",
838             "aaf64112624abab1f6cc8f610223f6e12b525e09		branch 'master' of git://github.com/D-Programming-Language/dmd\n"
839         ];
840 
841         std.file.write(buildPath(repo.path, "FETCH_HEAD"), fetchHeadItems.join());
842 
843         auto buffer = repo.getFetchHeadItems(appender!(FetchHeadItem[]));
844 
845         foreach (string line, FetchHeadItem item; lockstep(fetchHeadItems, buffer.data))
846         {
847             string commitHex = line.split[0];
848 
849             assert(item.refName == "refs/heads/master");
850             assert(item.remoteURL == "git://github.com/D-Programming-Language/dmd");
851             assert(item.oid == GitOid(commitHex));
852         }
853     }
854 
855     /**
856         Call the $(D callback) function for each entry in the $(B MERGE_HEAD) file in this repository.
857 
858         The $(D callback) type must be either $(D MergeHeadFunction) or $(D MergeHeadDelegate).
859 
860         This function will return when either all entries are exhausted or when the $(D callback)
861         returns $(D ContinueWalk.no).
862     */
863     void walkMergeHead(MergeHeadFunction callback)
864     {
865         walkMergeHeadImpl(callback);
866     }
867 
868     /// ditto
869     void walkMergeHead(scope MergeHeadDelegate callback)
870     {
871         walkMergeHeadImpl(callback);
872     }
873 
874     /// Walk the $(B MERGE_HEAD) file with a function.
875     unittest
876     {
877         auto repo = initRepo(_userRepo, OpenBare.yes);
878         scope(exit) rmdirRecurse(_userRepo);
879 
880         string[] mergeHeadItems = [
881             "e496660174425e3147a0593ced2954f3ddbf65ca\n",
882             "e496660174425e3147a0593ced2954f3ddbf65ca\n"
883         ];
884 
885         std.file.write(buildPath(repo.path, "MERGE_HEAD"), mergeHeadItems.join());
886 
887         static ContinueWalk walkFunc(GitOid oid)
888         {
889             static int count;
890             count++;
891 
892             assert(count != 2);  // we're stopping after the first iteration
893 
894             assert(oid == GitOid("e496660174425e3147a0593ced2954f3ddbf65ca"));
895 
896             return ContinueWalk.no;
897         }
898 
899         repo.walkMergeHead(&walkFunc);
900     }
901 
902     /// Walk the $(B MERGE_HEAD) file with a delegate.
903     unittest
904     {
905         auto repo = initRepo(_userRepo, OpenBare.yes);
906         scope(exit) rmdirRecurse(_userRepo);
907 
908         string[] mergeHeadItems = [
909             "e496660174425e3147a0593ced2954f3ddbf65ca\n",
910             "e496660174425e3147a0593ced2954f3ddbf65ca\n"
911         ];
912 
913         std.file.write(buildPath(repo.path, "MERGE_HEAD"), mergeHeadItems.join());
914 
915         struct S
916         {
917             size_t count;
918 
919             // delegate walker
920             ContinueWalk walker(GitOid oid)
921             {
922                 string line = mergeHeadItems[count++];
923                 string commitHex = line.split[0];
924                 assert(oid == GitOid(commitHex));
925 
926                 return ContinueWalk.yes;
927             }
928 
929             ~this()
930             {
931                 assert(count == 2);  // verify we've walked through all the items
932             }
933         }
934 
935         S s;
936         repo.walkMergeHead(&s.walker);
937     }
938 
939     /** Walk the $(B MERGE_HEAD) file in a foreach loop. */
940     auto walkMergeHead()
941     {
942         static struct S
943         {
944             GitRepo repo;
945 
946             int opApply(int delegate(GitOid oid) dg)
947             {
948                 repo.walkMergeHeadImpl(dg);
949                 return 1;
950             }
951         }
952 
953         return S(this);
954     }
955 
956     /// Walk the $(B MERGE_HEAD) using a foreach loop.
957     unittest
958     {
959         auto repo = initRepo(_userRepo, OpenBare.yes);
960         scope(exit) rmdirRecurse(_userRepo);
961 
962         string[] mergeHeadItems = [
963             "e496660174425e3147a0593ced2954f3ddbf65ca\n",
964             "e496660174425e3147a0593ced2954f3ddbf65ca\n"
965         ];
966 
967         std.file.write(buildPath(repo.path, "MERGE_HEAD"), mergeHeadItems.join());
968 
969         size_t count;
970         foreach (oid; repo.walkMergeHead)
971         {
972             string line = mergeHeadItems[count++];
973             string commitHex = line.split[0];
974             assert(oid == GitOid(commitHex));
975         }
976 
977         // ensure we've iterated all itmes
978         assert(count == 2);
979 
980         count = 0;
981         foreach (oid; repo.walkMergeHead)
982         {
983             string line = mergeHeadItems[count++];
984             string commitHex = line.split[0];
985             assert(oid == GitOid(commitHex));
986             break;
987         }
988 
989         // ensure 'break' works
990         assert(count == 1);
991     }
992 
993     private void walkMergeHeadImpl(Callback)(Callback callback)
994         if (is(Callback == MergeHeadFunction) || is(Callback == MergeHeadDelegate) || is(Callback == MergeHeadOpApply))
995     {
996         static extern(C) int c_callback(const(git_oid)* oid, void* payload)
997         {
998             Callback callback = *cast(Callback*)payload;
999 
1000             // return < 0 to stop iteration. Bug in v0.19.0
1001             // 0.20.0 and later requires just != 0, so -1 is fine there, too
1002             // https://github.com/libgit2/libgit2/issues/1703
1003             static if (is(Callback == MergeHeadOpApply))
1004                 return callback(GitOid(*oid)) ? -1 : 0;
1005             else
1006                 return callback(GitOid(*oid)) == ContinueWalk.no ? -1 : 0;
1007         }
1008 
1009         auto result = git_repository_mergehead_foreach(_data._payload, &c_callback, &callback);
1010         require(result == GIT_EUSER || result == 0);
1011     }
1012 
1013     /**
1014         Return the list of items in the $(B MERGE_HEAD) file as
1015         an array of $(D MergeHeadItem)'s.
1016     */
1017     MergeHeadItem[] getMergeHeadItems()
1018     {
1019         auto buffer = appender!(typeof(return));
1020         getMergeHeadItems(buffer);
1021         return buffer.data;
1022     }
1023 
1024     ///
1025     unittest
1026     {
1027         auto repo = initRepo(_userRepo, OpenBare.yes);
1028         scope(exit) rmdirRecurse(_userRepo);
1029 
1030         string[] mergeHeadItems = [
1031             "e496660174425e3147a0593ced2954f3ddbf65ca\n",
1032             "e496660174425e3147a0593ced2954f3ddbf65ca\n"
1033         ];
1034 
1035         std.file.write(buildPath(repo.path, "MERGE_HEAD"), mergeHeadItems.join());
1036 
1037         foreach (string line, MergeHeadItem item; lockstep(mergeHeadItems, repo.getMergeHeadItems()))
1038         {
1039             string commitHex = line.split[0];
1040             assert(item.oid == GitOid(commitHex));
1041         }
1042     }
1043 
1044     /**
1045         Read each item in the $(B MERGE_HEAD) file to
1046         the output range $(D sink), and return the $(D sink).
1047     */
1048     Range getMergeHeadItems(Range)(Range sink)
1049         if (isOutputRange!(Range, MergeHeadItem))
1050     {
1051         alias Params = ParameterTypeTuple!MergeHeadFunction;
1052         walkMergeHead( (Params params) { sink.put(MergeHeadItem(params)); return ContinueWalk.yes; } );
1053         return sink;
1054     }
1055 
1056     ///
1057     unittest
1058     {
1059         auto repo = initRepo(_userRepo, OpenBare.yes);
1060         scope(exit) rmdirRecurse(_userRepo);
1061 
1062         string[] mergeHeadItems = [
1063             "e496660174425e3147a0593ced2954f3ddbf65ca\n",
1064             "e496660174425e3147a0593ced2954f3ddbf65ca\n"
1065         ];
1066 
1067         std.file.write(buildPath(repo.path, "MERGE_HEAD"), mergeHeadItems.join());
1068 
1069         auto buffer = repo.getMergeHeadItems(appender!(MergeHeadItem[]));
1070 
1071         foreach (string line, MergeHeadItem item; lockstep(mergeHeadItems, buffer.data))
1072         {
1073             string commitHex = line.split[0];
1074             assert(item.oid == GitOid(commitHex));
1075         }
1076     }
1077 
1078     /**
1079         Return the current state this repository,
1080         e.g. whether an operation such as merge is in progress.
1081     */
1082     @property RepoState state()
1083     {
1084         return to!RepoState(git_repository_state(_data._payload));
1085     }
1086 
1087     ///
1088     unittest
1089     {
1090         // todo: test all states
1091         auto repo = initRepo(_userRepo, OpenBare.yes);
1092         scope(exit) rmdirRecurse(_userRepo);
1093         assert(repo.state == RepoState.none);
1094     }
1095 
1096     /** Get the currently active namespace for this repository. */
1097     @property string namespace()
1098     {
1099         return to!string(git_repository_get_namespace(_data._payload));
1100     }
1101 
1102     ///
1103     unittest
1104     {
1105         auto repo = initRepo(_userRepo, OpenBare.yes);
1106         scope(exit) rmdirRecurse(_userRepo);
1107         assert(repo.namespace is null);
1108     }
1109 
1110     /**
1111         Set the active namespace for this repository.
1112 
1113         This namespace affects all reference operations for the repo.
1114         See $(B man gitnamespaces).
1115 
1116         The namespace should not include the refs folder,
1117         e.g. to namespace all references under $(B refs/namespaces/foo/)
1118         use $(B foo) as the namespace.
1119     */
1120     @property void namespace(in char[] nspace)
1121     {
1122         require(git_repository_set_namespace(_data._payload, nspace.gitStr) == 0);
1123     }
1124 
1125     ///
1126     unittest
1127     {
1128         auto repo = initRepo(_userRepo, OpenBare.yes);
1129         scope(exit) rmdirRecurse(_userRepo);
1130         repo.namespace = "foobar";
1131         assert(repo.namespace == "foobar");
1132     }
1133 
1134     /** Determine if this repository is a shallow clone. */
1135     @property bool isShallow()
1136     {
1137         return git_repository_is_shallow(_data._payload) == 1;
1138     }
1139 
1140     ///
1141     unittest
1142     {
1143         auto repo = initRepo(_userRepo, OpenBare.yes);
1144         scope(exit) rmdirRecurse(_userRepo);
1145         assert(!repo.isShallow);
1146     }
1147 
1148     /**
1149         Remove all the metadata associated with an ongoing git merge,
1150         including MERGE_HEAD, MERGE_MSG, etc.
1151     */
1152     void cleanupMerge()
1153     {
1154         require(git_repository_merge_cleanup(_data._payload) == 0);
1155     }
1156 
1157     ///
1158     unittest
1159     {
1160         // write a merge message file
1161         auto repo = initRepo(_userRepo, OpenBare.yes);
1162         scope(exit) rmdirRecurse(_userRepo);
1163 
1164         string msgPath = buildPath(repo.path, "MERGE_MSG");
1165         string msg = "merge this";
1166         std.file.write(msgPath, msg);
1167 
1168         assert(repo.mergeMsg == msg);
1169 
1170         // verify removal of merge message
1171         repo.cleanupMerge();
1172         assert(repo.mergeMsg is null);
1173 
1174         // verify throwing when removing file which doesn't exist
1175         assertThrown!GitException(repo.removeMergeMsg());
1176     }
1177 
1178     /**
1179         Calculate hash of file using repository filtering rules.
1180 
1181         If you simply want to calculate the hash of a file on disk with no filters,
1182         you can use the global $(D hashFile) function. However, if you want to
1183         hash a file in the repository and you want to apply filtering rules (e.g.
1184         $(B crlf) filters) before generating the SHA, then use this function.
1185 
1186         Parameters:
1187 
1188         $(D path): Path to file on disk whose contents should be hashed.
1189                    This can be a relative path.
1190 
1191         $(D type): The object type to hash the file as (e.g. $(D GitType.blob))
1192 
1193         $(D asPath): The path to use to look up filtering rules.
1194                      If this is $(D null), then the $(D path) parameter will be
1195                      used instead. If this is passed as the empty string, then no
1196                      filters will be applied when calculating the hash.
1197     */
1198     GitOid hashFile(in char[] path, GitType type, in char[] asPath = null)
1199     {
1200         git_oid _git_oid;
1201 
1202         require(git_repository_hashfile(&_git_oid, _data._payload, path.gitStr, cast(git_otype)type, asPath.gitStr) == 0);
1203 
1204         return GitOid(_git_oid);
1205     }
1206 
1207     ///
1208     unittest
1209     {
1210         auto repo = initRepo(_userRepo, OpenBare.yes);
1211         scope(exit) rmdirRecurse(_userRepo);
1212 
1213         string objPath = buildPath(repo.path, "test.d");
1214         string text = "import std.stdio;";
1215         std.file.write(objPath, text);
1216 
1217         auto oid = repo.hashFile(objPath, GitType.blob);
1218     }
1219 
1220     /**
1221         Make the repository HEAD point to the specified reference.
1222 
1223         If the provided reference points to a Tree or a Blob, the HEAD is
1224         unaltered and -1 is returned.
1225 
1226         If the provided reference points to a branch, the HEAD will point
1227         to that branch, staying attached, or become attached if it isn't yet.
1228         If the branch doesn't exist yet, no error will be return. The HEAD
1229         will then be attached to an unborn branch.
1230 
1231         Otherwise, the HEAD will be detached and will directly point to
1232         the Commit.
1233 
1234         @param repo Repository pointer
1235         @param refname Canonical name of the reference the HEAD should point at
1236         @return 0 on success, or an error code
1237     */
1238     // todo: add overload that takes an actual reference, and then call .toname
1239     version(none)  // todo: implement when commit, blob, and refs APIs are in place
1240     void setHead(in char[] refName)
1241     {
1242         require(git_repository_set_head(_data._payload, refName.gitStr) == 0);
1243     }
1244 
1245     ///
1246     version(none)  // todo: implement when commit, blob, and refs APIs are in place
1247     unittest
1248     {
1249         auto repo = initRepo(_userRepo, OpenBare.no);
1250 
1251         scope(exit)
1252         {
1253             // workaround for Issue 10529:
1254             // http://d.puremagic.com/issues/show_bug.cgi?id=10529
1255             version(Windows)
1256                 executeShell(format("rmdir /q /s %s", _userRepo.absolutePath.buildNormalizedPath));
1257             else
1258                 rmdirRecurse(_userRepo);
1259         }
1260 
1261         // create a blob file in the work path
1262         string blobPath = buildPath(repo.workPath, "foo.text");
1263         std.file.write(blobPath, "blob");
1264 
1265         import deimos.git2.blob;
1266         git_oid _oid;
1267         require(0 == git_blob_create_fromworkdir(&_oid, repo._data._payload, "/foo.text"));
1268 
1269         import deimos.git2.refs;
1270         git_reference* ptr;
1271         require(0 == git_reference_create(&ptr, repo._data._payload, "MY_REF", &_oid, false));
1272 
1273         repo.setHead("MY_REF");
1274     }
1275 
1276     /**
1277      * Sets the repository's HEAD to the given commit object id.
1278      *
1279      * When successful, the HEAD will be in a detached state.
1280      *
1281      * If the provided oid is not found or cannot be resolved to a commit,
1282      * the repository is left unaltered and an exception is thrown.
1283      */
1284     void setHeadDetached(const(GitOid) oid) {
1285         require(git_repository_set_head_detached(_data._payload, &oid._get_oid()) == 0);
1286     }
1287 
1288     /**
1289      * Detach the HEAD.
1290      *
1291      * If the HEAD is already detached and points to a Commit, 0 is returned.
1292      *
1293      * If the HEAD is already detached and points to a Tag, the HEAD is
1294      * updated into making it point to the peeled Commit, and 0 is returned.
1295      *
1296      * If the HEAD is already detached and points to a non-commit oid, the HEAD is
1297      * unaltered, and -1 is returned.
1298      *
1299      * Otherwise, the HEAD will be detached and point to the peeled Commit.
1300      *
1301      * @param repo Repository pointer
1302      * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
1303      * branch or an error code
1304      */
1305     //~ int git_repository_detach_head(
1306             //~ git_repository* repo);
1307 
1308     /**
1309         Add one or more ignore rules to this repository.
1310 
1311         Excludesfile rules (i.e. .gitignore rules) are generally read from
1312         .gitignore files in the repository tree, or from a shared system file
1313         only if a $(B core.excludesfile) config value is set.  The library also
1314         keeps a set of per-repository internal ignores that can be configured
1315         in-memory and will not persist. This function allows you to add to
1316         that internal rules list.
1317     */
1318     void addIgnoreRules(scope const(char)[][] rules...)
1319     {
1320         require(git_ignore_add_rule(_data._payload, rules.join("\n").gitStr) == 0);
1321     }
1322 
1323     ///
1324     unittest
1325     {
1326         auto repo = initRepo(_userRepo, OpenBare.no);
1327         repo.addIgnoreRules("/foo");
1328         repo.addIgnoreRules(["/foo", "/bar"]);
1329     }
1330 
1331     /**
1332         Clear ignore rules that were explicitly added.
1333 
1334         Resets to the default internal ignore rules.  This will not turn off
1335         rules in .gitignore files that actually exist in the filesystem.
1336 
1337         The default internal ignores ignore ".", ".." and ".git" entries.
1338     */
1339     void clearIgnoreRules()
1340     {
1341         require(git_ignore_clear_internal_rules(_data._payload) == 0);
1342     }
1343 
1344     ///
1345     unittest
1346     {
1347         auto repo = initRepo(_userRepo, OpenBare.no);
1348         repo.addIgnoreRules("/foo");
1349         repo.addIgnoreRules(["/foo", "/bar"]);
1350         repo.clearIgnoreRules();
1351     }
1352 
1353     /**
1354         Test if the ignore rules apply to a given path.
1355 
1356         This function checks the ignore rules to see if they would apply to the
1357         given file.  This indicates if the file would be ignored regardless of
1358         whether the file is already in the index or committed to the repository.
1359 
1360         One way to think of this is if you were to do "git add ." on the
1361         directory containing the file, would it be added or not?
1362     */
1363     bool isPathIgnored(in char[] path)
1364     {
1365         int ignored;
1366         require(git_ignore_path_is_ignored(&ignored, _data._payload, path.gitStr) == 0);
1367         return ignored == 1;
1368     }
1369 
1370     ///
1371     unittest
1372     {
1373         auto repo = initRepo(_userRepo, OpenBare.no);
1374         assert(!repo.isPathIgnored("/foo"));
1375 
1376         repo.addIgnoreRules("/foo");
1377         assert(repo.isPathIgnored("/foo"));
1378 
1379         repo.addIgnoreRules(["/foo", "/bar"]);
1380         assert(repo.isPathIgnored("/bar"));
1381 
1382         repo.clearIgnoreRules();
1383         assert(!repo.isPathIgnored("/foo"));
1384         assert(!repo.isPathIgnored("/bar"));
1385     }
1386 
1387     mixin RefCountedGitObject!(git_repository, git_repository_free);
1388 }
1389 
1390 
1391 enum GitRepositoryOpenFlags {
1392 	none = 0,
1393     noSearch = GIT_REPOSITORY_OPEN_NO_SEARCH,
1394     crossFS = GIT_REPOSITORY_OPEN_CROSS_FS,
1395     //bare = GIT_REPOSITORY_OPEN_BARE // available in 0.20.0
1396 }
1397 
1398 enum GitRepositoryInitMode {
1399     sharedUmask = GIT_REPOSITORY_INIT_SHARED_UMASK,
1400     sharedGroup = GIT_REPOSITORY_INIT_SHARED_GROUP,
1401     sharedAll = GIT_REPOSITORY_INIT_SHARED_ALL,
1402 }
1403 
1404 enum GitRepositoryInitFlags {
1405     none = 0,
1406     bare = GIT_REPOSITORY_INIT_BARE,
1407     reinit = GIT_REPOSITORY_INIT_NO_REINIT,
1408     noDotGitDir = GIT_REPOSITORY_INIT_NO_DOTGIT_DIR,
1409     makeDir = GIT_REPOSITORY_INIT_MKDIR,
1410     makePath = GIT_REPOSITORY_INIT_MKPATH,
1411     externalTemplate = GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE
1412 }
1413 
1414 struct GitRepositoryInitOptions {
1415     GitRepositoryInitFlags flags;
1416     GitRepositoryInitMode mode;
1417     string workdirPath;
1418     string description;
1419     string templatePath;
1420     string initialHead;
1421     string originURL;
1422 }
1423 
1424 
1425 /// Used to specify whether to continue search on a file system change.
1426 enum UpdateGitlink
1427 {
1428     /// Stop searching on file system change.
1429     no,
1430 
1431     /// Continue searching on file system change.
1432     yes
1433 }
1434 
1435 /** A single item in the list of the $(B FETCH_HEAD) file. */
1436 struct FetchHeadItem
1437 {
1438     ///
1439     const(char)[] refName;
1440 
1441     ///
1442     const(char)[] remoteURL;
1443 
1444     ///
1445     GitOid oid;
1446 
1447     ///
1448     bool isMerge;
1449 }
1450 
1451 /// The function or delegate type that $(D walkFetchHead) can take as the callback.
1452 alias FetchHeadFunction = ContinueWalk function(in char[] refName, in char[] remoteURL, GitOid oid, bool isMerge);
1453 
1454 /// ditto
1455 alias FetchHeadDelegate = ContinueWalk delegate(in char[] refName, in char[] remoteURL, GitOid oid, bool isMerge);
1456 
1457 /// ditto
1458 alias FetchHeadOpApply = int delegate(in char[] refName, in char[] remoteURL, GitOid oid, bool isMerge);
1459 
1460 /** A single item in the list of the $(B MERGE_HEAD) file. */
1461 struct MergeHeadItem
1462 {
1463     GitOid oid;
1464 }
1465 
1466 /// The function or delegate type that $(D walkMergeHead) can take as the callback.
1467 alias MergeHeadFunction = ContinueWalk function(GitOid oid);
1468 
1469 /// ditto
1470 alias MergeHeadDelegate = ContinueWalk delegate(GitOid oid);
1471 
1472 /// ditto
1473 alias MergeHeadOpApply = int delegate(GitOid oid);
1474 
1475 /// The various states a repository can be in.
1476 enum RepoState
1477 {
1478     none, ///
1479     merge, ///
1480     revert, ///
1481     cherry_pick, ///
1482     bisect, ///
1483     rebase, ///
1484     rebase_interactive, ///
1485     rebase_merge, ///
1486     apply_mailbox, ///
1487     apply_mailbox_or_rebase, ///
1488 }
1489 
1490 
1491 /// Used to specify whether to continue search on a file system change.
1492 enum AcrossFS
1493 {
1494     /// Stop searching on file system change.
1495     no,
1496 
1497     /// Continue searching on file system change.
1498     yes
1499 }
1500 
1501 
1502 /// Used to specify whether to open a bare repository
1503 enum OpenBare
1504 {
1505     /// Open a non-bare repository
1506     no,
1507 
1508     /// Open a bare repository
1509     yes
1510 }
1511 
1512 
1513 version(none):
1514 
1515 /**
1516     TODO: Functions to wrap later:
1517 */
1518 
1519 // todo: wrap git_odb before wrapping this function
1520 int git_repository_wrap_odb(git_repository **out_, git_odb *odb);
1521 
1522 // todo: when git_config is ported
1523 int git_repository_config(git_config **out_, git_repository *repo);
1524 
1525 // todo: when git_odb is ported
1526 int git_repository_odb(git_odb **out_, git_repository *repo);
1527 
1528 // todo: when git_refdb is ported
1529 int git_repository_refdb(git_refdb **out_, git_repository *repo);
1530