Editor/git/models/Branch.cs
using System.Text.RegularExpressions;

namespace Sandbox.git.models;

/// <summary>
/// A branch as loaded from Git.
/// </summary>
public class Branch {
	public string Name { get; }
	public string Upstream { get; }
	public IBranchTip Tip { get; }
	public BranchType Type { get; }
	public string Ref { get; }

	/// <summary>
	/// A branch as loaded from Git.
	/// </summary>
	/// <param name="name">The short name of the branch, e.g. main.</param>
	/// <param name="upstream">The remote-prefixed upstream name, e.g. origin/main.</param>
	/// <param name="tip">Basic information (sha) of the latest commit on the branch.</param>
	/// <param name="type">The type of branch, local or remote.</param>
	/// <param name="refName">The canonical ref of the branch.</param>
	public Branch(
		string name,
		string upstream,
		IBranchTip tip,
		BranchType type,
		string refName) {
		Name = name ?? string.Empty;
		Upstream = upstream;
		Tip = tip;
		Type = type;
		Ref = refName ?? string.Empty;
	}

	/// <summary>
	/// The name of the upstream's remote.
	/// </summary>
	public string UpstreamRemoteName {
		get {
			if ( string.IsNullOrEmpty(Upstream) )
				return null;

			var match = Regex.Match(Upstream, @"(.*?)/.*");
			if ( !match.Success || match.Groups.Count < 2 )
				return null;

			return match.Groups[1].Value;
		}
	}

	/// <summary>
	/// The name of remote for a remote branch. If local, returns null.
	/// </summary>
	public string RemoteName {
		get {
			if ( Type == BranchType.Local )
				return null;

			var match = Regex.Match(Ref, @"^refs/remotes/(.*?)/.*");
			if ( !match.Success || match.Groups.Count != 2 )
				throw new System.Exception($"Remote branch ref has unexpected format: {Ref}");

			return match.Groups[1].Value;
		}
	}

	/// <summary>
	/// The name of the branch's upstream without the remote prefix.
	/// </summary>
	public string UpstreamWithoutRemote {
		get {
			if ( string.IsNullOrEmpty(Upstream) )
				return null;
			return RemoteHelpers.RemoveRemotePrefix(Upstream);
		}
	}

	/// <summary>
	/// The name of the branch without the remote prefix. For local branches, same as Name.
	/// </summary>
	public string NameWithoutRemote {
		get {
			if ( Type == BranchType.Local )
				return Name;

			var withoutRemote = RemoteHelpers.RemoveRemotePrefix(Name);
			return !string.IsNullOrEmpty(withoutRemote) ? withoutRemote : Name;
		}
	}

	/// <summary>
	/// True if this is a remote branch from one of Desktop's fork remotes (github-desktop-*).
	/// These are hidden in the UI as plumbing.
	/// </summary>
	public bool IsDesktopForkRemoteBranch =>
		Type == BranchType.Remote && Name.StartsWith(RemoteHelpers.ForkedRemotePrefix);
}