diff --git a/LibGit2Sharp.Tests/NetworkFixture.cs b/LibGit2Sharp.Tests/NetworkFixture.cs index 0cb03f46b..d31805fc0 100644 --- a/LibGit2Sharp.Tests/NetworkFixture.cs +++ b/LibGit2Sharp.Tests/NetworkFixture.cs @@ -43,6 +43,37 @@ public void CanListRemoteReferences(string url) } } + [Theory] + [InlineData("https://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] + [InlineData("git://github.com/libgit2/TestGitRepository.git")] + public void CanListRemoteReferencesFromUrl(string url) + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + IList references = repo.Network.ListReferences(url).ToList(); + + foreach (var directReference in references) + { + // None of those references point to an existing + // object in this brand new repository + Assert.Null(directReference.Target); + } + + List> actualRefs = references. + Select(directRef => new Tuple(directRef.CanonicalName, directRef.TargetIdentifier)).ToList(); + + Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); + for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + { + Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + } + } + } + [Fact] public void CanListRemoteReferenceObjects() { diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index d75488119..1cd002fcc 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -871,6 +871,13 @@ internal static extern int git_remote_create( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); + [DllImport(libgit2)] + internal static extern int git_remote_create_inmemory( + out RemoteSafeHandle remote, + RepositorySafeHandle repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refspec, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); + [DllImport(libgit2)] internal static extern void git_remote_disconnect(RemoteSafeHandle remote); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 2f113e464..970ffbbf0 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1543,6 +1543,18 @@ public static RemoteSafeHandle git_remote_create(RepositorySafeHandle repo, stri } } + public static RemoteSafeHandle git_remote_create_inmemory(RepositorySafeHandle repo, string url, string refspec) + { + using (ThreadAffinity()) + { + RemoteSafeHandle handle; + int res = NativeMethods.git_remote_create_inmemory(out handle, repo, url, refspec); + Ensure.ZeroResult(res); + + return handle; + } + } + public static void git_remote_connect(RemoteSafeHandle remote, GitDirection direction) { using (ThreadAffinity()) diff --git a/LibGit2Sharp/Network.cs b/LibGit2Sharp/Network.cs index bfc9ce985..704609b96 100644 --- a/LibGit2Sharp/Network.cs +++ b/LibGit2Sharp/Network.cs @@ -74,6 +74,57 @@ public virtual IEnumerable ListReferences(Remote remote) } } + /// + /// List references in a remote repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The url to list from. + /// The references in the remote repository. + public virtual IEnumerable ListReferences(string url) + { + Ensure.ArgumentNotNull(url, "url"); + + using (RemoteSafeHandle remoteHandle = Proxy.git_remote_create_inmemory(repository.Handle, null, url)) + { + Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); + return Proxy.git_remote_ls(repository, remoteHandle); + } + } + + static void DoFetch(RemoteSafeHandle remoteHandle, GitRemoteCallbacks gitCallbacks, TagFetchMode? tagFetchMode) + { + if (tagFetchMode.HasValue) + { + Proxy.git_remote_set_autotag(remoteHandle, tagFetchMode.Value); + } + + // It is OK to pass the reference to the GitCallbacks directly here because libgit2 makes a copy of + // the data in the git_remote_callbacks structure. If, in the future, libgit2 changes its implementation + // to store a reference to the git_remote_callbacks structure this would introduce a subtle bug + // where the managed layer could move the git_remote_callbacks to a different location in memory, + // but libgit2 would still reference the old address. + // + // Also, if GitRemoteCallbacks were a class instead of a struct, we would need to guard against + // GC occuring in between setting the remote callbacks and actual usage in one of the functions afterwords. + Proxy.git_remote_set_callbacks(remoteHandle, ref gitCallbacks); + + try + { + Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); + Proxy.git_remote_download(remoteHandle); + Proxy.git_remote_update_tips(remoteHandle); + } + finally + { + Proxy.git_remote_disconnect(remoteHandle); + } + } + /// /// Fetch from the . /// @@ -99,31 +150,39 @@ public virtual void Fetch( var callbacks = new RemoteCallbacks(onProgress, onTransferProgress, onUpdateTips, credentials); GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); - if (tagFetchMode.HasValue) - { - Proxy.git_remote_set_autotag(remoteHandle, tagFetchMode.Value); - } + DoFetch(remoteHandle, gitCallbacks, tagFetchMode); + } + } - // It is OK to pass the reference to the GitCallbacks directly here because libgit2 makes a copy of - // the data in the git_remote_callbacks structure. If, in the future, libgit2 changes its implementation - // to store a reference to the git_remote_callbacks structure this would introduce a subtle bug - // where the managed layer could move the git_remote_callbacks to a different location in memory, - // but libgit2 would still reference the old address. - // - // Also, if GitRemoteCallbacks were a class instead of a struct, we would need to guard against - // GC occuring in between setting the remote callbacks and actual usage in one of the functions afterwords. - Proxy.git_remote_set_callbacks(remoteHandle, ref gitCallbacks); + /// + /// Fetch from a url with a set of fetch refspecs + /// + /// The url to fetch from + /// The list of resfpecs to use + /// Optional parameter indicating what tags to download. + /// Progress callback. Corresponds to libgit2 progress callback. + /// UpdateTips callback. Corresponds to libgit2 update_tips callback. + /// Callback method that transfer progress will be reported through. + /// Reports the client's state regarding the received and processed (bytes, objects) from the server. + /// Credentials to use for username/password authentication. + public virtual void Fetch( + string url, + IEnumerable refspecs, + TagFetchMode? tagFetchMode = null, + ProgressHandler onProgress = null, + UpdateTipsHandler onUpdateTips = null, + TransferProgressHandler onTransferProgress = null, + Credentials credentials = null) + { + Ensure.ArgumentNotNull(url, "url"); - try - { - Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); - Proxy.git_remote_download(remoteHandle); - Proxy.git_remote_update_tips(remoteHandle); - } - finally - { - Proxy.git_remote_disconnect(remoteHandle); - } + using (RemoteSafeHandle remoteHandle = Proxy.git_remote_create_inmemory(repository.Handle, null, url)) + { + Proxy.git_remote_set_fetch_refspecs(remoteHandle, refspecs); + var callbacks = new RemoteCallbacks(onProgress, onTransferProgress, onUpdateTips, credentials); + GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); + + DoFetch(remoteHandle, gitCallbacks, tagFetchMode); } }