Editor/git/CheckoutIndex.cs
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Sandbox.git.models;

namespace Sandbox.git;

/// <summary>
/// Updates the working directory from the index for a given set of files (git checkout-index).
/// </summary>
public static class CheckoutIndex {
	const string OperationCheckoutIndex = "checkoutIndex";

	static readonly HashSet<int> SuccessExitCodes = new() { 0, 1 };

	/// <summary>
	/// Forcefully updates the working directory with information from the index
	/// for a given set of files.
	/// </summary>
	/// <remarks>
	/// This is essentially the same as running <c>git checkout -- files</c>
	/// except by using <c>checkout-index</c> we pass the files on stdin, avoiding
	/// issues with too long command lines.
	/// Does not throw for paths that do not exist in the index (-q).
	/// </remarks>
	/// <param name="repository">The repository in which to update the working directory from the index.</param>
	/// <param name="paths">Relative paths in the working directory to update from the index.</param>
	public static async Task CheckoutIndexAsync(Repository repository, IReadOnlyList<string> paths) {
		if ( repository == null )
			throw new ArgumentNullException(nameof(repository));
		if ( paths == null )
			throw new ArgumentNullException(nameof(paths));

		if ( paths.Count == 0 )
			return;

		var stdin = BuildCheckoutIndexStdin(paths);
		var args = GetCheckoutIndexArgs();

		await Core.GitAsync(
			args,
			repository.Path,
			OperationCheckoutIndex,
			SuccessExitCodes,
			stdin
		).ConfigureAwait(false);
	}

	/// <summary>Builds git arguments for checkout-index. Exposed for testing.</summary>
	public static string[] GetCheckoutIndexArgs() {
		return new[] { "checkout-index", "-f", "-u", "-q", "--stdin", "-z" };
	}

	/// <summary>Builds stdin for checkout-index (null-separated paths). Exposed for testing.</summary>
	public static string BuildCheckoutIndexStdin(IReadOnlyList<string> paths) {
		if ( paths == null || paths.Count == 0 )
			return string.Empty;
		return string.Join("\0", paths);
	}
}