Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.Darc.Options.VirtualMonoRepo;
using Microsoft.DotNet.DarcLib.Helpers;
using Microsoft.DotNet.DarcLib.Models.VirtualMonoRepo;
using Microsoft.DotNet.DarcLib.VirtualMonoRepo;
using Microsoft.Extensions.Logging;

#nullable enable
namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo;

internal class AddRepoOperation : VmrOperationBase
{
private readonly AddRepoCommandLineOptions _options;
private readonly IVmrInitializer _vmrInitializer;
private readonly IVmrInfo _vmrInfo;

public AddRepoOperation(
AddRepoCommandLineOptions options,
IVmrInitializer vmrInitializer,
IVmrInfo vmrInfo,
ILogger<AddRepoOperation> logger)
: base(options, logger)
{
_options = options;
_vmrInitializer = vmrInitializer;
_vmrInfo = vmrInfo;
}

protected override async Task ExecuteInternalAsync(
string repoName,
string? targetRevision,
IReadOnlyCollection<AdditionalRemote> additionalRemotes,
CancellationToken cancellationToken)
{
var sourceMappingsPath = _vmrInfo.VmrPath / VmrInfo.DefaultRelativeSourceMappingsPath;

await _vmrInitializer.InitializeRepository(
repoName,
targetRevision,
sourceMappingsPath,
new CodeFlowParameters(
additionalRemotes,
VmrInfo.ThirdPartyNoticesFileName,
GenerateCodeOwners: false,
GenerateCredScanSuppressions: true),
cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.Darc.Options.VirtualMonoRepo;
using Microsoft.DotNet.DarcLib.Models.VirtualMonoRepo;
using Microsoft.DotNet.DarcLib.VirtualMonoRepo;
using Microsoft.Extensions.Logging;

#nullable enable
namespace Microsoft.DotNet.Darc.Operations.VirtualMonoRepo;

internal class RemoveRepoOperation : VmrOperationBase
{
private readonly RemoveRepoCommandLineOptions _options;
private readonly IVmrRemover _vmrRemover;

public RemoveRepoOperation(
RemoveRepoCommandLineOptions options,
IVmrRemover vmrRemover,
ILogger<RemoveRepoOperation> logger)
: base(options, logger)
{
_options = options;
_vmrRemover = vmrRemover;
}

protected override async Task ExecuteInternalAsync(
string repoName,
string? targetRevision,
IReadOnlyCollection<AdditionalRemote> additionalRemotes,
CancellationToken cancellationToken)
{
await _vmrRemover.RemoveRepository(
repoName,
new CodeFlowParameters(
additionalRemotes,
VmrInfo.ThirdPartyNoticesFileName,
GenerateCodeOwners: false,
GenerateCredScanSuppressions: true),
cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using CommandLine;
using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo;

namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo;

[Verb("add-repo", HelpText = "Adds new repo(s) to the VMR that haven't been synchronized yet.")]
internal class AddRepoCommandLineOptions : VmrCommandLineOptions<AddRepoOperation>, IBaseVmrCommandLineOptions
{
[Value(0, Required = true, HelpText =
"Repository names in the form of NAME or NAME:REVISION where REVISION is a commit SHA or other git reference (branch, tag). " +
"Omitting REVISION will synchronize the repo to current HEAD.")]
public IEnumerable<string> Repositories { get; set; }

// Required by IBaseVmrCommandLineOptions but not used for this command
public IEnumerable<string> AdditionalRemotes { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using CommandLine;
using Microsoft.DotNet.Darc.Operations.VirtualMonoRepo;

namespace Microsoft.DotNet.Darc.Options.VirtualMonoRepo;

[Verb("remove-repo", HelpText = "Removes repo(s) from the VMR.")]
internal class RemoveRepoCommandLineOptions : VmrCommandLineOptions<RemoveRepoOperation>, IBaseVmrCommandLineOptions
{
[Value(0, Required = true, HelpText = "Repository names to remove from the VMR.")]
public IEnumerable<string> Repositories { get; set; }

// Required by IBaseVmrCommandLineOptions but not used for this command
public IEnumerable<string> AdditionalRemotes { get; set; } = [];
}
2 changes: 2 additions & 0 deletions src/Microsoft.DotNet.Darc/Darc/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ public static Type[] GetOptions() =>
// These are under the "vmr" subcommand
public static Type[] GetVmrOptions() =>
[
typeof(AddRepoCommandLineOptions),
typeof(RemoveRepoCommandLineOptions),
typeof(InitializeCommandLineOptions),
typeof(BackflowCommandLineOptions),
typeof(ForwardFlowCommandLineOptions),
Expand Down
23 changes: 23 additions & 0 deletions src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/IVmrRemover.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.DarcLib.Models.VirtualMonoRepo;

#nullable enable
namespace Microsoft.DotNet.DarcLib.VirtualMonoRepo;

public interface IVmrRemover
{
/// <summary>
/// Removes a repository from the VMR.
/// </summary>
/// <param name="mappingName">Name of a repository mapping</param>
/// <param name="codeFlowParameters">Record containing parameters for VMR operations</param>
/// <param name="cancellationToken">Cancellation token</param>
Task RemoveRepository(
string mappingName,
CodeFlowParameters codeFlowParameters,
CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ private static IServiceCollection AddVmrManagers(
services.TryAddTransient<IVmrPatchHandler, VmrPatchHandler>();
services.TryAddTransient<IVmrUpdater, VmrUpdater>();
services.TryAddTransient<IVmrInitializer, VmrInitializer>();
services.TryAddTransient<IVmrRemover, VmrRemover>();
services.TryAddTransient<IVmrBackFlower, VmrBackFlower>();
services.TryAddTransient<IVmrForwardFlower, VmrForwardFlower>();
services.TryAddTransient<ICodeFlowVmrUpdater, CodeFlowVmrUpdater>();
Expand Down
147 changes: 147 additions & 0 deletions src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrRemover.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Maestro.Common;
using Microsoft.DotNet.DarcLib.Helpers;
using Microsoft.DotNet.DarcLib.Models.VirtualMonoRepo;
using Microsoft.Extensions.Logging;

#nullable enable
namespace Microsoft.DotNet.DarcLib.VirtualMonoRepo;

/// <summary>
/// This class is able to remove a repository from the VMR.
/// It removes the sources, updates the source manifest, and optionally regenerates
/// third-party notices, codeowners, and credential scan suppressions.
/// </summary>
public class VmrRemover : VmrManagerBase, IVmrRemover
{
private const string RemovalCommitMessage =
$$"""
[{name}] Removal of the repository from VMR

{{Constants.AUTOMATION_COMMIT_TAG}}
""";

private readonly IVmrInfo _vmrInfo;
private readonly IVmrDependencyTracker _dependencyTracker;
private readonly IWorkBranchFactory _workBranchFactory;
private readonly IFileSystem _fileSystem;
private readonly ILogger<VmrRemover> _logger;
private readonly ISourceManifest _sourceManifest;
private readonly ILocalGitClient _localGitClient;
private readonly IThirdPartyNoticesGenerator _thirdPartyNoticesGenerator;
private readonly ICodeownersGenerator _codeownersGenerator;
private readonly ICredScanSuppressionsGenerator _credScanSuppressionsGenerator;

public VmrRemover(
IVmrDependencyTracker dependencyTracker,
IVmrPatchHandler patchHandler,
IThirdPartyNoticesGenerator thirdPartyNoticesGenerator,
ICodeownersGenerator codeownersGenerator,
ICredScanSuppressionsGenerator credScanSuppressionsGenerator,
ILocalGitClient localGitClient,
ILocalGitRepoFactory localGitRepoFactory,
IWorkBranchFactory workBranchFactory,
IFileSystem fileSystem,
ILogger<VmrRemover> logger,
ILogger<VmrUpdater> baseLogger,
ISourceManifest sourceManifest,
IVmrInfo vmrInfo)
: base(vmrInfo, dependencyTracker, patchHandler, thirdPartyNoticesGenerator, codeownersGenerator, credScanSuppressionsGenerator, localGitClient, localGitRepoFactory, baseLogger)
{
_vmrInfo = vmrInfo;
_dependencyTracker = dependencyTracker;
_workBranchFactory = workBranchFactory;
_fileSystem = fileSystem;
_logger = logger;
_sourceManifest = sourceManifest;
_localGitClient = localGitClient;
_thirdPartyNoticesGenerator = thirdPartyNoticesGenerator;
_codeownersGenerator = codeownersGenerator;
_credScanSuppressionsGenerator = credScanSuppressionsGenerator;
}

public async Task RemoveRepository(
string mappingName,
CodeFlowParameters codeFlowParameters,
CancellationToken cancellationToken)
{
await _dependencyTracker.RefreshMetadataAsync();
var mapping = _dependencyTracker.GetMapping(mappingName);

if (_dependencyTracker.GetDependencyVersion(mapping) is null)
{
throw new Exception($"Repository {mapping.Name} does not exist in the VMR");
}

var workBranchName = $"remove/{mapping.Name}";
IWorkBranch workBranch = await _workBranchFactory.CreateWorkBranchAsync(GetLocalVmr(), workBranchName);

try
{
_logger.LogInformation("Removing {name} from the VMR..", mapping.Name);

var sourcesPath = _vmrInfo.GetRepoSourcesPath(mapping);
if (_fileSystem.DirectoryExists(sourcesPath))
{
_logger.LogInformation("Removing source directory {path}", sourcesPath);
_fileSystem.DeleteDirectory(sourcesPath, recursive: true);
}
else
{
_logger.LogWarning("Source directory {path} does not exist", sourcesPath);
}

// Remove from source manifest
_sourceManifest.RemoveRepository(mapping.Name);
_fileSystem.WriteToFile(_vmrInfo.SourceManifestPath, _sourceManifest.ToJson());

var filesToStage = new List<string>
{
_vmrInfo.SourceManifestPath,
sourcesPath
};

await _localGitClient.StageAsync(_vmrInfo.VmrPath, filesToStage, cancellationToken);

cancellationToken.ThrowIfCancellationRequested();

// Regenerate files
if (codeFlowParameters.TpnTemplatePath != null)
{
await _thirdPartyNoticesGenerator.UpdateThirdPartyNotices(codeFlowParameters.TpnTemplatePath);
await _localGitClient.StageAsync(_vmrInfo.VmrPath, new[] { VmrInfo.ThirdPartyNoticesFileName }, cancellationToken);
}

if (codeFlowParameters.GenerateCodeOwners)
{
await _codeownersGenerator.UpdateCodeowners(cancellationToken);
}

if (codeFlowParameters.GenerateCredScanSuppressions)
{
await _credScanSuppressionsGenerator.UpdateCredScanSuppressions(cancellationToken);
}

var commitMessage = RemovalCommitMessage.Replace("{name}", mapping.Name);
await CommitAsync(commitMessage);

await workBranch.MergeBackAsync(commitMessage);

_logger.LogInformation("Removal of {repo} finished", mapping.Name);
}
catch (Exception)
{
_logger.LogWarning(
InterruptedSyncExceptionMessage,
workBranch.OriginalBranchName.StartsWith("sync") || workBranch.OriginalBranchName.StartsWith("init") || workBranch.OriginalBranchName.StartsWith("remove") ?
"the original" : workBranch.OriginalBranchName);
throw;
}
}
}