using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using LibGit2Sharp.Core; 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)); } /// /// Lookup and manage remotes in the repository. /// public virtual RemoteCollection Remotes { get { return remotes.Value; } } /// /// List references in a 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 to list from. /// The references in the repository. public virtual IEnumerable ListReferences(Remote remote) { Ensure.ArgumentNotNull(remote, "remote"); using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) { Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); return Proxy.git_remote_ls(repository, remoteHandle); } } /// /// 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_anonymous(repository.Handle, url, null)) { Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); return Proxy.git_remote_ls(repository, remoteHandle); } } static void DoFetch(RemoteSafeHandle remoteHandle, FetchOptions options, Signature signature, string logMessage) { if (options == null) { options = new FetchOptions(); } if (options.TagFetchMode.HasValue) { Proxy.git_remote_set_autotag(remoteHandle, options.TagFetchMode.Value); } var callbacks = new RemoteCallbacks(options); GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); // 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); Proxy.git_remote_fetch(remoteHandle, signature, logMessage); } /// /// Fetch from the . /// /// The remote to fetch /// controlling fetch behavior /// Identity for use when updating the reflog. /// Message to use when updating the reflog. public virtual void Fetch(Remote remote, FetchOptions options = null, Signature signature = null, string logMessage = null) { Ensure.ArgumentNotNull(remote, "remote"); using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) { DoFetch(remoteHandle, options, signature.OrDefault(repository.Config), logMessage); } } /// /// Fetch from the , using custom refspecs. /// /// The remote to fetch /// Refspecs to use, replacing the remote's fetch refspecs /// controlling fetch behavior /// Identity for use when updating the reflog. /// Message to use when updating the reflog. public virtual void Fetch(Remote remote, IEnumerable refspecs, FetchOptions options = null, Signature signature = null, string logMessage = null) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(refspecs, "refspecs"); using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) { Proxy.git_remote_set_fetch_refspecs(remoteHandle, refspecs); DoFetch(remoteHandle, options, signature.OrDefault(repository.Config), logMessage); } } /// /// Fetch from a url with a set of fetch refspecs /// /// The url to fetch from /// The list of resfpecs to use /// controlling fetch behavior /// Identity for use when updating the reflog. /// Message to use when updating the reflog. public virtual void Fetch( string url, IEnumerable refspecs, FetchOptions options = null, Signature signature = null, string logMessage = null) { Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(refspecs, "refspecs"); using (RemoteSafeHandle remoteHandle = Proxy.git_remote_create_anonymous(repository.Handle, url, null)) { Proxy.git_remote_set_fetch_refspecs(remoteHandle, refspecs); DoFetch(remoteHandle, options, signature.OrDefault(repository.Config), logMessage); } } /// /// 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. /// controlling push behavior /// Identity for use when updating the reflog. /// Message to use when updating the reflog. public virtual void Push( Remote remote, string objectish, string destinationSpec, PushOptions pushOptions = null, Signature signature = null, string logMessage = null) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(objectish, "objectish"); Ensure.ArgumentNotNullOrEmptyString(destinationSpec, destinationSpec); Push(remote, string.Format(CultureInfo.InvariantCulture, "{0}:{1}", objectish, destinationSpec), pushOptions, signature, logMessage); } /// /// Push specified reference to the . /// /// The to push to. /// The pushRefSpec to push. /// controlling push behavior /// Identity for use when updating the reflog. /// Message to use when updating the reflog. public virtual void Push( Remote remote, string pushRefSpec, PushOptions pushOptions = null, Signature signature = null, string logMessage = null) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNullOrEmptyString(pushRefSpec, "pushRefSpec"); Push(remote, new[] { pushRefSpec }, pushOptions, signature, logMessage); } /// /// Push specified references to the . /// /// The to push to. /// The pushRefSpecs to push. /// controlling push behavior /// Identity for use when updating the reflog. /// Message to use when updating the reflog. public virtual void Push( Remote remote, IEnumerable pushRefSpecs, PushOptions pushOptions = null, Signature signature = null, string logMessage = null) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs"); // The following local variables are protected from garbage collection // by a GC.KeepAlive call at the end of the method. Otherwise, // random crashes during push progress reporting could occur. PushTransferCallbacks pushTransferCallbacks; PackbuilderCallbacks packBuilderCallbacks; NativeMethods.git_push_transfer_progress pushProgress; NativeMethods.git_packbuilder_progress packBuilderProgress; // Return early if there is nothing to push. if (!pushRefSpecs.Any()) { return; } if (pushOptions == null) { pushOptions = new PushOptions(); } PushCallbacks pushStatusUpdates = new PushCallbacks(pushOptions.OnPushStatusError); // Load the remote. using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) { var callbacks = new RemoteCallbacks(null, null, null, pushOptions.Credentials); GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); Proxy.git_remote_set_callbacks(remoteHandle, ref gitCallbacks); try { Proxy.git_remote_connect(remoteHandle, GitDirection.Push); // Perform the actual push. using (PushSafeHandle pushHandle = Proxy.git_push_new(remoteHandle)) { pushTransferCallbacks = new PushTransferCallbacks(pushOptions.OnPushTransferProgress); packBuilderCallbacks = new PackbuilderCallbacks(pushOptions.OnPackBuilderProgress); pushProgress = pushTransferCallbacks.GenerateCallback(); packBuilderProgress = packBuilderCallbacks.GenerateCallback(); Proxy.git_push_set_callbacks(pushHandle, pushProgress, packBuilderProgress); // Set push options. Proxy.git_push_set_options(pushHandle, new GitPushOptions() { PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism }); // 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, signature.OrDefault(repository.Config), logMessage); } } finally { Proxy.git_remote_disconnect(remoteHandle); } } GC.KeepAlive(pushProgress); GC.KeepAlive(packBuilderProgress); GC.KeepAlive(pushTransferCallbacks); GC.KeepAlive(packBuilderCallbacks); } /// /// Pull changes from the configured upstream remote and branch into the branch pointed at by HEAD. /// /// If the merge is a non-fast forward merge that generates a merge commit, the of who made the merge. /// Specifies optional parameters controlling merge behavior of pull; if null, the defaults are used. public virtual MergeResult Pull(Signature merger, PullOptions options) { Ensure.ArgumentNotNull(merger, "merger"); Ensure.ArgumentNotNull(options, "options"); Branch currentBranch = repository.Head; if(!currentBranch.IsTracking) { throw new LibGit2SharpException("There is no tracking information for the current branch."); } if (currentBranch.Remote == null) { throw new LibGit2SharpException("No upstream remote for the current branch."); } Fetch(currentBranch.Remote, options.FetchOptions); return repository.MergeFetchHeads(merger, options.MergeOptions); } /// /// The heads that have been updated during the last fetch. /// internal 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++)); } } /// /// 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 = LaxUtf8Marshaler.FromNative(referenceNamePtr); string msg = LaxUtf8Marshaler.FromNative(msgPtr); onError(new PushStatusError(referenceName, msg)); } return 0; } } } }