using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// /// Provides access to network functionality for a repository. /// public class Network { private readonly Repository repository; private readonly Lazy remotes; /// /// Needed for mocking purposes. /// protected Network() { } internal Network(Repository repository) { this.repository = repository; remotes = new Lazy(() => new RemoteCollection(repository)); } /// /// The heads that have been updated during the last fetch. /// public virtual IEnumerable FetchHeads { get { int i = 0; return Proxy.git_repository_fetchhead_foreach( repository.Handle, (name, url, oid, isMerge) => new FetchHead(repository, name, url, oid, isMerge, i++)); } } /// /// Lookup and manage remotes in the repository. /// public virtual RemoteCollection Remotes { get { return remotes.Value; } } /// /// List references in a repository. /// /// The to list from. /// The references in the repository. public virtual IEnumerable ListReferences(Remote remote) { Ensure.ArgumentNotNull(remote, "remote"); List directReferences = new List(); using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) { Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); NativeMethods.git_headlist_cb cb = (ref GitRemoteHead remoteHead, IntPtr payload) => { // The name pointer should never be null - if it is, // this indicates a bug somewhere (libgit2, server, etc). if (remoteHead.NamePtr == IntPtr.Zero) { Proxy.giterr_set_str(GitErrorCategory.Invalid, "Not expecting null value for reference name."); return -1; } ObjectId oid = remoteHead.Oid; string name = Utf8Marshaler.FromNative(remoteHead.NamePtr); directReferences.Add(new DirectReference(name, this.repository, oid)); return 0; }; Proxy.git_remote_ls(remoteHandle, cb); } return directReferences; } /// /// Fetch from the . /// /// The remote to fetch /// Optional parameter indicating what tags to download. /// Progress callback. Corresponds to libgit2 progress callback. /// Completion callback. Corresponds to libgit2 completion 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( Remote remote, TagFetchMode tagFetchMode = TagFetchMode.Auto, ProgressHandler onProgress = null, CompletionHandler onCompletion = null, UpdateTipsHandler onUpdateTips = null, TransferProgressHandler onTransferProgress = null, Credentials credentials = null) { Ensure.ArgumentNotNull(remote, "remote"); // We need to keep a reference to the git_cred_acquire_cb callback around // so it will not be garbage collected before we are done with it. // Note that we also have a GC.KeepAlive call at the end of the method. NativeMethods.git_cred_acquire_cb credentialCallback = null; using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) { var callbacks = new RemoteCallbacks(onProgress, onCompletion, onUpdateTips); GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); Proxy.git_remote_set_autotag(remoteHandle, tagFetchMode); if (credentials != null) { credentialCallback = (out IntPtr cred, IntPtr url, IntPtr username_from_url, uint types, IntPtr payload) => NativeMethods.git_cred_userpass_plaintext_new(out cred, credentials.Username, credentials.Password); Proxy.git_remote_set_cred_acquire_cb( remoteHandle, credentialCallback, IntPtr.Zero); } // 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, onTransferProgress); Proxy.git_remote_update_tips(remoteHandle); } finally { Proxy.git_remote_disconnect(remoteHandle); } } // To be safe, make sure the credential callback is kept until // alive until at least this point. GC.KeepAlive(credentialCallback); } /// /// Push the objectish to the destination reference on the . /// /// The to push to. /// The source objectish to push. /// The reference to update on the remote. /// Handler for reporting failed push updates. /// Credentials to use for user/pass authentication public virtual void Push( Remote remote, string objectish, string destinationSpec, PushStatusErrorHandler onPushStatusError, Credentials credentials = null) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(objectish, "objectish"); Ensure.ArgumentNotNullOrEmptyString(destinationSpec, destinationSpec); Push(remote, string.Format(CultureInfo.InvariantCulture, "{0}:{1}", objectish, destinationSpec), onPushStatusError, credentials); } /// /// Push specified reference to the . /// /// The to push to. /// The pushRefSpec to push. /// Handler for reporting failed push updates. /// Credentials to use for user/pass authentication public virtual void Push( Remote remote, string pushRefSpec, PushStatusErrorHandler onPushStatusError, Credentials credentials = null) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNullOrEmptyString(pushRefSpec, "pushRefSpec"); Push(remote, new string[] { pushRefSpec }, onPushStatusError, credentials); } /// /// Push specified references to the . /// /// The to push to. /// The pushRefSpecs to push. /// Handler for reporting failed push updates. /// Credentials to use for user/pass authentication public virtual void Push( Remote remote, IEnumerable pushRefSpecs, PushStatusErrorHandler onPushStatusError, Credentials credentials = null) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs"); // We need to keep a reference to the git_cred_acquire_cb callback around // so it will not be garbage collected before we are done with it. // Note that we also have a GC.KeepAlive call at the end of the method. NativeMethods.git_cred_acquire_cb credentialCallback = null; // Return early if there is nothing to push. if (!pushRefSpecs.Any()) { return; } PushCallbacks pushStatusUpdates = new PushCallbacks(onPushStatusError); // Load the remote. using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) { if (credentials != null) { credentialCallback = (out IntPtr cred, IntPtr url, IntPtr username_from_url, uint types, IntPtr payload) => NativeMethods.git_cred_userpass_plaintext_new(out cred, credentials.Username, credentials.Password); Proxy.git_remote_set_cred_acquire_cb( remoteHandle, credentialCallback, IntPtr.Zero); } try { Proxy.git_remote_connect(remoteHandle, GitDirection.Push); // Perform the actual push. using (PushSafeHandle pushHandle = Proxy.git_push_new(remoteHandle)) { // Add refspecs. foreach (string pushRefSpec in pushRefSpecs) { Proxy.git_push_add_refspec(pushHandle, pushRefSpec); } Proxy.git_push_finish(pushHandle); if (!Proxy.git_push_unpack_ok(pushHandle)) { throw new LibGit2SharpException("Push failed - remote did not successfully unpack."); } Proxy.git_push_status_foreach(pushHandle, pushStatusUpdates.Callback); Proxy.git_push_update_tips(pushHandle); } } finally { Proxy.git_remote_disconnect(remoteHandle); } } // To be safe, make sure the credential callback is kept // alive until at least this point. GC.KeepAlive(credentialCallback); } /// /// Helper class to handle callbacks during push. /// private class PushCallbacks { readonly PushStatusErrorHandler onError; public PushCallbacks(PushStatusErrorHandler onError) { this.onError = onError; } public int Callback(IntPtr referenceNamePtr, IntPtr msgPtr, IntPtr payload) { // Exit early if there is no callback. if (onError == null) { return 0; } // The reference name pointer should never be null - if it is, // this indicates a bug somewhere (libgit2, server, etc). if (referenceNamePtr == IntPtr.Zero) { Proxy.giterr_set_str(GitErrorCategory.Invalid, "Not expecting null for reference name in push status."); return -1; } // Only report updates where there is a message - indicating // that there was an error. if (msgPtr != IntPtr.Zero) { string referenceName = Utf8Marshaler.FromNative(referenceNamePtr); string msg = Utf8Marshaler.FromNative(msgPtr); onError(new PushStatusError(referenceName, msg)); } return 0; } } } }