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