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);