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