Skip to content

Commit

Permalink
Allow targeting a specific branch in VMR code flow (#3331)
Browse files Browse the repository at this point in the history
  • Loading branch information
premun authored Feb 22, 2024
1 parent bb872fc commit d0a1879
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 105 deletions.
6 changes: 6 additions & 0 deletions src/Microsoft.DotNet.Darc/DarcLib/Helpers/GitRepoUrlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

#nullable enable
namespace Microsoft.DotNet.DarcLib.Helpers;
Expand Down Expand Up @@ -63,6 +66,9 @@ public static int OrderByLocalPublicOther(GitRepoType first, GitRepoType second)
return 1;
}

public static IEnumerable<string> OrderRemotesByLocalPublicOther(this IEnumerable<string> uris)
=> uris.OrderBy(ParseTypeFromUri, Comparer<GitRepoType>.Create(OrderByLocalPublicOther));

public static (string RepoName, string Org) GetRepoNameAndOwner(string uri)
{
var repoType = ParseTypeFromUri(uri);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.DotNet.DarcLib.VirtualMonoRepo;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.Json;
using Microsoft.DotNet.DarcLib.VirtualMonoRepo;

#nullable enable
namespace Microsoft.DotNet.Darc.Models.VirtualMonoRepo;
Expand All @@ -22,6 +23,8 @@ public interface ISourceManifest
void UpdateSubmodule(SubmoduleRecord submodule);
void UpdateVersion(string repository, string uri, string sha, string? packageVersion);
VmrDependencyVersion? GetVersion(string repository);
bool TryGetRepoVersion(string mappingName, [NotNullWhen(true)] out ISourceComponent? mapping);
ISourceComponent GetRepoVersion(string mappingName);
void Refresh(string sourceManifestPath);
}

Expand Down Expand Up @@ -122,11 +125,23 @@ public void Refresh(string sourceManifestPath)
_submodules = newManifst._submodules;
}

public bool TryGetRepoVersion(string mappingName, [NotNullWhen(true)] out ISourceComponent? version)
{
version = Repositories.FirstOrDefault(m => m.Path.Equals(mappingName, StringComparison.InvariantCultureIgnoreCase));
version ??= Submodules.FirstOrDefault(m => m.Path.Equals(mappingName, StringComparison.InvariantCultureIgnoreCase));
return version != null;
}

public ISourceComponent GetRepoVersion(string mappingName)
=> TryGetRepoVersion(mappingName, out var version)
? version
: throw new Exception($"No manifest record named {mappingName} found");

public static SourceManifest FromJson(string path)
{
if (!File.Exists(path))
{
return new SourceManifest(Array.Empty<RepositoryRecord>(), Array.Empty<SubmoduleRecord>());
return new SourceManifest([], []);
}

var options = new JsonSerializerOptions
Expand All @@ -149,7 +164,7 @@ public static SourceManifest FromJson(string path)
if (repositoryRecord != null)
{
return new(repositoryRecord.CommitSha, repositoryRecord.PackageVersion);
}
}
else
{
return null;
Expand All @@ -161,7 +176,7 @@ public static SourceManifest FromJson(string path)
/// </summary>
private class SourceManifestWrapper
{
public ICollection<RepositoryRecord> Repositories { get; init; } = Array.Empty<RepositoryRecord>();
public ICollection<SubmoduleRecord> Submodules { get; init; } = Array.Empty<SubmoduleRecord>();
public ICollection<RepositoryRecord> Repositories { get; init; } = [];
public ICollection<SubmoduleRecord> Submodules { get; init; } = [];
}
}
145 changes: 104 additions & 41 deletions src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrBackflower.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ namespace Microsoft.DotNet.DarcLib.VirtualMonoRepo;

