From e1b6166af04a346baac90c809ea79b7b753586b1 Mon Sep 17 00:00:00 2001 From: Marius Ungureanu Date: Mon, 2 Jun 2014 22:16:17 +0300 Subject: [PATCH] Introduce RemoteCollection.Rename. --- LibGit2Sharp.Tests/RemoteFixture.cs | 90 +++++++++++++++++++++++++++++ LibGit2Sharp/Core/NativeMethods.cs | 11 ++++ LibGit2Sharp/Core/Proxy.cs | 28 +++++++++ LibGit2Sharp/Handlers.cs | 10 ++++ LibGit2Sharp/RemoteCollection.cs | 17 ++++++ 5 files changed, 156 insertions(+) diff --git a/LibGit2Sharp.Tests/RemoteFixture.cs b/LibGit2Sharp.Tests/RemoteFixture.cs index b33213495..87e3bc0f4 100644 --- a/LibGit2Sharp.Tests/RemoteFixture.cs +++ b/LibGit2Sharp.Tests/RemoteFixture.cs @@ -214,5 +214,95 @@ public void CanDeleteNonExistingRemote() repo.Network.Remotes.Remove("i_dont_exist"); } } + + [Fact] + public void CanRenameExistingRemote() + { + var path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + Assert.Null(repo.Network.Remotes["renamed"]); + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/origin/*")); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/renamed/*")); + + repo.Network.Remotes.Rename("origin", "renamed", problem => Assert.True(false)); + Assert.Null(repo.Network.Remotes["origin"]); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/origin/*")); + + Assert.NotNull(repo.Network.Remotes["renamed"]); + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/renamed/*")); + } + } + + [Fact] + public void CanRenameNonExistingRemote() + { + using (var repo = new Repository(StandardTestRepoPath)) + { + Assert.Null(repo.Network.Remotes["i_dont_exist"]); + + repo.Network.Remotes.Rename("i_dont_exist", "i_dont_either", problem => Assert.True(false)); + Assert.Null(repo.Network.Remotes["i_dont_either"]); + } + } + + [Fact] + public void ReportsRemotesWithNonDefaultRefSpecs() + { + var path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + + repo.Network.Remotes.Update( + repo.Network.Remotes["origin"], + r => r.FetchRefSpecs = new[] { "+refs/heads/*:refs/remotes/upstream/*" }); + + repo.Network.Remotes.Rename("origin", "nondefault", problem => Assert.Equal("+refs/heads/*:refs/remotes/upstream/*", problem)); + + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/nondefault/*")); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/upstream/*")); + + Assert.Null(repo.Network.Remotes["origin"]); + Assert.NotNull(repo.Network.Remotes["nondefault"]); + } + } + + [Fact] + public void DoesNotReportRemotesWithAlreadyExistingRefSpec() + { + var path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + + repo.Refs.Add("refs/remotes/renamed/master", "32eab9cb1f450b5fe7ab663462b77d7f4b703344"); + + repo.Network.Remotes.Rename("origin", "renamed", problem => Assert.True(false)); + + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/renamed/*")); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/origin/*")); + + Assert.Null(repo.Network.Remotes["origin"]); + Assert.NotNull(repo.Network.Remotes["renamed"]); + } + } + + [Fact] + public void CanNotRenameWhenRemoteWithSameNameExists() + { + const string name = "upstream"; + const string url = "https://github.com/libgit2/libgit2sharp.git"; + + var path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + repo.Network.Remotes.Add(name, url); + + Assert.Throws(() => repo.Network.Remotes.Rename("origin", "upstream")); + } + } } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index faf1e02b5..ea5bff09b 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -212,6 +212,17 @@ internal static extern int git_branch_remote_name( RepositorySafeHandle repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string canonical_branch_name); + [DllImport(libgit2)] + internal static extern int git_remote_rename( + RemoteSafeHandle remote, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string new_name, + git_remote_rename_problem_cb callback, + IntPtr payload); + + internal delegate int git_remote_rename_problem_cb( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string problematic_refspec, + IntPtr payload); + [DllImport(libgit2)] internal static extern int git_branch_upstream_name( GitBuf buf, diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index e6b3940b2..feb66d6c0 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Threading; using LibGit2Sharp.Core.Handles; +using LibGit2Sharp.Handlers; // ReSharper disable InconsistentNaming namespace LibGit2Sharp.Core @@ -1958,6 +1959,33 @@ public static string git_remote_name(RemoteSafeHandle remote) return NativeMethods.git_remote_name(remote); } + public static void git_remote_rename(RepositorySafeHandle repo, string name, string new_name, RemoteRenameFailureHandler callback) + { + using (ThreadAffinity()) + { + using (RemoteSafeHandle remote = git_remote_load(repo, name, false)) + { + if (remote == null) + { + return; + } + + if (callback == null) + { + callback = (problem) => {}; + } + + int res = NativeMethods.git_remote_rename( + remote, + new_name, + (problem, payload) => { callback(problem); return 0; }, + IntPtr.Zero); + + Ensure.ZeroResult(res); + } + } + } + public static void git_remote_save(RemoteSafeHandle remote) { using (ThreadAffinity()) diff --git a/LibGit2Sharp/Handlers.cs b/LibGit2Sharp/Handlers.cs index 17e822ab6..3c0f67fc2 100644 --- a/LibGit2Sharp/Handlers.cs +++ b/LibGit2Sharp/Handlers.cs @@ -78,6 +78,16 @@ /// The unmatched path. public delegate void UnmatchedPathHandler(string unmatchedPath); + /// + /// Delegate definition for remote rename failure callback. + /// + /// This callback will be called to notify the caller of fetch refspecs + /// that haven't been automatically updated and need potential manual tweaking. + /// + /// + /// The refspec which didn't match the default. + public delegate void RemoteRenameFailureHandler(string problematicRefspec); + /// /// The stages of pack building. /// diff --git a/LibGit2Sharp/RemoteCollection.cs b/LibGit2Sharp/RemoteCollection.cs index 300ed8f72..782316afb 100644 --- a/LibGit2Sharp/RemoteCollection.cs +++ b/LibGit2Sharp/RemoteCollection.cs @@ -6,6 +6,7 @@ using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; +using LibGit2Sharp.Handlers; namespace LibGit2Sharp { @@ -138,6 +139,22 @@ public virtual void Remove(string name) Proxy.git_remote_delete(repository.Handle, name); } + /// + /// Renames an existing . + /// + /// The current remote name. + /// The new name the existing remote should bear. + /// The callback to be used when problems with renaming occur. (e.g. non-default fetch refspecs) + /// A new . + public virtual Remote Rename(string name, string newName, RemoteRenameFailureHandler callback = null) + { + Ensure.ArgumentNotNull(name, "name"); + Ensure.ArgumentNotNull(newName, "newName"); + + Proxy.git_remote_rename(repository.Handle, name, newName, callback); + return this[newName]; + } + private string DebuggerDisplay { get