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