Skip to content

Commit 2311bfe

Browse files
ADO Lens 0.9.4
- `ado_trace`: a branch earlier in the environment flow than the one a change was merged into is not reported as "NOT merged yet" (a change merged straight into master of a library repository says nothing about its dev branch). - Before "not yet", one question per source branch: completed PRs from the same source branch into that branch that are not linked to the item ("merged by PRs not linked to the item: PR 31002 (2021-09-01, staging <- 243623_…)").
1 parent 96f4bc6 commit 2311bfe

4 files changed

Lines changed: 34 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 0.9.4 — 2026-10-26
4+
5+
- `ado_trace`: a branch earlier in the environment flow than the one a change was merged into is not reported as
6+
"NOT merged yet" (a change merged straight into master of a library repository says nothing about its dev branch).
7+
- Before "not yet", one question per source branch: completed PRs from the same source branch into that branch that
8+
are not linked to the item ("merged by PRs not linked to the item: PR 31002 (2021-09-01, staging <- 243623_…)").
9+
310
## 0.9.3 — 2026-10-19
411

512
- `ado_trace`: "is the change in this branch / deployment" asks the history of the target around the day the change

‎src/AdoLens/AdoLens.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<AssemblyTitle>ADO Lens for Azure DevOps</AssemblyTitle>
2424
<Description>Azure DevOps (work items, boards, wiki, Git and TFVC, pipelines) served to AI assistants over MCP</Description>
2525
<Copyright>Copyright © 2026 WillowDev</Copyright>
26-
<Version>0.9.3</Version>
26+
<Version>0.9.4</Version>
2727
</PropertyGroup>
2828

2929
<ItemGroup>

‎src/AdoLens/Code.cs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@ public async Task<CodeChange> PullRequestAsync(string? repoId, int id, bool with
189189
catch (AdoApiException) { return null; }
190190
}
191191

192+
/// <summary>Completed PRs from <paramref name="source"/> into <paramref name="target"/>: "PR 123 (09-25, staging <- feature/x)".</summary>
193+
public async Task<List<string>> CompletedPullRequestsAsync(string repoId, string source, string target, CancellationToken ct)
194+
{
195+
try
196+
{
197+
var list = await client.GetListAsync($"_apis/git/repositories/{repoId}/pullrequests?searchCriteria.status=completed" +
198+
$"&searchCriteria.sourceRefName={Uri.EscapeDataString("refs/heads/" + source)}&searchCriteria.targetRefName={Uri.EscapeDataString("refs/heads/" + target)}&$top=10", ct);
199+
return list.Select(p => $"PR {(p.TryGetProperty("pullRequestId", out var id) ? id.GetInt32() : 0)} " +
200+
$"({Date(Json.Str(p, "closedDate"))?.ToLocalTime():yyyy-MM-dd}, {target} <- {source})").ToList();
201+
}
202+
catch (AdoApiException) { return []; }
203+
}
204+
192205
/// <summary>A build, or null when it is gone (builds are deleted by retention).</summary>
193206
public async Task<JsonElement?> BuildAsync(string project, int id, CancellationToken ct)
194207
{

‎src/AdoLens/Trace.cs‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ bool Later(string? changeTarget, string? branch) =>
312312
: isBranch && g.Kind == "pr" && string.Equals(g.TargetBranch, target, StringComparison.OrdinalIgnoreCase) ? true
313313
// A deployment that finished before a change was merged cannot contain it: no need to ask.
314314
: deployedAt is { } at && g.Date is { } merged && at < merged ? false
315-
: Later(g.TargetBranch, isBranch ? target : targetBranch) ? false
315+
: Later(g.TargetBranch, isBranch ? target : targetBranch) ? (bool?)null
316316
: !newest.Contains(g) ? (bool?)null
317317
: await Contains(repo, commit, target, isBranch, g.Date)));
318318
// The older changes of a pair follow the answer for its newest one.
@@ -324,7 +324,7 @@ bool Later(string? changeTarget, string? branch) =>
324324
var known = answers.Any(a => a is not null);
325325
// Not yet: unless a change from the same source branch is in and no older than a day before it (the same work
326326
// merged into each environment by its own PR).
327-
var notYet = git.Where(g => !inside.Contains(g) && !IsPromotion(g)
327+
var notYet = git.Where(g => !inside.Contains(g) && !IsPromotion(g) && !Later(g.TargetBranch, isBranch ? target : targetBranch)
328328
&& !inside.Any(i => i.Branch is not null && i.Branch == g.Branch && i.Date >= g.Date?.AddDays(-1))).ToList();
329329
return (inside, notYet, known);
330330
}
@@ -376,9 +376,18 @@ static string Names(IEnumerable<CodeChange> list) =>
376376
{
377377
var (inside, notYet, known) = await GitStatus(group.Key, branch, true, null);
378378
if (!known) return null;
379-
if (inside.Count > 0) anyIn = true;
379+
// Work often reaches a branch by its own PR from the same source branch that nobody linked to the
380+
// item: one cheap question per source branch before saying "not yet".
381+
var unlinked = new List<string>();
382+
foreach (var source in notYet.Select(n => n.Branch).Where(b => b is not null).Distinct(StringComparer.OrdinalIgnoreCase))
383+
unlinked.AddRange(await code.CompletedPullRequestsAsync(group.Key, source!, branch, dt));
384+
if (unlinked.Count > 0)
385+
notYet = notYet.Where(n => !unlinked.Any(u => u.EndsWith($"<- {n.Branch})", StringComparison.OrdinalIgnoreCase))).ToList();
386+
if (inside.Count > 0 || unlinked.Count > 0) anyIn = true;
387+
var also = unlinked.Count > 0 ? $"merged by PRs not linked to the item: {string.Join(", ", unlinked.Distinct())}" : null;
380388
return (string?)($"- {envLabel} = branch {branch} ({repoName}): " + (inside.Count > 0
381-
? $"merged: {Names(inside.Where(x => !IsPromotion(x)).DefaultIfEmpty(inside[0]))}" + (notYet.Count > 0 ? $"; not yet: {Names(notYet)}" : "")
389+
? $"merged: {Names(inside.Where(x => !IsPromotion(x)).DefaultIfEmpty(inside[0]))}" + (also is null ? "" : "; " + also) + (notYet.Count > 0 ? $"; not yet: {Names(notYet)}" : "")
390+
: also is not null ? also + (notYet.Count > 0 ? $"; not yet: {Names(notYet)}" : "")
382391
: "NOT merged yet"));
383392
}, dt));
384393
}

0 commit comments

Comments
 (0)