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;
}
}
}
}