using System;
using System.Globalization;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Compat;
using LibGit2Sharp.Handlers;
namespace LibGit2Sharp
{
///
/// A branch is a special kind of reference
///
public class Branch : ReferenceWrapper
{
private readonly Lazy trackedBranch;
///
/// Needed for mocking purposes.
///
protected Branch()
{ }
///
/// Initializes a new instance of the class.
///
/// The repo.
/// The reference.
/// The full name of the reference
internal Branch(Repository repo, Reference reference, string canonicalName)
: this(repo, reference, _ => canonicalName)
{
}
///
/// Initializes a new instance of an orphaned class.
///
/// This instance will point to no commit.
///
///
/// The repo.
/// The reference.
internal Branch(Repository repo, Reference reference)
: this(repo, reference, r => r.TargetIdentifier)
{
}
private Branch(Repository repo, Reference reference, Func canonicalNameSelector)
: base(repo, reference, canonicalNameSelector)
{
trackedBranch = new Lazy(ResolveTrackedBranch);
}
///
/// Gets the pointed at by the in the .
///
/// The relative path to the from the working directory.
/// null if nothing has been found, the otherwise.
public virtual TreeEntry this[string relativePath]
{
get
{
if (Tip == null)
{
return null;
}
return Tip[relativePath];
}
}
///
/// Gets a value indicating whether this instance is a remote.
///
///
/// true if this instance is remote; otherwise, false.
///
public virtual bool IsRemote
{
get { return IsRemoteBranch(CanonicalName); }
}
///
/// Gets the remote branch which is connected to this local one, or null if there is none.
///
public virtual Branch TrackedBranch
{
get { return trackedBranch.Value; }
}
///
/// Determines if this local branch is connected to a remote one.
///
public virtual bool IsTracking
{
get { return TrackedBranch != null; }
}
///
/// Gets additional information about the tracked branch.
///
public virtual BranchTrackingDetails TrackingDetails
{
get { return new BranchTrackingDetails(repo, this); }
}
///
/// Gets a value indicating whether this instance is current branch (HEAD) in the repository.
///
///
/// true if this instance is the current branch; otherwise, false.
///
public virtual bool IsCurrentRepositoryHead
{
get { return repo.Head == this; }
}
///
/// Gets the that this branch points to.
///
public virtual Commit Tip
{
get { return TargetObject; }
}
///
/// Gets the commits on this branch. (Starts walking from the References's target).
///
public virtual ICommitLog Commits
{
get { return repo.Commits.QueryBy(new Filter { Since = this }); }
}
///
/// Gets the configured canonical name of the upstream branch.
///
/// This is the upstream reference to which this branch will be pushed.
/// It corresponds to the "branch.branch_name.merge" property of the config file.
///
///
public virtual string UpstreamBranchCanonicalName
{
get
{
if (IsRemote)
{
return Remote.FetchSpecTransformToSource(CanonicalName);
}
return UpstreamBranchCanonicalNameFromLocalBranch();
}
}
///
/// Gets the configured to fetch from and push to.
///
public virtual Remote Remote
{
get
{
string remoteName;
if (IsRemote)
{
remoteName = RemoteNameFromRemoteTrackingBranch();
}
else
{
remoteName = RemoteNameFromLocalBranch();
if (remoteName == null)
{
return null;
}
}
return repo.Network.Remotes[remoteName];
}
}
private string UpstreamBranchCanonicalNameFromLocalBranch()
{
ConfigurationEntry mergeRefEntry = repo.Config.Get("branch", Name, "merge");
if (mergeRefEntry == null)
{
return null;
}
return mergeRefEntry.Value;
}
private string RemoteNameFromLocalBranch()
{
ConfigurationEntry remoteEntry = repo.Config.Get("branch", Name, "remote");
if (remoteEntry == null)
{
return null;
}
string remoteName = remoteEntry.Value;
if (string.IsNullOrEmpty(remoteName) ||
string.Equals(remoteName, ".", StringComparison.Ordinal))
{
return null;
}
return remoteName;
}
private string RemoteNameFromRemoteTrackingBranch()
{
return Proxy.git_branch_remote_name(repo.Handle, CanonicalName);
}
///
/// Checkout the tip commit of this object.
/// If this commit is the current tip of the branch, will checkout
/// the named branch. Otherwise, will checkout the tip commit as a
/// detached HEAD.
///
public virtual void Checkout()
{
repo.Checkout(this);
}
///
/// Checkout the tip commit of this object
/// with a callback for progress reporting. If this commit is the
/// current tip of the branch, will checkout the named branch. Otherwise,
/// will checkout the tip commit as a detached HEAD.
///
/// Options controlling checkout behavior.
/// Callback method to report checkout progress updates through.
public virtual void Checkout(CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress)
{
repo.Checkout(this, checkoutOptions, onCheckoutProgress);
}
private Branch ResolveTrackedBranch()
{
if (IsRemote)
{
return null;
}
string trackedReferenceName = Proxy.git_branch_upstream_name(repo.Handle, CanonicalName);
if (trackedReferenceName == null)
{
return null;
}
Branch branch = repo.Branches[trackedReferenceName];
if (branch != null)
{
return branch;
}
return new Branch(repo, new VoidReference(trackedReferenceName), trackedReferenceName);
}
private static bool IsRemoteBranch(string canonicalName)
{
return canonicalName.LooksLikeRemoteTrackingBranch();
}
///
/// Removes redundent leading namespaces (regarding the kind of
/// reference being wrapped) from the canonical name.
///
/// The friendly shortened name
protected override string Shorten()
{
if (CanonicalName.LooksLikeLocalBranch())
{
return CanonicalName.Substring(Reference.LocalBranchPrefix.Length);
}
if (CanonicalName.LooksLikeRemoteTrackingBranch())
{
return CanonicalName.Substring(Reference.RemoteTrackingBranchPrefix.Length);
}
throw new ArgumentException(
string.Format(CultureInfo.InvariantCulture,
"'{0}' does not look like a valid branch name.", CanonicalName));
}
}
}