Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.Horde/Commits/ICommitCollection.cs
2025-05-18 13:04:45 +08:00

145 lines
7.2 KiB
C#

// 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
{
/// <summary>
/// Represents errors that occur during commit collection operations.
/// This exception is thrown when operations on a <see cref="ICommitCollection"/> fail.
///
/// Serves as a wrapper for underlying VCS-specific exceptions, providing consistent errors for each VCS implementation
/// </summary>
public class CommitCollectionException(string? message, Exception? innerException) : Exception(message, innerException);
/// <summary>
/// VCS abstraction. Provides information about commits to a particular stream.
/// </summary>
public interface ICommitCollection
{
/// <summary>
/// Creates a new change
/// </summary>
/// <param name="path">Path to modify in the change</param>
/// <param name="description">Description of the change</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>New commit information</returns>
Task<CommitIdWithOrder> CreateNewAsync(string path, string description, CancellationToken cancellationToken = default);
/// <summary>
/// Gets a commit by id
/// </summary>
/// <param name="commitId">Commit to query</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>Commit details</returns>
Task<ICommit> GetAsync(CommitId commitId, CancellationToken cancellationToken = default);
/// <summary>
/// Gets an ordered commit id
/// </summary>
/// <param name="commitId">The commit to query</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>Numbered commit id</returns>
ValueTask<CommitIdWithOrder> GetOrderedAsync(CommitId commitId, CancellationToken cancellationToken = default);
/// <summary>
/// Finds changes submitted to a stream, in reverse order.
/// </summary>
/// <param name="minCommitId">The minimum changelist number</param>
/// <param name="includeMinCommit">Whether to include the minimum changelist in the range of enumerated responses</param>
/// <param name="maxCommitId">The maximum changelist number</param>
/// <param name="includeMaxCommit">Whether to include the maximum changelist in the range of enumerated responses</param>
/// <param name="maxResults">Maximum number of results to return</param>
/// <param name="tags">Tags for the commits to return</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>Changelist information</returns>
IAsyncEnumerable<ICommit> FindAsync(CommitId? minCommitId = null, bool includeMinCommit = true, CommitId? maxCommitId = null, bool includeMaxCommit = true, int? maxResults = null, IReadOnlyList<CommitTag>? tags = null, CancellationToken cancellationToken = default);
/// <summary>
/// Subscribes to changes from this commit source
/// </summary>
/// <param name="minCommitId">Minimum changelist number (exclusive)</param>
/// <param name="tags">Tags for the commit to return</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>New change information</returns>
IAsyncEnumerable<ICommit> SubscribeAsync(CommitId minCommitId, IReadOnlyList<CommitTag>? tags = null, CancellationToken cancellationToken = default);
}
/// <summary>
/// Extension methods for <see cref="ICommitCollection"/>
/// </summary>
public static class CommitCollectionExtensions
{
/// <summary>
/// Creates a new change for a template
/// </summary>
/// <param name="commitCollection">The Perforce service instance</param>
/// <param name="template">The template being built</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>New changelist number</returns>
public static Task<CommitIdWithOrder> 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);
}
/// <summary>
/// Finds changes submitted to a stream, in reverse order.
/// </summary>
/// <param name="commitCollection">Collection to operate on</param>
/// <param name="minCommitId">The minimum changelist number</param>
/// <param name="maxCommitId">The maximum changelist number</param>
/// <param name="maxResults">Maximum number of results to return</param>
/// <param name="tags">Tags for the commits to return</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>Changelist information</returns>
public static IAsyncEnumerable<ICommit> FindAsync(this ICommitCollection commitCollection, CommitId? minCommitId, CommitId? maxCommitId, int? maxResults = null, IReadOnlyList<CommitTag>? tags = null, CancellationToken cancellationToken = default)
=> commitCollection.FindAsync(minCommitId: minCommitId, maxCommitId: maxCommitId, maxResults: maxResults, tags: tags, cancellationToken: cancellationToken);
/// <summary>
/// Gets the last code code equal or before the given change number
/// </summary>
/// <param name="commitCollection">The commit source to query</param>
/// <param name="maxCommitId">Maximum code change to query</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>The last code change</returns>
public static async ValueTask<ICommit?> 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);
}
/// <summary>
/// Finds the latest commit from a source
/// </summary>
/// <param name="commitCollection">The commit source to query</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>The latest commit</returns>
public static async Task<ICommit> 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;
}
/// <summary>
/// Finds the latest commit from a source
/// </summary>
/// <param name="commitCollection">The commit source to query</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>The latest commit</returns>
public static async Task<CommitIdWithOrder> GetLastCommitIdAsync(this ICommitCollection commitCollection, CancellationToken cancellationToken = default)
{
ICommit commit = await GetLatestAsync(commitCollection, cancellationToken);
return commit.Id;
}
}
}