Skip to content

Commit 87c79a3

Browse files
authored
[Rollout] Production rollout 2025-05-29 (#4897)
#4896
2 parents 55b90ec + 889d571 commit 87c79a3

File tree

14 files changed

+136
-60
lines changed

14 files changed

+136
-60
lines changed

src/Microsoft.DotNet.Darc/DarcLib/Helpers/ProcessExecutionResult.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void ThrowIfFailed(string failureMessage)
1818
{
1919
if (!Succeeded)
2020
{
21-
throw new Exception(failureMessage + Environment.NewLine + this);
21+
throw new ProcessFailedException(this, failureMessage);
2222
}
2323
}
2424

@@ -40,3 +40,11 @@ public override string ToString()
4040
return output.ToString();
4141
}
4242
}
43+
44+
public class ProcessFailedException(ProcessExecutionResult executionResult, string failureMessage)
45+
: Exception
46+
{
47+
public ProcessExecutionResult ExecutionResult { get; } = executionResult;
48+
49+
public override string Message => failureMessage + Environment.NewLine + ExecutionResult.ToString();
50+
}

src/Microsoft.DotNet.Darc/DarcLib/Models/VirtualMonoRepo/CodeFlowResult.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,4 @@ public record CodeFlowResult(
1212
bool HadUpdates,
1313
IReadOnlyCollection<UnixPath> ConflictedFiles,
1414
NativePath RepoPath,
15-
string? PreviouslyFlownSha,
1615
List<DependencyUpdate> DependencyUpdates);

src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/Exceptions.cs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,44 @@ private static string GetExceptionMessage(VmrIngestionPatch patch, ProcessExecut
3131
/// Exception thrown when the service can't apply an update to the PR branch due to a conflict
3232
/// between the source repo and a change that was made in the PR after it was opened.
3333
/// </summary>
34-
public class ConflictInPrBranchException(ProcessExecutionResult gitMergeResult, string targetBranch, string targetRepo, bool isForwardFlow)
35-
: Exception($"Failed to flow changes due to conflicts in the target branch ({targetBranch})")
34+
public class ConflictInPrBranchException : Exception
3635
{
37-
public List<string> FilesInConflict { get; } = ParseResult(gitMergeResult, targetRepo, isForwardFlow);
38-
3936
private static readonly Regex AlreadyExistsRegex = new("patch failed: (.+): already exist in index");
4037
private static readonly Regex PatchFailedRegex = new("error: patch failed: (.*):");
4138
private static readonly Regex PatchDoesNotApplyRegex = new("error: (.+): patch does not apply");
4239
private static readonly Regex FileDoesNotExistRegex = new("error: (.+): does not exist in index");
40+
private static readonly Regex FailedMergeRegex = new("CONFLICT (content): Merge conflict in (.+)");
4341

4442
private static readonly Regex[] ConflictRegex =
45-
[
46-
AlreadyExistsRegex,
47-
PatchFailedRegex,
48-
PatchDoesNotApplyRegex,
49-
FileDoesNotExistRegex
50-
];
43+
[
44+
AlreadyExistsRegex,
45+
PatchFailedRegex,
46+
PatchDoesNotApplyRegex,
47+
FileDoesNotExistRegex,
48+
FailedMergeRegex,
49+
];
50+
51+
public List<string> ConflictedFiles { get; }
52+
53+
public ConflictInPrBranchException(
54+
string failedMergeMessage,
55+
string targetBranch,
56+
string mappingName,
57+
bool isForwardFlow)
58+
: this(ParseResult(failedMergeMessage, mappingName, isForwardFlow), targetBranch)
59+
{
60+
}
5161

52-
private static List<string> ParseResult(ProcessExecutionResult gitMergeResult, string targetRepo, bool isForwardFlow)
62+
private ConflictInPrBranchException(List<string> conflictedFiles, string targetBranch)
63+
: base($"Failed to flow changes due to conflicts in the target branch ({targetBranch})")
5364
{
54-
List<string> filesInConflict = new();
55-
var errors = gitMergeResult.StandardError.Split(Environment.NewLine);
65+
ConflictedFiles = conflictedFiles;
66+
}
67+
68+
private static List<string> ParseResult(string failureException, string mappingName, bool isForwardFlow)
69+
{
70+
List<string> filesInConflict = [];
71+
var errors = failureException.Split(Environment.NewLine);
5672
foreach (var error in errors)
5773
{
5874
foreach (var regex in ConflictRegex)
@@ -65,15 +81,16 @@ private static List<string> ParseResult(ProcessExecutionResult gitMergeResult, s
6581
}
6682
}
6783
}
84+
6885
if (isForwardFlow)
6986
{
7087
// Convert VMR paths to normal repo paths, for example src/repo/file.cs -> file.cs
71-
return filesInConflict.Select(file => file.Split('/', 3)[2]).Distinct().ToList();
88+
return [..filesInConflict.Select(file => file.Split('/', 3)[2]).Distinct()];
7289
}
73-
// If we're backflowing, the file paths are already normal
7490
else
7591
{
76-
return filesInConflict.Distinct().Select(file => $"src/{targetRepo}/{file}").ToList();
92+
// If we're backflowing, the file paths are already normalized
93+
return [..filesInConflict.Distinct().Select(file => $"src/{mappingName}/{file}")];
7794
}
7895
}
7996
}

