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