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