public interface IVmrBackFlower
{
/// <summary>
/// Flows backward the code from the VMR to the target branch of a product repo.
/// This overload is used in the context of the darc CLI.
/// </summary>
/// <param name="mapping">Mapping to flow</param>
/// <param name="targetRepo">Local checkout of the repository</param>
/// <param name="shaToFlow">SHA to flow</param>
/// <param name="buildToFlow">Build to flow</param>
/// <param name="branchName">New branch name</param>
/// <param name="targetBranch">Target branch to create the PR branch on top of</param>
/// <param name="discardPatches">Keep patch files?</param>
Task<bool> FlowBackAsync(
string mapping,
NativePath targetRepo,
Expand All @@ -25,6 +36,17 @@ Task<bool> FlowBackAsync(
bool discardPatches = false,
CancellationToken cancellationToken = default);

/// <summary>
/// Flows backward the code from the VMR to the target branch of a product repo.
/// This overload is used in the context of the darc CLI.
/// </summary>
/// <param name="mapping">Mapping to flow</param>
/// <param name="targetRepo">Local checkout of the repository</param>
/// <param name="shaToFlow">SHA to flow</param>
/// <param name="buildToFlow">Build to flow</param>
/// <param name="branchName">New branch name</param>
/// <param name="targetBranch">Target branch to create the PR branch on top of</param>
/// <param name="discardPatches">Keep patch files?</param>
Task<bool> FlowBackAsync(
string mapping,
ILocalGitRepo targetRepo,
Expand All @@ -33,26 +55,30 @@ Task<bool> FlowBackAsync(
string? branchName,
bool discardPatches = false,
CancellationToken cancellationToken = default);

/// <summary>
/// Flows backward the code from the VMR to the target branch of a product repo.
/// This overload is used in the context of the PCS.
/// </summary>
/// <param name="mappingName">Mapping to flow</param>
/// <param name="build">Build to flow</param>
/// <param name="branchName">New branch name</param>
/// <param name="targetBranch">Target branch to create the PR branch on top of</param>
/// <returns>True when there were changes to be flown</returns>
Task<bool> FlowBackAsync(
string mappingName,
Build build,
string branchName,
string targetBranch,
CancellationToken cancellationToken = default);
}

internal class VmrBackFlower : VmrCodeFlower, IVmrBackFlower
{
private readonly IVmrInfo _vmrInfo;
private readonly ISourceManifest _sourceManifest;
private readonly IVmrDependencyTracker _dependencyTracker;
private readonly ILocalGitClient _localGitClient;
private readonly ILocalGitRepoFactory _localGitRepoFactory;
private readonly IVmrPatchHandler _vmrPatchHandler;
private readonly IWorkBranchFactory _workBranchFactory;
private readonly IBasicBarClient _barClient;
private readonly IFileSystem _fileSystem;
private readonly ILogger<VmrCodeFlower> _logger;

public VmrBackFlower(
internal class VmrBackFlower(
IVmrInfo vmrInfo,
ISourceManifest sourceManifest,
IVmrDependencyTracker dependencyTracker,
IDependencyFileManager dependencyFileManager,
IRepositoryCloneManager repositoryCloneManager,
ILocalGitClient localGitClient,
ILocalGitRepoFactory localGitRepoFactory,
IVersionDetailsParser versionDetailsParser,
Expand All @@ -64,31 +90,19 @@ public VmrBackFlower(
IAssetLocationResolver assetLocationResolver,
IFileSystem fileSystem,
ILogger<VmrCodeFlower> logger)
: base(
vmrInfo,
sourceManifest,
dependencyTracker,
localGitClient,
libGit2Client,
localGitRepoFactory,
versionDetailsParser,
dependencyFileManager,
coherencyUpdateResolver,
assetLocationResolver,
fileSystem,
logger)
{
_vmrInfo = vmrInfo;
_sourceManifest = sourceManifest;
_dependencyTracker = dependencyTracker;
_localGitClient = localGitClient;
_localGitRepoFactory = localGitRepoFactory;
_vmrPatchHandler = vmrPatchHandler;
_workBranchFactory = workBranchFactory;
_barClient = basicBarClient;
_fileSystem = fileSystem;
_logger = logger;
}
: VmrCodeFlower(vmrInfo, sourceManifest, dependencyTracker, repositoryCloneManager, localGitClient, libGit2Client, localGitRepoFactory, versionDetailsParser, dependencyFileManager, coherencyUpdateResolver, assetLocationResolver, fileSystem, logger),
IVmrBackFlower
{
private readonly IVmrInfo _vmrInfo = vmrInfo;
private readonly ISourceManifest _sourceManifest = sourceManifest;
private readonly IVmrDependencyTracker _dependencyTracker = dependencyTracker;
private readonly ILocalGitClient _localGitClient = localGitClient;
private readonly ILocalGitRepoFactory _localGitRepoFactory = localGitRepoFactory;
private readonly IVmrPatchHandler _vmrPatchHandler = vmrPatchHandler;
private readonly IWorkBranchFactory _workBranchFactory = workBranchFactory;
private readonly IBasicBarClient _barClient = basicBarClient;
private readonly IFileSystem _fileSystem = fileSystem;
private readonly ILogger<VmrCodeFlower> _logger = logger;

public Task<bool> FlowBackAsync(
string mapping,
Expand All @@ -98,7 +112,36 @@ public Task<bool> FlowBackAsync(
string? branchName,
bool discardPatches = false,
CancellationToken cancellationToken = default)
=> FlowBackAsync(mapping, _localGitRepoFactory.Create(targetRepoPath), shaToFlow, buildToFlow, branchName, discardPatches, cancellationToken);
=> FlowBackAsync(
mapping,
_localGitRepoFactory.Create(targetRepoPath),
shaToFlow,
buildToFlow,
branchName,
discardPatches,
cancellationToken);

public async Task<bool> FlowBackAsync(
string mappingName,
Build build,
string branchName,
string targetBranch,
CancellationToken cancellationToken = default)
{
SourceMapping mapping = _dependencyTracker.GetMapping(mappingName);
ILocalGitRepo targetRepo = await PrepareRepoAndVmr(mapping, targetBranch, build.Commit, cancellationToken);
Codeflow lastFlow = await GetLastFlowAsync(mapping, targetRepo, currentIsBackflow: true);

return await FlowBackAsync(
mapping,
targetRepo,
lastFlow,
build.Commit,
build,
branchName,
true,
cancellationToken);
}

public async Task<bool> FlowBackAsync(
string mappingName,
Expand Down Expand Up @@ -127,9 +170,29 @@ public async Task<bool> FlowBackAsync(
await CheckOutVmr(shaToFlow);
}

var mapping = _dependencyTracker.Mappings.First(m => m.Name == mappingName);
var mapping = _dependencyTracker.GetMapping(mappingName);
Codeflow lastFlow = await GetLastFlowAsync(mapping, targetRepo, currentIsBackflow: true);
return await FlowBackAsync(
mapping,
targetRepo,
lastFlow,
shaToFlow,
build,
branchName,
discardPatches,
cancellationToken);
}

private async Task<bool> FlowBackAsync(
SourceMapping mapping,
ILocalGitRepo targetRepo,
Codeflow lastFlow,
string shaToFlow,
Build? build,
string? branchName,
bool discardPatches,
CancellationToken cancellationToken)
{
var hasChanges = await FlowCodeAsync(
lastFlow,
new Backflow(lastFlow.TargetSha, shaToFlow),
Expand Down
46 changes: 42 additions & 4 deletions src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrCodeflower.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal abstract class VmrCodeFlower
private readonly IVmrInfo _vmrInfo;
private readonly ISourceManifest _sourceManifest;
private readonly IVmrDependencyTracker _dependencyTracker;
private readonly IRepositoryCloneManager _repositoryCloneManager;
private readonly ILocalGitClient _localGitClient;
private readonly ILocalLibGit2Client _libGit2Client;
private readonly IVersionDetailsParser _versionDetailsParser;
Expand All @@ -41,6 +42,7 @@ protected VmrCodeFlower(
IVmrInfo vmrInfo,
ISourceManifest sourceManifest,
IVmrDependencyTracker dependencyTracker,
IRepositoryCloneManager repositoryCloneManager,
ILocalGitClient localGitClient,
ILocalLibGit2Client libGit2Client,
ILocalGitRepoFactory localGitRepoFactory,
Expand All @@ -54,6 +56,7 @@ protected VmrCodeFlower(
_vmrInfo = vmrInfo;
_sourceManifest = sourceManifest;
_dependencyTracker = dependencyTracker;
_repositoryCloneManager = repositoryCloneManager;
_localGitClient = localGitClient;
_libGit2Client = libGit2Client;
_versionDetailsParser = versionDetailsParser;
Expand Down Expand Up @@ -99,12 +102,28 @@ protected async Task<bool> FlowCodeAsync(
if (lastFlow.Name == currentFlow.Name)
{
_logger.LogInformation("Current flow is in the same direction");
hasChanges = await SameDirectionFlowAsync(mapping, lastFlow, currentFlow, repo, build, branchName, discardPatches, cancellationToken);
hasChanges = await SameDirectionFlowAsync(
mapping,
lastFlow,
currentFlow,
repo,
build,
branchName,
discardPatches,
cancellationToken);
}
else
{
_logger.LogInformation("Current flow is in the opposite direction");
hasChanges = await OppositeDirectionFlowAsync(mapping, lastFlow, currentFlow, repo, build, branchName, discardPatches, cancellationToken);
hasChanges = await OppositeDirectionFlowAsync(
mapping,
lastFlow,
currentFlow,
repo,
build,
branchName,
discardPatches,
cancellationToken);
}

if (!hasChanges)
Expand Down Expand Up @@ -259,8 +278,7 @@ protected async Task<Codeflow> GetLastFlowAsync(SourceMapping mapping, ILocalGit
/// </summary>
private async Task<ForwardFlow> GetLastForwardFlow(string mappingName)
{
IVersionedSourceComponent repoInVmr = _sourceManifest.Repositories.FirstOrDefault(r => r.Path == mappingName)
?? throw new ArgumentException($"No repository mapping named {mappingName} found");
ISourceComponent repoInVmr = _sourceManifest.GetRepoVersion(mappingName);

// Last forward flow SHAs come from source-manifest.json in the VMR
string lastForwardRepoSha = repoInVmr.CommitSha;
Expand Down Expand Up @@ -356,6 +374,26 @@ protected async Task UpdateDependenciesAndToolset(
await targetRepo.CommitAsync($"Update dependency files to {currentVmrSha}", allowEmpty: true, cancellationToken: cancellationToken);
}

protected async Task<ILocalGitRepo> PrepareRepoAndVmr(
SourceMapping mapping,
string repoRef,
string vmrRef,
CancellationToken cancellationToken)
{
var remotes = new[] { mapping.DefaultRef, _sourceManifest.GetRepoVersion(mapping.Name).RemoteUri }
.Distinct()
.OrderRemotesByLocalPublicOther();

ILocalGitRepo repo = await _repositoryCloneManager.PrepareCloneAsync(
mapping,
[.. remotes],
repoRef,
cancellationToken);

await CheckOutVmr(vmrRef);
return repo;
}

/// <summary>
/// Compares 2 git commits and returns true if the first one is an ancestor of the second one.
/// </summary>
Expand Down
Loading

0 comments on commit d0a1879

Please sign in to comment.