src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrBackflower.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,7 @@ public async Task<CodeFlowResult> FlowBackAsync(
121121
headBranch,
122122
discardPatches,
123123
headBranchExisted,
124-
cancellationToken) with
125-
{
126-
PreviouslyFlownSha = lastBackFlow?.SourceSha,
127-
};
124+
cancellationToken);
128125
}
129126

130127
protected async Task<CodeFlowResult> FlowBackAsync(
@@ -170,7 +167,6 @@ protected async Task<CodeFlowResult> FlowBackAsync(
170167
hasChanges || mergeResult.DependencyUpdates.Count > 0,
171168
mergeResult.ConflictedFiles,
172169
targetRepo.Path,
173-
PreviouslyFlownSha: null,
174170
mergeResult.DependencyUpdates);
175171
}
176172

@@ -240,7 +236,7 @@ protected override async Task<bool> SameDirectionFlowAsync(
240236
if (headBranchExisted)
241237
{
242238
_logger.LogInformation("Failed to update a PR branch because of a conflict. Stopping the flow..");
243-
throw new ConflictInPrBranchException(e.Result, targetBranch, mapping.Name, isForwardFlow: false);
239+
throw new ConflictInPrBranchException(e.Result.StandardError, targetBranch, mapping.Name, isForwardFlow: false);
244240
}
245241

246242
// Otherwise, we have a conflicting change in the last backflow PR (before merging)
@@ -266,7 +262,19 @@ await RecreatePreviousFlowAndApplyBuild(
266262

267263
await targetRepo.CommitAsync(commitMessage, allowEmpty: false, cancellationToken: cancellationToken);
268264
await targetRepo.ResetWorkingTree();
269-
await workBranch.MergeBackAsync(commitMessage);
265+
266+
try
267+
{
268+
await workBranch.MergeBackAsync(commitMessage);
269+
}
270+
catch (ProcessFailedException e) when (headBranchExisted && e.ExecutionResult.StandardError.Contains("CONFLICT (content): Merge conflict"))
271+
{
272+
_logger.LogWarning("Failed to merge back the work branch {branchName} into {mainBranch}: {error}",
273+
newBranchName,
274+
headBranch,
275+
e.Message);
276+
throw new ConflictInPrBranchException(e.ExecutionResult.StandardError, targetBranch, mapping.Name, isForwardFlow: false);
277+
}
270278

271279
_logger.LogInformation("Branch {branch} with code changes is ready in {repoDir}", headBranch, targetRepo);
272280

src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrCodeflower.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ protected abstract Task<bool> OppositeDirectionFlowAsync(
228228

229229
if (objectType1 != GitObjectType.Commit || objectType2 != GitObjectType.Commit)
230230
{
231-
throw new InvalidSynchronizationException($"Failed to find one or both commits {lastBackflow.VmrSha}, {lastForwardFlow.VmrSha} in {sourceRepo}");
231+
throw new InvalidSynchronizationException($"Failed to find one or both commits {backwardSha}, {forwardSha} in {sourceRepo}");
232232
}
233233

234234
// If the SHA's are the same, it's a commit created by inflow which was then flown out
@@ -253,6 +253,34 @@ protected abstract Task<bool> OppositeDirectionFlowAsync(
253253
throw new InvalidSynchronizationException($"Failed to determine which commit of {sourceRepo} is older ({backwardSha}, {forwardSha})");
254254
}
255255

256+
// When the last backflow to our repo came from a different branch, we ignore it and return the last forward flow.
257+
// Some repositories do not snap branches for preview (e.g. razor, roslyn, msbuild), and forward-flow their main
258+
// branch into both the main branch and preview branch of the VMR.
259+
// Forward-flows into preview branches should not accept backflows as the previous flow on which to compute
260+
// the deltas, because those commits come from the main branch VMR. In those cases, we should always return
261+
// the previous forward-flow into the same preview branch of the VMR.
262+
if (!currentIsBackflow && isForwardOlder)
263+
{
264+
var vmr = _localGitRepoFactory.Create(_vmrInfo.VmrPath);
265+
var currentVmrSha = await vmr.GetShaForRefAsync();
266+
267+
// We can tell the above by checking if the current target VMR commit is a child of the last backflow commit.
268+
// For normal flows it should be, but for the case described above it will be on a different branch.
269+
if (!await vmr.IsAncestorCommit(lastBackflow.VmrSha, currentVmrSha))
270+
{
271+
_logger.LogWarning("Last detected backflow ({sha1}) from VMR is from a different branch than target VMR sha ({sha2}). " +
272+
"Ignoring backflow and considering the last forward flow to be the last flow.",
273+
lastBackflow.VmrSha,
274+
currentVmrSha);
275+
276+
return (
277+
lastForwardFlow,
278+
lastBackflow,
279+
lastForwardFlow
280+
);
281+
}
282+
}
283+
256284
return
257285
(
258286
isBackwardOlder ? lastForwardFlow : lastBackflow,

src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VmrForwardFlower.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ public async Task<CodeFlowResult> FlowForwardAsync(
143143
hasChanges,
144144
conflictedFiles ?? [],
145145
sourceRepo.Path,
146-
lastForwardFlow.SourceSha,
147146
DependencyUpdates: []);
148147
}
149148

@@ -222,7 +221,7 @@ protected override async Task<bool> SameDirectionFlowAsync(
222221
if (headBranchExisted)
223222
{
224223
_logger.LogInformation("Failed to update a PR branch because of a conflict. Stopping the flow..");
225-
throw new ConflictInPrBranchException(e.Result, targetBranch, mapping.Name, isForwardFlow: true);
224+
throw new ConflictInPrBranchException(e.Result.StandardError, targetBranch, mapping.Name, isForwardFlow: true);
226225
}
227226

228227
// This happens when a conflicting change was made in the last backflow PR (before merging)

src/ProductConstructionService/ProductConstructionService.BarViz/Pages/Subscriptions.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
@(context.LastAppliedBuild == null ? "N/A" : (DateTime.UtcNow - context.LastAppliedBuild.DateProduced).ToTimeAgo())
126126
</FluentLabel>
127127
</TemplateColumn>
128-
<TemplateColumn Style="width:60px">
128+
<TemplateColumn Width="60px">
129129
<SubscriptionContextMenu Subscription="@context" Refresh="@OnParametersSetAsync" ShowDetails="@ShowDetails" />
130130
</TemplateColumn>
131131
</ChildContent>

src/ProductConstructionService/ProductConstructionService.DependencyFlow/PcsVmrBackFlower.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ public async Task<CodeFlowResult> FlowBackAsync(
9595
{
9696
// For already existing PRs, we want to always push the changes (even if only the <Source> tag changed)
9797
HadUpdates = result.HadUpdates || headBranchExisted,
98-
PreviouslyFlownSha = lastFlows.LastBackFlow?.SourceSha,
9998
};
10099
}
101100

src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestConflictNotifier.cs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public async Task NotifyAboutConflictingUpdateAsync(
6969
{
7070
var (fileUrlInVmr, fileUrlInRepo) = GetFileUrls(update, subscription, filePath, prHeadBranch);
7171
string vmrString = $"[🔍 View in VMR]({fileUrlInVmr})";
72-
string repoString = $"[🔍 View in {GitRepoUrlUtils.GetRepoNameWithOrg(subscription.TargetRepository)}]({fileUrlInRepo})";
72+
string repoString = $"[🔍 View in {GitRepoUrlUtils.GetRepoNameWithOrg(subscription.IsBackflow() ? subscription.TargetRepository : subscription.SourceRepository)}]({fileUrlInRepo})";
7373
sb.AppendLine($" - `{filePath}` - {repoString} / {vmrString}");
7474
}
7575
sb.AppendLine();
@@ -87,7 +87,7 @@ public async Task NotifyAboutConflictingUpdateAsync(
8787
}
8888
}
8989

90-
private (string, string) GetFileUrls(SubscriptionUpdateWorkItem update,
90+
private static (string, string) GetFileUrls(SubscriptionUpdateWorkItem update,
9191
Subscription subscription,
9292
string filePath,
9393
string prHeadBranch)
@@ -122,12 +122,6 @@ public async Task NotifyAboutMergeConflictAsync(
122122

123123
if (subscription.IsBackflow())
124124
{
125-
if (!conflictedFiles.Any(f => f.Path.Equals(VersionFiles.VersionDetailsXml, StringComparison.InvariantCultureIgnoreCase)))
126-
{
127-
// No version files in conflict, so we don't need to post a comment
128-
return;
129-
}
130-
131125
metadataFile = VersionFiles.VersionDetailsXml;
132126
contentType = "xml";
133127
var sourceMetadata = new SourceDependency(
@@ -139,13 +133,6 @@ public async Task NotifyAboutMergeConflictAsync(
139133
}
140134
else
141135
{
142-
if (!conflictedFiles.Any(f => f.Path.Equals(VmrInfo.DefaultRelativeSourceManifestPath.Path, StringComparison.InvariantCultureIgnoreCase)
143-
|| f.Path.StartsWith(VmrInfo.GitInfoSourcesDir)))
144-
{
145-
// No version files in conflict, so we don't need to post a comment
146-
return;
147-
}
148-
149136
metadataFile = VmrInfo.DefaultRelativeSourceManifestPath;
150137
contentType = "json";
151138
correctContent = JsonSerializer.Serialize(

src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,30 +1015,43 @@ private async Task ProcessCodeFlowUpdateAsync(
10151015
subscription.TargetBranch,
10161016
prHeadBranch);
10171017

1018+
IRemote remote = await _remoteFactory.CreateRemoteAsync(subscription.TargetRepository);
1019+
10181020
NativePath localRepoPath;
10191021
CodeFlowResult codeFlowRes;
1020-
string? previousSourceSha;
1022+
string? previousSourceSha; // is null in some edge cases like onboarding a new repository
10211023

10221024
try
10231025
{
10241026
if (isForwardFlow)
10251027
{
10261028
codeFlowRes = await _vmrForwardFlower.FlowForwardAsync(subscription, build, prHeadBranch, cancellationToken: default);
10271029
localRepoPath = _vmrInfo.VmrPath;
1028-
previousSourceSha = codeFlowRes.PreviouslyFlownSha;
1030+
1031+
SourceManifest? sourceManifest = await remote.GetSourceManifestAsync(
1032+
subscription.TargetRepository,
1033+
subscription.TargetBranch);
1034+
1035+
previousSourceSha = sourceManifest?
1036+
.GetRepoVersion(subscription.TargetDirectory)?.CommitSha;
10291037
}
10301038
else
10311039
{
10321040
codeFlowRes = await _vmrBackFlower.FlowBackAsync(subscription, build, prHeadBranch, cancellationToken: default);
10331041
localRepoPath = codeFlowRes.RepoPath;
1034-
previousSourceSha = codeFlowRes.PreviouslyFlownSha;
1042+
1043+
SourceDependency? sourceDependency = await remote.GetSourceDependencyAsync(
1044+
subscription.TargetRepository,
1045+
subscription.TargetBranch);
1046+
1047+
previousSourceSha = sourceDependency?.Sha;
10351048
}
10361049
}
10371050
catch (ConflictInPrBranchException conflictException)
10381051
{
10391052
if (pr != null)
10401053
{
1041-
await HandlePrUpdateConflictAsync(conflictException.FilesInConflict, update, subscription, pr, prHeadBranch);
1054+
await HandlePrUpdateConflictAsync(conflictException.ConflictedFiles, update, subscription, pr, prHeadBranch);
10421055
}
10431056
return;
10441057
}

0 commit comments

Comments
 (0)