diff --git a/src/GitTools.Core.Tests/Git/GitRepositoryFactoryTests.cs b/src/GitTools.Core.Tests/Git/DynamicRepositoriesTests.cs similarity index 58% rename from src/GitTools.Core.Tests/Git/GitRepositoryFactoryTests.cs rename to src/GitTools.Core.Tests/Git/DynamicRepositoriesTests.cs index 7f329eb..251dd97 100644 --- a/src/GitTools.Core.Tests/Git/GitRepositoryFactoryTests.cs +++ b/src/GitTools.Core.Tests/Git/DynamicRepositoriesTests.cs @@ -11,7 +11,7 @@ using Testing; [TestFixture] - public class GitRepositoryFactoryTests + public class DynamicRepositoriesTests { const string DefaultBranchName = "master"; const string SpecificBranchName = "feature/foo"; @@ -37,25 +37,22 @@ public void WorksCorrectlyWithRemoteRepository(string branchName, string expecte fixture.Repository.MakeCommits(5); fixture.Repository.CreateFileAndCommit("TestFile.txt"); - fixture.Repository.CreateBranch(SpecificBranchName); + var branch = fixture.Repository.CreateBranch(SpecificBranchName); // Copy contents into working directory File.Copy(Path.Combine(fixture.RepositoryPath, "TestFile.txt"), Path.Combine(tempDir, "TestFile.txt")); var repositoryInfo = new RepositoryInfo { - Url = fixture.RepositoryPath, - Branch = branchName + Url = fixture.RepositoryPath }; - using (var gitRepository = GitRepositoryFactory.CreateRepository(repositoryInfo)) + using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, branchName, branch.Tip.Sha)) { - dynamicRepositoryPath = gitRepository.DotGitDirectory; + dynamicRepositoryPath = dynamicRepository.Repository.Info.Path; + dynamicRepository.Repository.Info.Path.ShouldBe(Path.Combine(expectedDynamicRepoLocation, ".git\\")); - gitRepository.IsDynamic.ShouldBe(true); - gitRepository.DotGitDirectory.ShouldBe(Path.Combine(expectedDynamicRepoLocation, ".git")); - - var currentBranch = gitRepository.Repository.Head.CanonicalName; + var currentBranch = dynamicRepository.Repository.Head.CanonicalName; currentBranch.ShouldEndWith(expectedBranchName); } @@ -85,26 +82,24 @@ public void UpdatesExistingDynamicRepository() { using (var mainRepositoryFixture = new EmptyRepositoryFixture()) { - mainRepositoryFixture.Repository.MakeCommits(1); + var commit = mainRepositoryFixture.Repository.MakeACommit(); var repositoryInfo = new RepositoryInfo { - Url = mainRepositoryFixture.RepositoryPath, - Branch = "master" + Url = mainRepositoryFixture.RepositoryPath }; - using (var gitRepository = GitRepositoryFactory.CreateRepository(repositoryInfo)) + using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", commit.Sha)) { - dynamicRepositoryPath = gitRepository.DotGitDirectory; + dynamicRepositoryPath = dynamicRepository.Repository.Info.Path; } var newCommit = mainRepositoryFixture.Repository.MakeACommit(); - using (var gitRepository = GitRepositoryFactory.CreateRepository(repositoryInfo)) + using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", newCommit.Sha)) { - mainRepositoryFixture.Repository.DumpGraph(); - gitRepository.Repository.DumpGraph(); - gitRepository.Repository.Commits.ShouldContain(c => c.Sha == newCommit.Sha); + dynamicRepository.Repository.Info.Path.ShouldBe(dynamicRepositoryPath); + dynamicRepository.Repository.Commits.ShouldContain(c => c.Sha == newCommit.Sha); } } } @@ -133,21 +128,19 @@ public void PicksAnotherDirectoryNameWhenDynamicRepoFolderTaken() { using (var fixture = new EmptyRepositoryFixture()) { - fixture.Repository.CreateFileAndCommit("TestFile.txt"); + var head = fixture.Repository.CreateFileAndCommit("TestFile.txt"); File.Copy(Path.Combine(fixture.RepositoryPath, "TestFile.txt"), Path.Combine(tempDir, "TestFile.txt")); expectedDynamicRepoLocation = Path.Combine(tempPath, fixture.RepositoryPath.Split(Path.DirectorySeparatorChar).Last()); Directory.CreateDirectory(expectedDynamicRepoLocation); var repositoryInfo = new RepositoryInfo { - Url = fixture.RepositoryPath, - Branch = "master" + Url = fixture.RepositoryPath }; - using (var gitRepository = GitRepositoryFactory.CreateRepository(repositoryInfo)) + using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", head.Sha)) { - gitRepository.IsDynamic.ShouldBe(true); - gitRepository.DotGitDirectory.ShouldBe(Path.Combine(expectedDynamicRepoLocation + "_1", ".git")); + dynamicRepository.Repository.Info.Path.ShouldBe(Path.Combine(expectedDynamicRepoLocation + "_1", ".git\\")); } } } @@ -166,6 +159,47 @@ public void PicksAnotherDirectoryNameWhenDynamicRepoFolderTaken() } } + [Test] + [Category("NoMono")] + public void PicksAnotherDirectoryNameWhenDynamicRepoFolderIsInUse() + { + var tempPath = Path.GetTempPath(); + var expectedDynamicRepoLocation = default(string); + var expectedDynamicRepo2Location = default(string); + + try + { + using (var fixture = new EmptyRepositoryFixture()) + { + var head = fixture.Repository.CreateFileAndCommit("TestFile.txt"); + var repositoryInfo = new RepositoryInfo + { + Url = fixture.RepositoryPath + }; + + using (var dynamicRepository = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", head.Sha)) + using (var dynamicRepository2 = DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", head.Sha)) + { + expectedDynamicRepoLocation = dynamicRepository.Repository.Info.Path; + expectedDynamicRepo2Location = dynamicRepository2.Repository.Info.Path; + dynamicRepository.Repository.Info.Path.ShouldNotBe(dynamicRepository2.Repository.Info.Path); + } + } + } + finally + { + if (expectedDynamicRepoLocation != null) + { + DeleteHelper.DeleteDirectory(expectedDynamicRepoLocation, true); + } + + if (expectedDynamicRepo2Location != null) + { + DeleteHelper.DeleteGitRepository(expectedDynamicRepo2Location); + } + } + } + [Test] public void ThrowsExceptionWhenNotEnoughInfo() { @@ -173,11 +207,10 @@ public void ThrowsExceptionWhenNotEnoughInfo() var repositoryInfo = new RepositoryInfo { - Url = tempDir, - Branch = "master" + Url = tempDir }; - Should.Throw(() => GitRepositoryFactory.CreateRepository(repositoryInfo)); + Should.Throw(() => DynamicRepositories.CreateOrOpen(repositoryInfo, tempDir, null, null)); } [Test] @@ -192,21 +225,19 @@ public void UsingDynamicRepositoryWithFeatureBranchWorks() { using (var mainRepositoryFixture = new EmptyRepositoryFixture()) { - mainRepositoryFixture.Repository.MakeACommit(); + var commit = mainRepositoryFixture.Repository.MakeACommit(); var repositoryInfo = new RepositoryInfo { - Url = mainRepositoryFixture.RepositoryPath, - Branch = "feature1" + Url = mainRepositoryFixture.RepositoryPath }; mainRepositoryFixture.Repository.Checkout(mainRepositoryFixture.Repository.CreateBranch("feature1")); Should.NotThrow(() => { - using (var gitRepository = GitRepositoryFactory.CreateRepository(repositoryInfo)) + using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "feature1", commit.Sha)) { - // this code shouldn't throw } }); } @@ -220,35 +251,46 @@ public void UsingDynamicRepositoryWithFeatureBranchWorks() [Test] public void UsingDynamicRepositoryWithoutTargetBranchFails() { - var repoName = Guid.NewGuid().ToString(); var tempPath = Path.GetTempPath(); - var tempDir = Path.Combine(tempPath, repoName); - Directory.CreateDirectory(tempDir); - try + using (var mainRepositoryFixture = new EmptyRepositoryFixture()) { - using (var mainRepositoryFixture = new EmptyRepositoryFixture()) - { - mainRepositoryFixture.Repository.MakeACommit(); + mainRepositoryFixture.Repository.MakeACommit(); - var repositoryInfo = new RepositoryInfo - { - Url = mainRepositoryFixture.RepositoryPath, - Branch = null - }; + var repositoryInfo = new RepositoryInfo + { + Url = mainRepositoryFixture.RepositoryPath + }; - Should.Throw(() => + Should.Throw(() => + { + using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, null, null)) { - using (var gitRepository = GitRepositoryFactory.CreateRepository(repositoryInfo)) - { - // this code shouldn't throw - } - }); - } + } + }); } - finally + } + + [Test] + public void UsingDynamicRepositoryWithoutTargetBranchCommitFails() + { + var tempPath = Path.GetTempPath(); + + using (var mainRepositoryFixture = new EmptyRepositoryFixture()) { - Directory.Delete(tempDir, true); + mainRepositoryFixture.Repository.MakeACommit(); + + var repositoryInfo = new RepositoryInfo + { + Url = mainRepositoryFixture.RepositoryPath + }; + + Should.Throw(() => + { + using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", null)) + { + } + }); } } @@ -264,15 +306,13 @@ public void TestErrorThrownForInvalidRepository() { var repositoryInfo = new RepositoryInfo { - Url = "http://127.0.0.1/testrepo.git", - Branch = "master" + Url = "http://127.0.0.1/testrepo.git" }; Should.Throw(() => { - using (var gitRepository = GitRepositoryFactory.CreateRepository(repositoryInfo)) + using (DynamicRepositories.CreateOrOpen(repositoryInfo, tempPath, "master", "sha")) { - // this code shouldn't throw } }); } diff --git a/src/GitTools.Core.Tests/Git/GitRepositoryHelperTests.cs b/src/GitTools.Core.Tests/Git/GitRepositoryHelperTests.cs index 4aa9239..53ae411 100644 --- a/src/GitTools.Core.Tests/Git/GitRepositoryHelperTests.cs +++ b/src/GitTools.Core.Tests/Git/GitRepositoryHelperTests.cs @@ -23,7 +23,7 @@ public void NormalisationOfPullRequestsWithFetch() localFixture.Checkout(commit.Sha); GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: string.Empty); - var normalisedPullBranch = localFixture.Repository.FindBranch("pull/3/merge"); + var normalisedPullBranch = localFixture.Repository.Branches["pull/3/merge"]; normalisedPullBranch.ShouldNotBe(null); } } @@ -51,7 +51,7 @@ public void NormalisationOfPullRequestsWithoutFetch() } [Test] - public void UpdatesLocalBranchesWhen() + public void NormalisationOfTag() { using (var fixture = new EmptyRepositoryFixture()) { @@ -59,16 +59,22 @@ public void UpdatesLocalBranchesWhen() fixture.Repository.Checkout(fixture.Repository.CreateBranch("feature/foo")); fixture.Repository.MakeACommit(); + + fixture.BranchTo("release/2.0.0"); + fixture.MakeACommit(); + fixture.MakeATaggedCommit("2.0.0-rc.1"); + fixture.Checkout("master"); + fixture.MergeNoFF("release/2.0.0"); + fixture.Repository.Branches.Remove(fixture.Repository.Branches["release/2.0.0"]); + var remoteTagSha = fixture.Repository.Tags["2.0.0-rc.1"].Target.Sha; + using (var localFixture = fixture.CloneRepository()) { - localFixture.Checkout("feature/foo"); - // Advance remote - var advancedCommit = fixture.Repository.MakeACommit(); - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: null); + localFixture.Checkout(remoteTagSha); + GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: string.Empty); - var normalisedBranch = localFixture.Repository.FindBranch("feature/foo"); - normalisedBranch.ShouldNotBe(null); - normalisedBranch.Tip.Sha.ShouldBe(advancedCommit.Sha); + localFixture.Repository.Head.FriendlyName.ShouldBe("(no branch)"); + localFixture.Repository.Head.Tip.Sha.ShouldBe(remoteTagSha); } } } @@ -89,13 +95,10 @@ public void UpdatesCurrentBranch() var advancedCommit = fixture.Repository.MakeACommit(); Commands.Fetch((Repository)localFixture.Repository, localFixture.Repository.Network.Remotes["origin"].Name, new string[0], null, null); localFixture.Repository.Checkout(advancedCommit.Sha); - localFixture.Repository.DumpGraph(); - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: "ref/heads/develop"); + GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: "refs/heads/develop"); - var normalisedBranch = localFixture.Repository.FindBranch("develop"); + var normalisedBranch = localFixture.Repository.Branches["develop"]; normalisedBranch.ShouldNotBe(null); - fixture.Repository.DumpGraph(); - localFixture.Repository.DumpGraph(); normalisedBranch.Tip.Sha.ShouldBe(advancedCommit.Sha); localFixture.Repository.Head.Tip.Sha.ShouldBe(advancedCommit.Sha); } @@ -125,10 +128,53 @@ public void ShouldNotChangeBranchWhenNormalizingTheDirectory() GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: null); - localFixture.Repository.DumpGraph(); localFixture.Repository.Head.Tip.Sha.ShouldBe(lastCommitOnDevelop.Sha); } } } + + [Test] + public void ShouldNotMoveLocalBranchWhenRemoteAdvances() + { + using (var fixture = new EmptyRepositoryFixture()) + { + fixture.Repository.MakeACommit(); + + fixture.Repository.Checkout(fixture.Repository.CreateBranch("feature/foo")); + fixture.Repository.MakeACommit(); + using (var localFixture = fixture.CloneRepository()) + { + localFixture.Checkout("feature/foo"); + var expectedTip = localFixture.Repository.Head.Tip; + // Advance remote + fixture.Repository.MakeACommit(); + GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: null); + + var normalisedBranch = localFixture.Repository.Branches["feature/foo"]; + normalisedBranch.ShouldNotBe(null); + normalisedBranch.Tip.Sha.ShouldBe(expectedTip.Sha); + } + } + } + + [Test] + public void CheckedOutShaShouldNotChanged() + { + using (var fixture = new EmptyRepositoryFixture()) + { + fixture.Repository.MakeACommit(); + var commitToBuild = fixture.Repository.MakeACommit(); + fixture.Repository.MakeACommit(); + + using (var localFixture = fixture.CloneRepository()) + { + localFixture.Repository.Checkout(commitToBuild); + GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: "refs/heads/master"); + + var normalisedBranch = localFixture.Repository.Branches["master"]; + normalisedBranch.Tip.Sha.ShouldBe(commitToBuild.Sha); + } + } + } } } \ No newline at end of file diff --git a/src/GitTools.Core.Tests/GitRepositoryTests.cs b/src/GitTools.Core.Tests/GitRepositoryTests.cs deleted file mode 100644 index 5875b07..0000000 --- a/src/GitTools.Core.Tests/GitRepositoryTests.cs +++ /dev/null @@ -1,159 +0,0 @@ -namespace GitTools.Tests -{ - using GitTools.Git; - using LibGit2Sharp; - using NUnit.Framework; - using Shouldly; - using Testing; - - public class GitHelperTests - { - [Test] - public void NormalisationOfPullRequestsWithFetch() - { - using (var fixture = new EmptyRepositoryFixture()) - { - fixture.Repository.MakeACommit(); - - fixture.Repository.Checkout(fixture.Repository.CreateBranch("feature/foo")); - fixture.Repository.MakeACommit(); - var commit = fixture.Repository.CreatePullRequestRef("feature/foo", "master", prNumber: 3); - using (var localFixture = fixture.CloneRepository()) - { - localFixture.Checkout(commit.Sha); - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: string.Empty); - - var normalisedPullBranch = localFixture.Repository.Branches["pull/3/merge"]; - normalisedPullBranch.ShouldNotBe(null); - } - } - } - - [Test] - public void NormalisationOfTag() - { - using (var fixture = new EmptyRepositoryFixture()) - { - fixture.Repository.MakeACommit(); - - fixture.Repository.Checkout(fixture.Repository.CreateBranch("feature/foo")); - fixture.Repository.MakeACommit(); - - fixture.BranchTo("release/2.0.0"); - fixture.MakeACommit(); - fixture.MakeATaggedCommit("2.0.0-rc.1"); - fixture.Checkout("master"); - fixture.MergeNoFF("release/2.0.0"); - fixture.Repository.Branches.Remove(fixture.Repository.Branches["release/2.0.0"]); - var remoteTagSha = fixture.Repository.Tags["2.0.0-rc.1"].Target.Sha; - - using (var localFixture = fixture.CloneRepository()) - { - localFixture.Checkout(remoteTagSha); - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: string.Empty); - - localFixture.Repository.Head.FriendlyName.ShouldBe("(no branch)"); - localFixture.Repository.Head.Tip.Sha.ShouldBe(remoteTagSha); - } - } - } - - [Test] - public void NormalisationOfPullRequestsWithoutFetch() - { - using (var fixture = new EmptyRepositoryFixture()) - { - fixture.Repository.MakeACommit(); - - fixture.Repository.Checkout(fixture.Repository.CreateBranch("feature/foo")); - fixture.Repository.MakeACommit(); - var commit = fixture.Repository.CreatePullRequestRef("feature/foo", "master", prNumber: 3, allowFastFowardMerge: true); - using (var localFixture = fixture.CloneRepository()) - { - localFixture.Checkout(commit.Sha); - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: true, currentBranch: "refs/pull/3/merge"); - - var normalisedPullBranch = localFixture.Repository.Branches["pull/3/merge"]; - normalisedPullBranch.ShouldNotBe(null); - } - } - } - - [Test] - public void UpdatesLocalBranchesWhen() - { - using (var fixture = new EmptyRepositoryFixture()) - { - fixture.Repository.MakeACommit(); - - fixture.Repository.Checkout(fixture.Repository.CreateBranch("feature/foo")); - fixture.Repository.MakeACommit(); - using (var localFixture = fixture.CloneRepository()) - { - localFixture.Checkout("feature/foo"); - // Advance remote - var advancedCommit = fixture.Repository.MakeACommit(); - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: null); - - var normalisedBranch = localFixture.Repository.Branches["feature/foo"]; - normalisedBranch.ShouldNotBe(null); - normalisedBranch.Tip.Sha.ShouldBe(advancedCommit.Sha); - } - } - } - - [Test] - public void UpdatesCurrentBranch() - { - using (var fixture = new EmptyRepositoryFixture()) - { - fixture.Repository.MakeACommit(); - fixture.Repository.Checkout(fixture.Repository.CreateBranch("develop")); - fixture.Repository.MakeACommit(); - fixture.Repository.Checkout("master"); - using (var localFixture = fixture.CloneRepository()) - { - // Advance remote - fixture.Repository.Checkout("develop"); - var advancedCommit = fixture.Repository.MakeACommit(); - Commands.Fetch((Repository)localFixture.Repository, localFixture.Repository.Network.Remotes["origin"].Name, new string[0], null, null); - localFixture.Repository.Checkout(advancedCommit.Sha); - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: "ref/heads/develop"); - - var normalisedBranch = localFixture.Repository.Branches["develop"]; - normalisedBranch.ShouldNotBe(null); - normalisedBranch.Tip.Sha.ShouldBe(advancedCommit.Sha); - localFixture.Repository.Head.Tip.Sha.ShouldBe(advancedCommit.Sha); - } - } - } - - [Test] - public void ShouldNotChangeBranchWhenNormalizingTheDirectory() - { - using (var fixture = new EmptyRepositoryFixture()) - { - fixture.Repository.MakeATaggedCommit("v1.0.0"); - - fixture.Repository.Checkout(fixture.Repository.CreateBranch("develop")); - var lastCommitOnDevelop = fixture.Repository.MakeACommit(); - - fixture.Repository.Checkout(fixture.Repository.CreateBranch("feature/foo")); - fixture.Repository.MakeACommit(); - - using (var localFixture = fixture.CloneRepository()) - { - localFixture.Repository.Checkout("origin/develop"); - - // Another commit on feature/foo will force an update - fixture.Checkout("feature/foo"); - fixture.Repository.MakeACommit(); - - GitRepositoryHelper.NormalizeGitDirectory(localFixture.RepositoryPath, new AuthenticationInfo(), noFetch: false, currentBranch: null); - - localFixture.Repository.Head.Tip.Sha.ShouldBe(lastCommitOnDevelop.Sha); - } - } - } - } -} \ No newline at end of file diff --git a/src/GitTools.Core.Tests/GitTools.Core.Tests.csproj b/src/GitTools.Core.Tests/GitTools.Core.Tests.csproj index 15da9a3..b57a718 100644 --- a/src/GitTools.Core.Tests/GitTools.Core.Tests.csproj +++ b/src/GitTools.Core.Tests/GitTools.Core.Tests.csproj @@ -71,11 +71,10 @@ Properties\SolutionAssemblyInfo.cs - - + diff --git a/src/GitTools.Core/GitTools.Core.Shared/Exceptions/GitToolsException.cs b/src/GitTools.Core/GitTools.Core.Shared/Exceptions/GitToolsException.cs index fdb75c3..3f749c8 100644 --- a/src/GitTools.Core/GitTools.Core.Shared/Exceptions/GitToolsException.cs +++ b/src/GitTools.Core/GitTools.Core.Shared/Exceptions/GitToolsException.cs @@ -8,5 +8,9 @@ public GitToolsException(string messageFormat, params object[] args) : base(string.Format(messageFormat, args)) { } + public GitToolsException(string message, Exception innerException) + : base(message, innerException) + { + } } } \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/AuthenticationInfo.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/AuthenticationInfo.cs index 0b1ec34..24a2b82 100644 --- a/src/GitTools.Core/GitTools.Core.Shared/Git/AuthenticationInfo.cs +++ b/src/GitTools.Core/GitTools.Core.Shared/Git/AuthenticationInfo.cs @@ -1,9 +1,27 @@ namespace GitTools.Git { + using LibGit2Sharp; + public class AuthenticationInfo { public string Username { get; set; } public string Password { get; set; } public string Token { get; set; } + + public FetchOptions ToFetchOptions() + { + var fetchOptions = new FetchOptions(); + + if (!string.IsNullOrEmpty(Username)) + { + fetchOptions.CredentialsProvider = (url, user, types) => new UsernamePasswordCredentials + { + Username = Username, + Password = Password + }; + } + + return fetchOptions; + } } } \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/DynamicRepositories.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/DynamicRepositories.cs new file mode 100644 index 0000000..c039fa6 --- /dev/null +++ b/src/GitTools.Core/GitTools.Core.Shared/Git/DynamicRepositories.cs @@ -0,0 +1,215 @@ +namespace GitTools.Git +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using LibGit2Sharp; + using Logging; + + public static class DynamicRepositories + { + static readonly ILog Log = LogProvider.GetCurrentClassLogger(); + + /// + /// Creates a dynamic repository based on the repository info + /// + /// The source repository information. + /// The path to create the dynamic repository, NOT thread safe. + /// + /// + /// The git repository. + public static DynamicRepository CreateOrOpen(RepositoryInfo repositoryInfo, string dynamicRepsitoryPath, string targetBranch, string targetCommit) + { + if (string.IsNullOrWhiteSpace(dynamicRepsitoryPath) || !Directory.Exists(dynamicRepsitoryPath)) + throw new GitToolsException(string.Format("Dynamic repository path {0} does not exist, ensure it is created before trying to create dynamic repository.", dynamicRepsitoryPath)); + if (string.IsNullOrWhiteSpace(targetBranch)) + throw new GitToolsException("Dynamic Git repositories must have a target branch"); + if (string.IsNullOrWhiteSpace(targetCommit)) + throw new GitToolsException("Dynamic Git repositories must have a target commit"); + + var tempRepositoryPath = GetAndLockTemporaryRepositoryPath(repositoryInfo.Url, dynamicRepsitoryPath); + var dynamicRepositoryPath = CreateDynamicRepository(tempRepositoryPath, repositoryInfo, targetBranch, targetCommit); + + return new DynamicRepository(new Repository(dynamicRepositoryPath), () => ReleaseDynamicRepoLock(tempRepositoryPath)); + } + + static void ReleaseDynamicRepoLock(string repoPath) + { + var lockFile = GetLockFile(repoPath); + try + { + File.Delete(lockFile); + } + catch (Exception ex) + { + throw new GitToolsException(string.Format("Failed to delete dynamic repository lock file '{0}', this dynamic repository will not be used until the lock file is removed", lockFile), ex); + } + } + + static bool TakeDynamicRepoLock(string possibleDynamicRepoPath) + { + try + { + // Ensure directory exists + try { Directory.CreateDirectory(possibleDynamicRepoPath); } catch (IOException) { } + // Check if file exists and create lock file in a safe way + using (new FileStream(GetLockFile(possibleDynamicRepoPath), FileMode.CreateNew)) { } + } + catch (IOException) + { + return false; + } + return true; + } + + static string GetLockFile(string repoPath) + { + return Path.Combine(repoPath, "dynamicrepository.lock"); + } + + static string GetAndLockTemporaryRepositoryPath(string targetUrl, string dynamicRepositoryLocation) + { + var repositoryName = targetUrl.Split('/', '\\').Last().Replace(".git", string.Empty); + var possiblePath = Path.Combine(dynamicRepositoryLocation, repositoryName); + + var i = 1; + var originalPath = possiblePath; + var possiblePathExists = Directory.Exists(possiblePath); + if (VerifyDynamicRepositoryTarget(targetUrl, possiblePathExists, possiblePath)) return possiblePath; + do + { + if (i > 10) + { + throw new GitToolsException(string.Format( + "Failed to find a dynamic repository path at {0} -> {1}", + originalPath, + possiblePath)); + } + possiblePath = string.Concat(originalPath, "_", i++.ToString()); + possiblePathExists = Directory.Exists(possiblePath); + } while (!VerifyDynamicRepositoryTarget(targetUrl, possiblePathExists, possiblePath)); + + return possiblePath; + } + + static bool VerifyDynamicRepositoryTarget(string targetUrl, bool possiblePathExists, string possiblePath) + { + // First take a lock on that path + var lockTaken = TakeDynamicRepoLock(possiblePath); + if (!lockTaken) return false; + + if (!possiblePathExists) return true; + + // Then verify it's suitable + if (!GitRepoHasMatchingRemote(possiblePath, targetUrl)) + { + // Release lock if not suitable + ReleaseDynamicRepoLock(possiblePath); + return false; + } + return true; + } + + static bool GitRepoHasMatchingRemote(string possiblePath, string targetUrl) + { + try + { + using (var repository = new Repository(possiblePath)) + { + return repository.Network.Remotes.Any(r => r.Url == targetUrl); + } + } + catch (Exception) + { + return false; + } + } + + [SuppressMessage("ReSharper", "ArgumentsStyleLiteral")] + [SuppressMessage("ReSharper", "ArgumentsStyleNamedExpression")] + static string CreateDynamicRepository(string targetPath, RepositoryInfo repositoryInfo, string targetBranch, string targetCommit) + { + Log.Info(string.Format("Creating dynamic repository at '{0}'", targetPath)); + + var gitDirectory = Path.Combine(targetPath, ".git"); + if (Directory.Exists(gitDirectory)) + { + Log.Info("Git repository already exists"); + using (var repo = new Repository(gitDirectory)) + { + // We need to fetch before we can checkout the commit + var remote = GitRepositoryHelper.EnsureOnlyOneRemoteIsDefined(repo); + GitRepositoryHelper.Fetch(repositoryInfo.Authentication, remote, repo); + CheckoutCommit(repo, targetCommit); + } + GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, repositoryInfo.Authentication, noFetch: true, currentBranch: targetBranch); + + return gitDirectory; + } + + CloneRepository(repositoryInfo.Url, gitDirectory, repositoryInfo.Authentication); + + using (var repo = new Repository(gitDirectory)) + { + CheckoutCommit(repo, targetCommit); + } + + // Normalize (download branches) before using the branch + GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, repositoryInfo.Authentication, noFetch: true, currentBranch: targetBranch); + + return gitDirectory; + } + + static void CheckoutCommit(IRepository repo, string targetCommit) + { + Log.Info(string.Format("Checking out {0}", targetCommit)); + repo.Checkout(targetCommit); + } + + static void CloneRepository(string repositoryUrl, string gitDirectory, AuthenticationInfo authentication) + { + Credentials credentials = null; + if (!string.IsNullOrWhiteSpace(authentication.Username) && !string.IsNullOrWhiteSpace(authentication.Password)) + { + Log.Info(string.Format("Setting up credentials using name '{0}'", authentication.Username)); + + credentials = new UsernamePasswordCredentials + { + Username = authentication.Username, + Password = authentication.Password + }; + } + + Log.Info(string.Format("Retrieving git info from url '{0}'", repositoryUrl)); + + try + { + Repository.Clone(repositoryUrl, gitDirectory, + new CloneOptions + { + Checkout = false, + CredentialsProvider = (url, usernameFromUrl, types) => credentials + }); + } + catch (LibGit2SharpException ex) + { + var message = ex.Message; + if (message.Contains("401")) + { + throw new GitToolsException("Unauthorised: Incorrect username/password"); + } + if (message.Contains("403")) + { + throw new GitToolsException("Forbidden: Possbily Incorrect username/password"); + } + if (message.Contains("404")) + { + throw new GitToolsException("Not found: The repository was not found"); + } + + throw new GitToolsException("There was an unknown problem with the Git repository you provided"); + } + } + } +} \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/DynamicRepository.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/DynamicRepository.cs new file mode 100644 index 0000000..6f4e0e1 --- /dev/null +++ b/src/GitTools.Core/GitTools.Core.Shared/Git/DynamicRepository.cs @@ -0,0 +1,31 @@ +namespace GitTools.Git +{ + using System; + using LibGit2Sharp; + + public class DynamicRepository : IDisposable + { + readonly Action _dispose; + + public DynamicRepository(Repository repository, Action dispose) + { + Repository = repository; + _dispose = dispose; + } + + public Repository Repository { get; private set; } + + public void Dispose() + { + try + { + Repository.Dispose(); + + } + finally + { + _dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/Extensions/LibGitExtensions.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/Extensions/LibGitExtensions.cs index 9ac05c0..591c6a7 100644 --- a/src/GitTools.Core/GitTools.Core.Shared/Git/Extensions/LibGitExtensions.cs +++ b/src/GitTools.Core/GitTools.Core.Shared/Git/Extensions/LibGitExtensions.cs @@ -166,8 +166,7 @@ public static void DumpGraph(string workingDirectory, Action writer = nu e => output.AppendLineFormat("ERROR: {0}", e), null, "git", - @"log --graph --format=""%h %cr %d"" --decorate --date=relative --all --remotes=*" + (maxCommits != null ? string.Format(" -n {0}", maxCommits) : null), - //@"log --graph --abbrev-commit --decorate --date=relative --all --remotes=*", + CreateGitLogArgs(maxCommits), workingDirectory); } catch (FileNotFoundException exception) @@ -190,5 +189,10 @@ public static void DumpGraph(string workingDirectory, Action writer = nu Console.Write(output.ToString()); } } + + public static string CreateGitLogArgs(int? maxCommits) + { + return @"log --graph --format=""%h %cr %d"" --decorate --date=relative --all --remotes=*" + (maxCommits != null ? string.Format(" -n {0}", maxCommits) : null); + } } } \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/GitRepository.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/GitRepository.cs deleted file mode 100644 index 98141e2..0000000 --- a/src/GitTools.Core/GitTools.Core.Shared/Git/GitRepository.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace GitTools.Git -{ - using System.IO; - using LibGit2Sharp; - - public class GitRepository : Disposable - { - public GitRepository(IRepository repository, bool isDynamic) - { - Repository = repository; - IsDynamic = isDynamic; - - ProjectRootDirectory = repository.Info.WorkingDirectory; - DotGitDirectory = Path.Combine(ProjectRootDirectory, ".git"); - } - - public IRepository Repository { get; private set; } - - public bool IsDynamic { get; private set; } - - public string DotGitDirectory { get; private set; } - - public string ProjectRootDirectory { get; private set; } - - protected override void DisposeManaged() - { - base.DisposeManaged(); - - if (Repository != null) - { - Repository.Dispose(); - Repository = null; - } - } - } -} \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/GitRepositoryFactory.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/GitRepositoryFactory.cs deleted file mode 100644 index 0ab716b..0000000 --- a/src/GitTools.Core/GitTools.Core.Shared/Git/GitRepositoryFactory.cs +++ /dev/null @@ -1,172 +0,0 @@ -namespace GitTools.Git -{ - using System; - using System.IO; - using System.Linq; - using LibGit2Sharp; - using Logging; - - public static class GitRepositoryFactory - { - static readonly ILog Log = LogProvider.GetCurrentClassLogger(); - - /// - /// Creates the repository based on the repository info. If the points - /// to a valid directory, it will be used as the source for the git repository. Otherwise this method will create - /// a dynamic repository based on the url and authentication info. - /// - /// The repository information. - /// If set to true, don't fetch anything. - /// The git repository. - public static GitRepository CreateRepository(RepositoryInfo repositoryInfo, bool noFetch = false) - { - bool isDynamicRepository = false; - string repositoryDirectory = null; - - // TODO: find a better way to check for existing repositories - if (!string.IsNullOrWhiteSpace(repositoryInfo.Directory)) - { - var expectedDirectory = Path.Combine(repositoryInfo.Directory, ".git"); - if (Directory.Exists(expectedDirectory)) - { - repositoryDirectory = expectedDirectory; - } - } - - if (string.IsNullOrWhiteSpace(repositoryDirectory)) - { - isDynamicRepository = true; - - var tempRepositoryPath = CalculateTemporaryRepositoryPath(repositoryInfo.Url, repositoryDirectory); - repositoryDirectory = CreateDynamicRepository(tempRepositoryPath, repositoryInfo.Authentication, - repositoryInfo.Url, repositoryInfo.Branch, noFetch); - } - - if (string.IsNullOrWhiteSpace(repositoryDirectory)) - { - Log.Warn("Could not create a repository, not enough information was specified"); - return null; - } - - // TODO: Should we do something with fetch for existing repositoriess? - var repository = new Repository(repositoryDirectory); - return new GitRepository(repository, isDynamicRepository); - } - - static string CalculateTemporaryRepositoryPath(string targetUrl, string dynamicRepositoryLocation) - { - var userTemp = dynamicRepositoryLocation; - if (string.IsNullOrWhiteSpace(userTemp)) - { - userTemp = Path.GetTempPath(); - } - - var repositoryName = targetUrl.Split('/', '\\').Last().Replace(".git", string.Empty); - var possiblePath = Path.Combine(userTemp, repositoryName); - - // Verify that the existing directory is ok for us to use - if (Directory.Exists(possiblePath)) - { - if (!GitRepoHasMatchingRemote(possiblePath, targetUrl)) - { - var i = 1; - var originalPath = possiblePath; - bool possiblePathExists; - do - { - possiblePath = string.Concat(originalPath, "_", i++.ToString()); - possiblePathExists = Directory.Exists(possiblePath); - } while (possiblePathExists && !GitRepoHasMatchingRemote(possiblePath, targetUrl)); - } - } - - return possiblePath; - } - - static bool GitRepoHasMatchingRemote(string possiblePath, string targetUrl) - { - try - { - using (var repository = new Repository(possiblePath)) - { - return repository.Network.Remotes.Any(r => r.Url == targetUrl); - } - } - catch (Exception) - { - return false; - } - } - - static string CreateDynamicRepository(string targetPath, AuthenticationInfo authentication, string repositoryUrl, string targetBranch, bool noFetch) - { - if (string.IsNullOrWhiteSpace(targetBranch)) - { - throw new GitToolsException("Dynamic Git repositories must have a target branch (/b)"); - } - - Log.Info(string.Format("Creating dynamic repository at '{0}'", targetPath)); - - var gitDirectory = Path.Combine(targetPath, ".git"); - if (Directory.Exists(targetPath)) - { - Log.Info("Git repository already exists"); - GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, authentication, noFetch, targetBranch); - - return gitDirectory; - } - - CloneRepository(repositoryUrl, gitDirectory, authentication); - - // Normalize (download branches) before using the branch - GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, authentication, noFetch, targetBranch); - - return gitDirectory; - } - - private static void CloneRepository(string repositoryUrl, string gitDirectory, AuthenticationInfo authentication) - { - Credentials credentials = null; - if (!string.IsNullOrWhiteSpace(authentication.Username) && !string.IsNullOrWhiteSpace(authentication.Password)) - { - Log.Info(string.Format("Setting up credentials using name '{0}'", authentication.Username)); - - credentials = new UsernamePasswordCredentials - { - Username = authentication.Username, - Password = authentication.Password - }; - } - - Log.Info(string.Format("Retrieving git info from url '{0}'", repositoryUrl)); - - try - { - Repository.Clone(repositoryUrl, gitDirectory, - new CloneOptions - { - Checkout = false, - CredentialsProvider = (url, usernameFromUrl, types) => credentials - }); - } - catch (LibGit2SharpException ex) - { - var message = ex.Message; - if (message.Contains("401")) - { - throw new GitToolsException("Unauthorised: Incorrect username/password"); - } - if (message.Contains("403")) - { - throw new GitToolsException("Forbidden: Possbily Incorrect username/password"); - } - if (message.Contains("404")) - { - throw new GitToolsException("Not found: The repository was not found"); - } - - throw new GitToolsException("There was an unknown problem with the Git repository you provided"); - } - } - } -} \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/Helpers/BugException.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/Helpers/BugException.cs new file mode 100644 index 0000000..4e1845c --- /dev/null +++ b/src/GitTools.Core/GitTools.Core.Shared/Git/Helpers/BugException.cs @@ -0,0 +1,11 @@ +namespace GitTools.Git +{ + using System; + + public class BugException : Exception + { + public BugException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/Helpers/GitRepositoryHelper.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/Helpers/GitRepositoryHelper.cs index 1f1fa14..792687e 100644 --- a/src/GitTools.Core/GitTools.Core.Shared/Git/Helpers/GitRepositoryHelper.cs +++ b/src/GitTools.Core/GitTools.Core.Shared/Git/Helpers/GitRepositoryHelper.cs @@ -22,91 +22,116 @@ public static void NormalizeGitDirectory(string gitDirectory, AuthenticationInfo { using (var repo = new Repository(gitDirectory)) { - var remote = EnsureOnlyOneRemoteIsDefined(repo); - - AddMissingRefSpecs(repo, remote); - - //If noFetch is enabled, then GitVersion will assume that the git repository is normalized before execution, so that fetching from remotes is not required. - if (noFetch) - { - Log.Info("Skipping fetching, if GitVersion does not calculate your version as expected you might need to allow fetching or use dynamic repositories"); - } - else + // Need to unsure the HEAD does not move, this is essentially a BugCheck + var expectedSha = repo.Head.Tip.Sha; + try { - Log.Info(string.Format("Fetching from remote '{0}' using the following refspecs: {1}.", - remote.Name, string.Join(", ", remote.FetchRefSpecs.Select(r => r.Specification)))); - var fetchOptions = BuildFetchOptions(authentication.Username, authentication.Password); - Commands.Fetch(repo, remote.Name, new string[0], fetchOptions, null); - } + var remote = EnsureOnlyOneRemoteIsDefined(repo); - EnsureLocalBranchExistsForCurrentBranch(repo, currentBranch); - CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(repo, remote.Name); + AddMissingRefSpecs(repo, remote); - var headSha = repo.Refs.Head.TargetIdentifier; + //If noFetch is enabled, then GitVersion will assume that the git repository is normalized before execution, so that fetching from remotes is not required. + if (noFetch) + { + Log.Info("Skipping fetching, if GitVersion does not calculate your version as expected you might need to allow fetching or use dynamic repositories"); + } + else + { + Fetch(authentication, remote, repo); + } - if (!repo.Info.IsHeadDetached) - { - Log.Info(string.Format("HEAD points at branch '{0}'.", headSha)); - return; - } + EnsureLocalBranchExistsForCurrentBranch(repo, currentBranch); + CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(repo, remote.Name); - Log.Info(string.Format("HEAD is detached and points at commit '{0}'.", headSha)); - Log.Info(string.Format("Local Refs:\r\n" + string.Join(Environment.NewLine, repo.Refs.FromGlob("*").Select(r => string.Format("{0} ({1})", r.CanonicalName, r.TargetIdentifier))))); + var headSha = repo.Refs.Head.TargetIdentifier; - // In order to decide whether a fake branch is required or not, first check to see if any local branches have the same commit SHA of the head SHA. - // If they do, go ahead and checkout that branch - // If no, go ahead and check out a new branch, using the known commit SHA as the pointer - var localBranchesWhereCommitShaIsHead = repo.Branches.Where(b => !b.IsRemote && b.Tip.Sha == headSha).ToList(); + if (!repo.Info.IsHeadDetached) + { + Log.Info(string.Format("HEAD points at branch '{0}'.", headSha)); + return; + } - var matchingCurrentBranch = !string.IsNullOrEmpty(currentBranch) - ? localBranchesWhereCommitShaIsHead.SingleOrDefault(b => b.CanonicalName.Replace("/heads/", "/") == currentBranch.Replace("/heads/", "/")) - : null; - if (matchingCurrentBranch != null) - { - Log.Info(string.Format("Checking out local branch '{0}'.", currentBranch)); - repo.Checkout(matchingCurrentBranch); - } - else if (localBranchesWhereCommitShaIsHead.Count > 1) - { - var branchNames = localBranchesWhereCommitShaIsHead.Select(r => r.CanonicalName); - var csvNames = string.Join(", ", branchNames); - const string moveBranchMsg = "Move one of the branches along a commit to remove warning"; + Log.Info(string.Format("HEAD is detached and points at commit '{0}'.", headSha)); + Log.Info(string.Format("Local Refs:\r\n" + string.Join(Environment.NewLine, repo.Refs.FromGlob("*").Select(r => string.Format("{0} ({1})", r.CanonicalName, r.TargetIdentifier))))); + + // In order to decide whether a fake branch is required or not, first check to see if any local branches have the same commit SHA of the head SHA. + // If they do, go ahead and checkout that branch + // If no, go ahead and check out a new branch, using the known commit SHA as the pointer + var localBranchesWhereCommitShaIsHead = repo.Branches.Where(b => !b.IsRemote && b.Tip.Sha == headSha).ToList(); - Log.Warn(string.Format("Found more than one local branch pointing at the commit '{0}' ({1}).", headSha, csvNames)); - var master = localBranchesWhereCommitShaIsHead.SingleOrDefault(n => n.FriendlyName == "master"); - if (master != null) + var matchingCurrentBranch = !string.IsNullOrEmpty(currentBranch) + ? localBranchesWhereCommitShaIsHead.SingleOrDefault(b => b.CanonicalName.Replace("/heads/", "/") == currentBranch.Replace("/heads/", "/")) + : null; + if (matchingCurrentBranch != null) { - Log.Warn("Because one of the branches is 'master', will build master." + moveBranchMsg); - repo.Checkout(master); + Log.Info(string.Format("Checking out local branch '{0}'.", currentBranch)); + repo.Checkout(matchingCurrentBranch); } - else + else if (localBranchesWhereCommitShaIsHead.Count > 1) { - var branchesWithoutSeparators = localBranchesWhereCommitShaIsHead.Where(b => !b.FriendlyName.Contains('/') && !b.FriendlyName.Contains('-')).ToList(); - if (branchesWithoutSeparators.Count == 1) + var branchNames = localBranchesWhereCommitShaIsHead.Select(r => r.CanonicalName); + var csvNames = string.Join(", ", branchNames); + const string moveBranchMsg = "Move one of the branches along a commit to remove warning"; + + Log.Warn(string.Format("Found more than one local branch pointing at the commit '{0}' ({1}).", headSha, csvNames)); + var master = localBranchesWhereCommitShaIsHead.SingleOrDefault(n => n.FriendlyName == "master"); + if (master != null) { - var branchWithoutSeparator = branchesWithoutSeparators[0]; - Log.Warn(string.Format("Choosing {0} as it is the only branch without / or - in it. " + moveBranchMsg, branchWithoutSeparator.CanonicalName)); - repo.Checkout(branchWithoutSeparator); + Log.Warn("Because one of the branches is 'master', will build master." + moveBranchMsg); + repo.Checkout(master); } else { - throw new WarningException("Failed to try and guess branch to use. " + moveBranchMsg); + var branchesWithoutSeparators = localBranchesWhereCommitShaIsHead.Where(b => !b.FriendlyName.Contains('/') && !b.FriendlyName.Contains('-')).ToList(); + if (branchesWithoutSeparators.Count == 1) + { + var branchWithoutSeparator = branchesWithoutSeparators[0]; + Log.Warn(string.Format("Choosing {0} as it is the only branch without / or - in it. " + moveBranchMsg, branchWithoutSeparator.CanonicalName)); + repo.Checkout(branchWithoutSeparator); + } + else + { + throw new WarningException("Failed to try and guess branch to use. " + moveBranchMsg); + } } } + else if (localBranchesWhereCommitShaIsHead.Count == 0) + { + Log.Info(string.Format("No local branch pointing at the commit '{0}'. Fake branch needs to be created.", headSha)); + CreateFakeBranchPointingAtThePullRequestTip(repo, authentication); + } + else + { + Log.Info(string.Format("Checking out local branch 'refs/heads/{0}'.", localBranchesWhereCommitShaIsHead[0].FriendlyName)); + repo.Checkout(repo.Branches[localBranchesWhereCommitShaIsHead[0].FriendlyName]); + } } - else if (localBranchesWhereCommitShaIsHead.Count == 0) - { - Log.Info(string.Format("No local branch pointing at the commit '{0}'. Fake branch needs to be created.", headSha)); - CreateFakeBranchPointingAtThePullRequestTip(repo, authentication); - } - else + finally { - Log.Info(string.Format("Checking out local branch 'refs/heads/{0}'.", localBranchesWhereCommitShaIsHead[0].FriendlyName)); - repo.Checkout(repo.Branches[localBranchesWhereCommitShaIsHead[0].FriendlyName]); + if (repo.Head.Tip.Sha != expectedSha) + { + if (Environment.GetEnvironmentVariable("IGNORE_NORMALISATION_GIT_HEAD_MOVE") != "1") + { + // Whoa, HEAD has moved, it shouldn't have. We need to blow up because there is a bug in normalisation + throw new BugException(string.Format(@"GitTools.Core has a bug, your HEAD has moved after repo normalisation. + +To disable this error set an environmental variable called IGNORE_NORMALISATION_GIT_HEAD_MOVE to 1 + +Please run `git {0}` and submit it along with your build log (with personal info removed) in a new issue at https://github.com/GitTools/GitTools.Core", + LibGitExtensions.CreateGitLogArgs(100))); + } + } } } } + public static void Fetch(AuthenticationInfo authentication, Remote remote, Repository repo) + { + Log.Info(string.Format("Fetching from remote '{0}' using the following refspecs: {1}.", + remote.Name, string.Join(", ", remote.FetchRefSpecs.Select(r => r.Specification)))); + Commands.Fetch(repo, remote.Name, new string[0], authentication.ToFetchOptions(), null); + } + static void EnsureLocalBranchExistsForCurrentBranch(Repository repo, string currentBranch) { if (string.IsNullOrEmpty(currentBranch)) return; @@ -143,27 +168,11 @@ static void AddMissingRefSpecs(Repository repo, Remote remote) var allBranchesFetchRefSpec = string.Format("+refs/heads/*:refs/remotes/{0}/*", remote.Name); Log.Info(string.Format("Adding refspec: {0}", allBranchesFetchRefSpec)); - + repo.Network.Remotes.Update(remote.Name, r => r.FetchRefSpecs.Add(allBranchesFetchRefSpec)); } - static FetchOptions BuildFetchOptions(string username, string password) - { - var fetchOptions = new FetchOptions(); - - if (!string.IsNullOrEmpty(username)) - { - fetchOptions.CredentialsProvider = (url, user, types) => new UsernamePasswordCredentials - { - Username = username, - Password = password - }; - } - - return fetchOptions; - } - static void CreateFakeBranchPointingAtThePullRequestTip(Repository repo, AuthenticationInfo authentication) { var remote = repo.Network.Remotes.Single(); @@ -244,6 +253,9 @@ static void CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(Repository repo, s var branchName = remoteTrackingReferenceName.Substring(prefix.Length); var localCanonicalName = "refs/heads/" + branchName; + // We do not want to touch our current branch + if (branchName == repo.Head.FriendlyName) continue; + if (repo.Refs.Any(x => x.CanonicalName == localCanonicalName)) { var localRef = repo.Refs[localCanonicalName]; @@ -267,7 +279,7 @@ static void CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(Repository repo, s } } - static Remote EnsureOnlyOneRemoteIsDefined(IRepository repo) + public static Remote EnsureOnlyOneRemoteIsDefined(IRepository repo) { var remotes = repo.Network.Remotes; var howMany = remotes.Count(); diff --git a/src/GitTools.Core/GitTools.Core.Shared/Git/RepositoryInfo.cs b/src/GitTools.Core/GitTools.Core.Shared/Git/RepositoryInfo.cs index 86304f5..3a987ec 100644 --- a/src/GitTools.Core/GitTools.Core.Shared/Git/RepositoryInfo.cs +++ b/src/GitTools.Core/GitTools.Core.Shared/Git/RepositoryInfo.cs @@ -8,11 +8,6 @@ public RepositoryInfo() } public AuthenticationInfo Authentication { get; set; } - - public string Directory { get; set; } - - public string Branch { get; set; } - public string Url { get; set; } } } \ No newline at end of file diff --git a/src/GitTools.Core/GitTools.Core.Shared/GitTools.Core.Shared.projitems b/src/GitTools.Core/GitTools.Core.Shared/GitTools.Core.Shared.projitems index a76f002..f135c8e 100644 --- a/src/GitTools.Core/GitTools.Core.Shared/GitTools.Core.Shared.projitems +++ b/src/GitTools.Core/GitTools.Core.Shared/GitTools.Core.Shared.projitems @@ -14,12 +14,13 @@ + - - + +