Repository navigation
Expand file tree
/
Copy pathGet-RemainingCommitCount.ps1
More file actions
147 lines (123 loc) · 5.58 KB
/
Copy pathGet-RemainingCommitCount.ps1
File metadata and controls
147 lines (123 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<#
.SYNOPSIS
Counts how many upstream commits remain to be merged between a start ref and an end ref (tag or HEAD).
.DESCRIPTION
MCP-compatible tool that reports the number of commits reachable from EndRef but not from
StartRef (i.e. `git rev-list --count <StartRef>..<EndRef>`). It is used during an upstream
merge to answer "how many commits are left to merge to the target tag / HEAD?" so the agent
and user can gauge remaining work and estimate the number of batches.
Both refs must be resolvable in the local repository. For an upstream merge, StartRef is
typically the last-merged upstream commit (or tag) and EndRef is the upstream target tag or
the tip of the upstream tracking branch. Run a fetch first so the refs are up to date, e.g.
`git fetch upstream --tags`.
.PARAMETER StartRef
The ref (commit SHA, tag, or branch) that marks the last already-merged upstream point.
Commits reachable from this ref are excluded from the count. Required.
.PARAMETER EndRef
The ref (commit SHA, tag, or branch) that marks the merge target. Defaults to 'HEAD'.
For an upstream merge this is usually the upstream target tag (e.g. 'V_10_3_P1') or the
upstream tracking branch tip (e.g. 'upstream/master').
.OUTPUTS
Hashtable with:
Success [bool] Whether both refs resolved and the count succeeded
Message [string] Human-readable summary
StartRef [string] Echoed StartRef
EndRef [string] Echoed EndRef
StartCommit [string] Resolved full SHA of StartRef (or empty on error)
EndCommit [string] Resolved full SHA of EndRef (or empty on error)
RemainingCount [int] Commits reachable from EndRef but not StartRef (StartRef..EndRef)
AlreadyMerged [bool] True if RemainingCount is 0 (nothing left to merge)
Errors [string[]] Any error messages
.EXAMPLE
# How many commits remain between the last merged tag and the target tag
# MCP Tool: mcp_openssh-server_Get_RemainingCommitCount
# StartRef="V_10_0_P2", EndRef="V_10_3_P1"
.EXAMPLE
# How many commits remain to the current upstream tip
# MCP Tool: mcp_openssh-server_Get_RemainingCommitCount
# StartRef="6fb728df50c1afd338cb0223a84ce24579577eff", EndRef="upstream/master"
#>
param(
[Parameter(Mandatory)]
[string]$StartRef,
[string]$EndRef = 'HEAD'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# ── Git process helper (same pattern as Get-ConflictContext.ps1 / Invoke-Git.ps1) ──
function Invoke-GitCommand {
param([string[]]$Arguments)
$processInfo = [System.Diagnostics.ProcessStartInfo]::new()
$processInfo.FileName = 'git'
$processInfo.Arguments = ($Arguments | ForEach-Object {
if ($_ -match '\s') { "`"$_`"" } else { $_ }
}) -join ' '
$processInfo.UseShellExecute = $false
$processInfo.CreateNoWindow = $true
$processInfo.RedirectStandardInput = $true
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$process = [System.Diagnostics.Process]::new()
$process.StartInfo = $processInfo
$process.Start() | Out-Null
$process.StandardInput.Close()
$stdoutTask = $process.StandardOutput.ReadToEndAsync()
$stderrTask = $process.StandardError.ReadToEndAsync()
$completed = $process.WaitForExit(30000)
if (-not $completed) {
$process.Kill()
return @{
ExitCode = -1
Success = $false
Output = ''
Error = "git $($Arguments -join ' ') timed out after 30 seconds"
}
}
return @{
ExitCode = $process.ExitCode
Success = ($process.ExitCode -eq 0)
Output = $stdoutTask.GetAwaiter().GetResult().TrimEnd()
Error = $stderrTask.GetAwaiter().GetResult().TrimEnd()
}
}
$result = @{
Success = $false
Message = ''
StartRef = $StartRef
EndRef = $EndRef
StartCommit = ''
EndCommit = ''
RemainingCount = 0
AlreadyMerged = $false
Errors = @()
}
# Resolve both refs so we fail clearly if one is missing/unfetched.
$startResolve = Invoke-GitCommand @('rev-parse', '--verify', "$StartRef^{commit}")
if (-not $startResolve.Success) {
$result.Errors += "Could not resolve StartRef '$StartRef': $($startResolve.Error). Did you fetch upstream (git fetch upstream --tags)?"
$result.Message = "Failed to resolve StartRef '$StartRef'."
return $result
}
$result.StartCommit = $startResolve.Output
$endResolve = Invoke-GitCommand @('rev-parse', '--verify', "$EndRef^{commit}")
if (-not $endResolve.Success) {
$result.Errors += "Could not resolve EndRef '$EndRef': $($endResolve.Error). Did you fetch upstream (git fetch upstream --tags)?"
$result.Message = "Failed to resolve EndRef '$EndRef'."
return $result
}
$result.EndCommit = $endResolve.Output
$count = Invoke-GitCommand @('rev-list', '--count', "$StartRef..$EndRef")
if (-not $count.Success) {
$result.Errors += "git rev-list failed: $($count.Error)"
$result.Message = "Failed to count commits between '$StartRef' and '$EndRef'."
return $result
}
$result.RemainingCount = [int]$count.Output
$result.AlreadyMerged = ($result.RemainingCount -eq 0)
$result.Success = $true
if ($result.AlreadyMerged) {
$result.Message = "0 commits remain between '$StartRef' and '$EndRef' — the target is already merged (or EndRef is not ahead of StartRef)."
} else {
$result.Message = "$($result.RemainingCount) commit(s) remain to be merged from '$StartRef' to '$EndRef'."
}
return $result