// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EpicGames.Horde.Jobs.Templates;
namespace EpicGames.Horde.Commits
{
///
/// Represents errors that occur during commit collection operations.
/// This exception is thrown when operations on a fail.
///
/// Serves as a wrapper for underlying VCS-specific exceptions, providing consistent errors for each VCS implementation
///
public class CommitCollectionException(string? message, Exception? innerException) : Exception(message, innerException);
///
/// VCS abstraction. Provides information about commits to a particular stream.
///
public interface ICommitCollection
{
///
/// Creates a new change
///
/// Path to modify in the change
/// Description of the change
/// Cancellation token for the operation
/// New commit information
Task CreateNewAsync(string path, string description, CancellationToken cancellationToken = default);
///
/// Gets a commit by id
///
/// Commit to query
/// Cancellation token for the operation
/// Commit details
Task GetAsync(CommitId commitId, CancellationToken cancellationToken = default);
///
/// Gets an ordered commit id
///
/// The commit to query
/// Cancellation token for the operation
/// Numbered commit id
ValueTask GetOrderedAsync(CommitId commitId, CancellationToken cancellationToken = default);
///
/// Finds changes submitted to a stream, in reverse order.
///
/// The minimum changelist number
/// Whether to include the minimum changelist in the range of enumerated responses
/// The maximum changelist number
/// Whether to include the maximum changelist in the range of enumerated responses
/// Maximum number of results to return
/// Tags for the commits to return
/// Cancellation token for the operation
/// Changelist information
IAsyncEnumerable FindAsync(CommitId? minCommitId = null, bool includeMinCommit = true, CommitId? maxCommitId = null, bool includeMaxCommit = true, int? maxResults = null, IReadOnlyList? tags = null, CancellationToken cancellationToken = default);
///
/// Subscribes to changes from this commit source
///
/// Minimum changelist number (exclusive)
/// Tags for the commit to return
/// Cancellation token for the operation
/// New change information
IAsyncEnumerable SubscribeAsync(CommitId minCommitId, IReadOnlyList? tags = null, CancellationToken cancellationToken = default);
}
///
/// Extension methods for
///
public static class CommitCollectionExtensions
{
///
/// Creates a new change for a template
///
/// The Perforce service instance
/// The template being built
/// Cancellation token for the operation
/// New changelist number
public static Task CreateNewAsync(this ICommitCollection commitCollection, ITemplate template, CancellationToken cancellationToken)
{
string description = (template.SubmitDescription ?? "[Horde] New change for $(TemplateName)").Replace("$(TemplateName)", template.Name, StringComparison.OrdinalIgnoreCase);
return commitCollection.CreateNewAsync(template.SubmitNewChange!, description, cancellationToken);
}
///
/// Finds changes submitted to a stream, in reverse order.
///
/// Collection to operate on
/// The minimum changelist number
/// The maximum changelist number
/// Maximum number of results to return
/// Tags for the commits to return
/// Cancellation token for the operation
/// Changelist information
public static IAsyncEnumerable FindAsync(this ICommitCollection commitCollection, CommitId? minCommitId, CommitId? maxCommitId, int? maxResults = null, IReadOnlyList? tags = null, CancellationToken cancellationToken = default)
=> commitCollection.FindAsync(minCommitId: minCommitId, maxCommitId: maxCommitId, maxResults: maxResults, tags: tags, cancellationToken: cancellationToken);
///
/// Gets the last code code equal or before the given change number
///
/// The commit source to query
/// Maximum code change to query
/// Cancellation token for the operation
/// The last code change
public static async ValueTask GetLastCodeChangeAsync(this ICommitCollection commitCollection, CommitId? maxCommitId, CancellationToken cancellationToken = default)
{
return await commitCollection.FindAsync(minCommitId: null, maxCommitId: maxCommitId, maxResults: 1, tags: new[] { CommitTag.Code }, cancellationToken: cancellationToken).FirstOrDefaultAsync(cancellationToken);
}
///
/// Finds the latest commit from a source
///
/// The commit source to query
/// Cancellation token for the operation
/// The latest commit
public static async Task GetLatestAsync(this ICommitCollection commitCollection, CancellationToken cancellationToken = default)
{
ICommit? commit = await commitCollection.FindAsync(null, null, maxResults: 1, cancellationToken: cancellationToken).FirstOrDefaultAsync(cancellationToken);
if (commit == null)
{
throw new Exception("No changes found for stream.");
}
return commit;
}
///
/// Finds the latest commit from a source
///
/// The commit source to query
/// Cancellation token for the operation
/// The latest commit
public static async Task GetLastCommitIdAsync(this ICommitCollection commitCollection, CancellationToken cancellationToken = default)
{
ICommit commit = await GetLatestAsync(commitCollection, cancellationToken);
return commit.Id;
}
}
}