Skip to content

Commit 4ecc31b

Browse files
azure-sdkscbeddbenbp
authored
Sync eng/common directory with azure-sdk-tools for PR 7537 (#33888)
* Add ConflictedFile to git-helpers.ps1, add git-helpers.tests.ps1 to exercise basic functionality. * Add `resolve-asset-conflict.ps1` a script that can autoresolve an assets.json file. --------- Co-authored-by: Scott Beddall (from Dev Box) <[email protected]> Co-authored-by: Scott Beddall <[email protected]> Co-authored-by: Ben Broderick Phillips <[email protected]>
1 parent e588964 commit 4ecc31b

File tree

7 files changed

+296
-22
lines changed

7 files changed

+296
-22
lines changed

eng/common/scripts/Helpers/git-helpers.ps1

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,75 @@ function Get-ChangedFiles {
3636
}
3737
return $changedFiles
3838
}
39+
40+
class ConflictedFile {
41+
[string]$LeftSource = ""
42+
[string]$RightSource = ""
43+
[string]$Content = ""
44+
[string]$Path = ""
45+
[boolean]$IsConflicted = $false
46+
47+
ConflictedFile([string]$File = "") {
48+
if (!(Test-Path $File)) {
49+
throw "File $File does not exist, pass a valid file path to the constructor."
50+
}
51+
52+
# Normally we would use Resolve-Path $file, but git only can handle relative paths using git show <commitsh>:<path>
53+
# Therefore, just maintain whatever the path is given to us. Left() and Right() should therefore be called from the same
54+
# directory as where we defined the relative path to the target file.
55+
$this.Path = $File
56+
$this.Content = Get-Content -Raw $File
57+
58+
$this.ParseContent($this.Content)
59+
}
60+
61+
[array] Left(){
62+
if ($this.IsConflicted) {
63+
# we are forced to get this line by line and reassemble via join because of how powershell is interacting with
64+
# git show --textconv commitsh:path
65+
# powershell ignores the newlines with and without --textconv, which results in a json file without original spacing.
66+
# by forcefully reading into the array line by line, the whitespace is preserved. we're relying on gits autoconverstion of clrf to lf
67+
# to ensure that the line endings are consistent.
68+
Write-Host "git show $($this.LeftSource):$($this.Path)"
69+
$tempContent = git show ("$($this.LeftSource):$($this.Path)")
70+
return $tempContent -split "`r?`n"
71+
}
72+
else {
73+
return $this.Content
74+
}
75+
}
76+
77+
[array] Right(){
78+
if ($this.IsConflicted) {
79+
Write-Host "git show $($this.RightSource):$($this.Path)"
80+
$tempContent = git show ("$($this.RightSource):$($this.Path)")
81+
return $tempContent -split "`r?`n"
82+
}
83+
else {
84+
return $this.Content
85+
}
86+
}
87+
88+
[void] ParseContent([string]$IncomingContent) {
89+
$lines = $IncomingContent -split "`r?`n"
90+
$l = @()
91+
$r = @()
92+
93+
foreach($line in $lines) {
94+
if ($line -match "^<<<<<<<\s*(.+)") {
95+
$this.IsConflicted = $true
96+
$this.LeftSource = $matches[1]
97+
continue
98+
}
99+
elseif ($line -match "^>>>>>>>\s*(.+)") {
100+
$this.IsConflicted = $true
101+
$this.RightSource = $matches[1]
102+
continue
103+
}
104+
105+
if ($this.LeftSource -and $this.RightSource) {
106+
break
107+
}
108+
}
109+
}
110+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Install-Module -Name Pester -Force -SkipPublisherCheck
2+
# Invoke-Pester -Passthru path/to/git-helpers.tests.ps1
3+
BeforeAll {
4+
. $PSScriptRoot/git-helpers.ps1
5+
6+
$RunFolder = "$PSScriptRoot/.testruns"
7+
8+
if (Test-Path $RunFolder){
9+
Remove-Item -Recurse -Force $RunFolder
10+
}
11+
12+
New-Item -ItemType Directory -Path $RunFolder
13+
}
14+
15+
Describe "git-helpers.ps1 tests"{
16+
Context "Test Parse-ConflictedFile" {
17+
It "Parses a basic conflicted file" {
18+
$content = @'
19+
{
20+
"AssetsRepo": "Azure/azure-sdk-assets-integration",
21+
"AssetsRepoPrefixPath": "python",
22+
"TagPrefix": "python/storage/azure-storage-blob",
23+
<<<<<<< HEAD
24+
"Tag": "integration/example/storage_feature_addition2"
25+
=======
26+
"Tag": "integration/example/storage_feature_addition1"
27+
>>>>>>> test-storage-tag-combination
28+
}
29+
'@
30+
$contentPath = Join-Path $RunFolder "basic_conflict_test.json"
31+
Set-Content -Path $contentPath -Value $content
32+
33+
$resolution = [ConflictedFile]::new($contentPath)
34+
$resolution.IsConflicted | Should -Be $true
35+
$resolution.LeftSource | Should -Be "HEAD"
36+
$resolution.RightSource | Should -Be "test-storage-tag-combination"
37+
}
38+
39+
It "Recognizes when no conflicts are present" {
40+
$content = @'
41+
{
42+
"AssetsRepo": "Azure/azure-sdk-assets-integration",
43+
"AssetsRepoPrefixPath": "python",
44+
"TagPrefix": "python/storage/azure-storage-blob",
45+
"Tag": "integration/example/storage_feature_addition1"
46+
}
47+
'@
48+
$contentPath = Join-Path $RunFolder "no_conflict_test.json"
49+
Set-Content -Path $contentPath -Value $content
50+
51+
$resolution = [ConflictedFile]::new($contentPath)
52+
$resolution.IsConflicted | Should -Be $false
53+
$resolution.LeftSource | Should -Be ""
54+
$resolution.RightSource | Should -Be ""
55+
}
56+
}
57+
}

eng/common/testproxy/onboarding/common-asset-functions.ps1

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ class Version {
5757
}
5858
}
5959

