using System;
using System.Globalization;
using LibGit2Sharp.Core;
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
{
if (this is DetachedHead)
{
return repo.Head.Reference.TargetIdentifier == this.Reference.TargetIdentifier;
}
return repo.Head.Reference.TargetIdentifier == this.CanonicalName;
}
}
///
/// 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 CommitFilter { IncludeReachableFrom = 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)
{
using (var remote = repo.Network.Remotes.RemoteForName(RemoteName))
{
return remote.FetchSpecTransformToSource(CanonicalName);
}
}
return UpstreamBranchCanonicalNameFromLocalBranch();
}
}
///
/// Get the name of the remote for the branch.
///
/// If this is a local branch, this will return the configured
/// to fetch from and push to. If this is a
/// remote-tracking branch, this will return the name of the remote
/// containing the tracked branch. If there no tracking information
/// this will return null.
///
///
public virtual string RemoteName
{
get
{
return IsRemote
? RemoteNameFromRemoteTrackingBranch()
: RemoteNameFromLocalBranch();
}
}
private string UpstreamBranchCanonicalNameFromLocalBranch()
{
ConfigurationEntry mergeRefEntry = repo.Config.Get("branch", FriendlyName, "merge");
if (mergeRefEntry == null)
{
return null;
}
return mergeRefEntry.Value;
}
private string RemoteNameFromLocalBranch()
{
ConfigurationEntry remoteEntry = repo.Config.Get("branch", FriendlyName, "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, false);
}
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(repo, 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));
}
}
}