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