60+
Function Resolve-Proxy {
61+
$testProxyExe = "test-proxy"
62+
# this script requires the presence of the test-proxy on the PATH
63+
$proxyToolPresent = Test-Exe-In-Path -ExeToLookFor "test-proxy" -ExitOnError $false
64+
$proxyStandalonePresent = Test-Exe-In-Path -ExeToLookFor "Azure.Sdk.Tools.TestProxy" -ExitOnError $false
65+
66+
if (-not $proxyToolPresent -and -not $proxyStandalonePresent) {
67+
Write-Error "This script requires the presence of a test-proxy executable to complete its operations. Exiting."
68+
exit 1
69+
}
70+
71+
if (-not $proxyToolPresent) {
72+
$testProxyExe = "Azure.Sdk.Tools.TestProxy"
73+
}
74+
75+
return $testProxyExe
76+
}
77+
6078
Function Test-Exe-In-Path {
6179
Param([string] $ExeToLookFor, [bool]$ExitOnError = $true)
6280
if ($null -eq (Get-Command $ExeToLookFor -ErrorAction SilentlyContinue)) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Merge Proxy Tags Script
2+
3+
This script is intended to allow easy resolution of a conflicting `assets.json` file.
4+
5+
In most cases where two branches `X` and `Y` have progressed alongside each other, a simple
6+
7+
`git checkout X && git merge Y` can successfully merge _other_ than the `assets.json` file.
8+
9+
That often will end up looking like this:
10+
11+
```text
12+
{
13+
"AssetsRepo": "Azure/azure-sdk-assets-integration",
14+
"AssetsRepoPrefixPath": "python",
15+
"TagPrefix": "python/storage/azure-storage-blob",
16+
<<<<<<< HEAD
17+
"Tag": "integration/example/storage_feature_addition2"
18+
=======
19+
"Tag": "integration/example/storage_feature_addition1"
20+
>>>>>>> test-storage-tag-combination
21+
}
22+
```
23+
24+
This script uses `git` to tease out the source and target tags, then merge the incoming tag into the recordings of the base tag.
25+
26+
This script should _only_ be used on an already conflicted `assets.json` file. Otherwise, no action will be executed.
27+
28+
## Usage
29+
30+
### PreReqs
31+
32+
- Must have []`pshell 6+`](https://learn.microsoft.com/powershell/scripting/install/installing-powershell-on-windows)
33+
- Must have `git` available on your PATH
34+
- Must have the `test-proxy` available on your PATH
35+
- `test-proxy` is honored when the proxy is installed as a `dotnet tool`
36+
- `Azure.Sdk.Tools.TestProxy` is honored when the standalone executable is on your PATH
37+
- Defaults to `dotnet tool` if both are present on the PATH.
38+
39+
### Call the script
40+
41+
For simplicity when resolving merge-conflicts, invoke the script from the root of the repo. The help instructions from `merge-asset-tags` use paths relative from repo root.
42+
43+
```powershell
44+
# including context to get into a merge conflict
45+
cd "path/to/language/repo/root"
46+
git checkout base-branch
47+
git merge target-branch
48+
# auto resolve / merge conflicting tag values
49+
./eng/common/testproxy/scripts/resolve-asset-conflict/resolve-asset-conflict.ps1 sdk/storage/azure-storage-blob/assets.json
50+
# user pushes
51+
test-proxy push -a sdk/storage/azure-storage-blob/assets.json
52+
```
53+
54+
### Resolving conflicts
55+
56+
When an assets.json merge has conflicts on the **test recordings** side, the `merge-proxy-tags` script will exit with an error describing how to re-invoke the `merge-proxy-tags` script AFTER you resolve the conflicts.
57+
58+
- `cd` into the assets location output by the script
59+
- resolve the conflict or conflicts
60+
- add the resolution, and invoke `git cherry-pick --continue`
61+
62+
Afterwards, re-invoke the `merge-proxy-tags` script with arguments given to you in original error. This will leave the assets in a `touched` state that can be `test-proxy push`-ed.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#Requires -Version 6.0
2+
#Requires -PSEdition Core
3+
4+
<#
5+
.SYNOPSIS
6+
Within an assets.json file that has in a conflicted state (specifically on asset tag), merge the two tags that are conflicting and leave the assets.json in a commitable state.
7+
8+
.DESCRIPTION
9+
USAGE: resolve-asset-conflict.ps1 path/to/target_assets_json
10+
11+
Parses the assets.json file and determines which tags are in conflict. If there are no conflicts, the script exits.
12+
13+
1. Parse the tags (base and target) from conflicting assets.json.
14+
2. Update the assets.json with the base tag, but remember the target tag.
15+
3. merge-proxy-tags.ps1 $AssetsJson base_tag target_tag
16+
17+
This script requires that test-proxy or azure.sdk.tools.testproxy should be on the PATH.
18+
19+
.PARAMETER AssetsJson
20+
The script uses a target assets.json to understand what tags are in conflict. This is the only required parameter.
21+
#>
22+
23+
param(
24+
[Parameter(Position=0)]
25+
[string] $AssetsJson
26+
)
27+
28+
. (Join-Path $PSScriptRoot ".." ".." "onboarding" "common-asset-functions.ps1")
29+
. (Join-Path $PSScriptRoot ".." ".." ".." "scripts" "Helpers" "git-helpers.ps1")
30+
31+
$TestProxy = Resolve-Proxy
32+
33+
if (!(Test-Path $AssetsJson)) {
34+
Write-Error "AssetsJson file does not exist: $AssetsJson"
35+
exit 1
36+
}
37+
38+
# normally we we would Resolve-Path the $AssetsJson, but the git show command only works with relative paths, so we'll just keep that here.
39+
if (-not $AssetsJson.EndsWith("assets.json")) {
40+
Write-Error "This script can only resolve conflicts within an assets.json. The file provided is not an assets.json: $AssetsJson"
41+
exit 1
42+
}
43+
44+
$conflictingAssets = [ConflictedFile]::new($AssetsJson)
45+
46+
if (-not $conflictingAssets.IsConflicted) {
47+
Write-Host "No conflicts found in $AssetsJson, nothing to resolve, so there is no second tag to merge. Exiting"
48+
exit 0
49+
}
50+
51+
# this is very dumb, but will properly work!
52+
try {
53+
$BaseAssets = $conflictingAssets.Left() | ConvertFrom-Json
54+
}
55+
catch {
56+
Write-Error "Failed to convert previous version to valid JSON format."
57+
exit 1
58+
}
59+
60+
try {
61+
$TargetAssets = $conflictingAssets.Right() | ConvertFrom-Json
62+
}
63+
catch {
64+
Write-Error "Failed to convert target assets.json version to valid JSON format."
65+
exit 1
66+
}
67+
68+
Write-Host "Replacing conflicted assets.json with base branch version." -ForegroundColor Green
69+
Set-Content -Path $AssetsJson -Value $conflictingAssets.Left()
70+
71+
$ScriptPath = Join-Path $PSScriptRoot ".." "tag-merge" "merge-proxy-tags.ps1"
72+
& $ScriptPath $AssetsJson $BaseAssets.Tag $TargetAssets.Tag
73+
74+
if ($lastexitcode -eq 0) {
75+
Write-Host "Successfully auto-merged assets tag '$($TargetASsets.Tag)' into tag '$($BaseAssets.Tag)'. Invoke 'test-proxy push -a $AssetsJson' and commit the resulting assets.json!" -ForegroundColor Green
76+
}
77+
else {
78+
Write-Host "Conflicts were discovered, resolve the conflicts and invoke the `"merge-proxy-tags.ps1`" as recommended in the line directly above."
79+
}

eng/common/testproxy/scripts/tag-merge/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ This script merely allows the abstraction of some of this "combination" work.
1616
- Must have the `test-proxy` available on your PATH
1717
- `test-proxy` is honored when the proxy is installed as a `dotnet tool`
1818
- `Azure.Sdk.Tools.TestProxy` is honored when the standalone executable is on your PATH
19-
- Preference for `dotnet tool` if present
19+
- Defaults to `dotnet tool` if both are present on the PATH.
2020

2121
### Call the script
2222

eng/common/testproxy/scripts/tag-merge/merge-proxy-tags.ps1

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,6 @@ function Git-Command($CommandString, $WorkingDirectory, $HardExit=$true) {
6868
return $result.Output
6969
}
7070

71-
function Resolve-Proxy {
72-
$testProxyExe = "test-proxy"
73-
# this script requires the presence of the test-proxy on the PATH
74-
$proxyToolPresent = Test-Exe-In-Path -ExeToLookFor "test-proxy" -ExitOnError $false
75-
$proxyStandalonePresent = Test-Exe-In-Path -ExeToLookFor "Azure.Sdk.Tools.TestProxy" -ExitOnError $false
76-
77-
if (-not $proxyToolPresent -and -not $proxyStandalonePresent) {
78-
Write-Error "This script requires the presence of a test-proxy executable to complete its operations. Exiting."
79-
exit 1
80-
}
81-
82-
if (-not $proxyToolPresent) {
83-
$testProxyExe = "Azure.Sdk.Tools.TestProxy"
84-
}
85-
86-
return $testProxyExe
87-
}
88-
8971
function Call-Proxy {
9072
param(
9173
[string] $TestProxyExe,
@@ -256,14 +238,17 @@ function Prepare-Assets($ProxyExe, $MountDirectory, $AssetsJson) {
256238
}
257239
}
258240

259-
function Combine-Tags($RemainingTags, $AssetsRepoLocation, $MountDirectory){
241+
function Combine-Tags($RemainingTags, $AssetsRepoLocation, $MountDirectory, $RelativeAssetsJson){
242+
$remainingTagString = $RemainingTags -join " "
260243
foreach($Tag in $RemainingTags) {
261244
$tagSha = Get-Tag-SHA $Tag $AssetsRepoLocation
262245
$existingTags = Save-Incomplete-Progress $Tag $MountDirectory
263246
$cherryPickResult = Git-Command-With-Result "cherry-pick $tagSha" - $AssetsRepoLocation -HardExit $false
264247

265248
if ($cherryPickResult.ExitCode -ne 0) {
266-
Write-Host "Conflicts while cherry-picking $Tag. Resolve the the conflict over in `"$AssetsRepoLocation`", and re-run this script with the same arguments as before." -ForegroundColor Red
249+
$error = "Conflicts while cherry-picking $Tag. Resolve the the conflict over in `"$AssetsRepoLocation`", and re-invoke " +
250+
"by `"./eng/common/testproxy/scripts/tag-merge/merge-proxy-tags.ps1 $RelativeAssetsJson $remainingTagString`""
251+
Write-Host $error -ForegroundColor Red
267252
exit 1
268253
}
269254
}
@@ -294,6 +279,7 @@ if ($PSVersionTable["PSVersion"].Major -lt 6) {
294279
# resolve the proxy location so that we can invoke it easily, if not present we exit here.
295280
$proxyExe = Resolve-Proxy
296281

282+
$relativeAssetsJson = $AssetsJson
297283
$AssetsJson = Resolve-Path $AssetsJson
298284

299285
# figure out where the root of the repo for the passed assets.json is. We need it to properly set the mounting
@@ -313,6 +299,6 @@ $tags = Resolve-Target-Tags $AssetsJson $TargetTags $mountDirectory
313299

314300
Start-Message $AssetsJson $Tags $AssetsRepoLocation $mountDirectory
315301

316-
$CombinedTags = Combine-Tags $Tags $AssetsRepoLocation $mountDirectory
302+
$CombinedTags = Combine-Tags $Tags $AssetsRepoLocation $mountDirectory $relativeAssetsJson
317303

318304
Finish-Message $AssetsJson $CombinedTags $AssetsRepoLocation $mountDirectory

0 commit comments

Comments
 (0)