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