Hello,
I found a reproducible svn merge issue which may be related to SVN-3373:
https://issues.apache.org/jira/browse/SVN-3373
When the merge target file contains a mixed CRLF/LF function block and has no
svn:eol-style property, svn merge silently inserts a one-line change after the
function's closing "end" statement instead of inside the function. No conflict
or warning is produced.
I reproduced this with command-line clients on Windows 10 x64:
- Subversion 1.14.5 (r1922182)
- locally built Subversion 1.15.0-rc3
The attached PowerShell script is named repro.ps1.txt solely to avoid Gmail
attachment restrictions. Please rename it to repro.ps1 before running it.
The script creates a new local file:// repository and reproduces the problem
without using project source code, private URLs, a GUI client, an external
merge tool, a server hook, or an existing working copy.
Expected behavior: apply the line inside the function, or report a conflict.
Actual behavior: the line is silently placed after the function's "end"
statement.
The same fixture succeeds when the target file is normalized to CRLF before
the merge (run the script with -NormalizeBranchEol).
Could this be considered a mixed-EOL variant of SVN-3373? If so, I would be
happy to add the reproduction to that issue.
Regards,
Rinne Ohara
<#
Creates an isolated local SVN repository and reproduces a wrong-context merge.
No network access, project files, or existing SVN working copies are used.
#>
[CmdletBinding()]
param(
[string]$WorkRoot = (Join-Path $env:TEMP ("svn-mixed-eol-merge-repro-" +
[guid]::NewGuid().ToString("N"))),
[switch]$NormalizeBranchEol,
[string]$SvnExecutable = "svn",
[string]$SvnAdminExecutable = "svnadmin"
)
$ErrorActionPreference = "Stop";
function Invoke-Svn {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$Arguments)
& $script:szSvnCommand @Arguments;
if ($LASTEXITCODE -ne 0) {
throw "svn command failed with exit code ${LASTEXITCODE}: svn
$($Arguments -join ' ')";
}
}
function Write-Utf8NoBom {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string]$Text
)
[System.IO.File]::WriteAllText($Path, $Text,
[System.Text.UTF8Encoding]::new($false));
}
function Get-IndexOfLine {
param(
[Parameter(Mandatory = $true)][AllowEmptyString()][string[]]$Lines,
[Parameter(Mandatory = $true)][string]$Text,
[int]$StartIndex = 0
)
for ($nIndex = $StartIndex; $nIndex -lt $Lines.Length; $nIndex++) {
if ($Lines[$nIndex] -eq $Text) {
return $nIndex;
}
}
return -1;
}
try {
$script:szSvnCommand = (Get-Command $SvnExecutable -CommandType Application
-ErrorAction Stop).Source;
$script:szSvnAdminCommand = (Get-Command $SvnAdminExecutable -CommandType
Application -ErrorAction Stop).Source;
}
catch {
throw "Could not resolve svn or svnadmin. Pass -SvnExecutable and
-SvnAdminExecutable if they are not on PATH.";
}
if (Test-Path -LiteralPath $WorkRoot) {
throw "Refusing to overwrite existing path: $WorkRoot";
}
$null = New-Item -ItemType Directory -Path $WorkRoot;
$szRepoPath = Join-Path $WorkRoot "repo";
$szTrunkWc = Join-Path $WorkRoot "wc-trunk";
$szBranchWc = Join-Path $WorkRoot "wc-branch";
$szRepoUrl = (New-Object System.Uri($szRepoPath)).AbsoluteUri.TrimEnd('/');
$szFileName = "sample.lua";
$szTrunkFile = Join-Path $szTrunkWc $szFileName;
$szBranchFile = Join-Path $szBranchWc $szFileName;
$szCrlf = "`r`n";
$szLf = "`n";
# r2: A completely CRLF-terminated baseline.
$tbFunctionLines = @(
"function CustomerActionPrepareFinish()",
" self.nStartTime = GetTime();",
" self:_SwitchAIState(true);",
" self:StartUICountDown();",
" self:_AddActorEnterBuffStateToAll();",
"",
" Log(`"Prepare finished`");",
"end"
);
$szBaseText = (@("-- Fixture header", "") + $tbFunctionLines + @("", "--
Fixture trailer")) -join $szCrlf;
$szBaseText += $szCrlf;
# r4: The branch retains identical text but the function block is LF-terminated.
# The header/trailer intentionally remain CRLF-terminated, so this is a
mixed-EOL file.
$szMixedText = "-- Fixture header${szCrlf}${szCrlf}";
$szMixedText += ($tbFunctionLines -join $szLf) + $szLf;
$szMixedText += "${szCrlf}-- Fixture trailer${szCrlf}";
if ($NormalizeBranchEol) {
$szMixedText = $szBaseText;
}
# r5: The trunk inserts a call inside the CRLF version of the function.
$tbTrunkFunctionLines = @(
"function CustomerActionPrepareFinish()",
" self.nStartTime = GetTime();",
" self:_SwitchAIState(true);",
" self:StartUICountDown();",
" self:_AddActorEnterBuffStateToAll();",
" self:_ChangePlayersFightStateHelper(true);",
"",
" Log(`"Prepare finished`");",
"end"
);
$szTrunkText = (@("-- Fixture header", "") + $tbTrunkFunctionLines + @("", "--
Fixture trailer")) -join $szCrlf;
$szTrunkText += $szCrlf;
& $script:szSvnAdminCommand create $szRepoPath;
if ($LASTEXITCODE -ne 0) {
throw "svnadmin create failed with exit code $LASTEXITCODE.";
}
Invoke-Svn mkdir "${szRepoUrl}/trunk" "${szRepoUrl}/branches" -m "Create
fixture layout";
Invoke-Svn checkout "${szRepoUrl}/trunk" $szTrunkWc;
Write-Utf8NoBom -Path $szTrunkFile -Text $szBaseText;
Invoke-Svn add $szTrunkFile;
Invoke-Svn commit $szTrunkWc -m "Add CRLF baseline";
Invoke-Svn copy "${szRepoUrl}/trunk" "${szRepoUrl}/branches/release" -m "Create
release branch";
Invoke-Svn checkout "${szRepoUrl}/branches/release" $szBranchWc;
Write-Utf8NoBom -Path $szBranchFile -Text $szMixedText;
if (-not $NormalizeBranchEol) {
Invoke-Svn commit $szBranchWc -m "Make only the branch function block
LF-terminated";
}
Write-Utf8NoBom -Path $szTrunkFile -Text $szTrunkText;
Invoke-Svn commit $szTrunkWc -m "Insert helper call in trunk function";
$nMergeRevision = if ($NormalizeBranchEol) { 4 } else { 5 };
Invoke-Svn update $szBranchWc;
Set-Location $szBranchWc;
Invoke-Svn merge -c $nMergeRevision "${szRepoUrl}/trunk" .;
$tbResultLines = [System.IO.File]::ReadAllLines($szBranchFile);
$nFunctionLine = Get-IndexOfLine -Lines $tbResultLines -Text "function
CustomerActionPrepareFinish()";
$nFunctionEndLine = Get-IndexOfLine -Lines $tbResultLines -Text "end"
-StartIndex $nFunctionLine;
$nInsertedCallLine = Get-IndexOfLine -Lines $tbResultLines -Text "
self:_ChangePlayersFightStateHelper(true);";
if ($nFunctionLine -lt 0 -or $nFunctionEndLine -lt 0 -or $nInsertedCallLine -lt
0) {
throw "The expected function or inserted call was not found.";
}
Write-Output "SVN executable: $script:szSvnCommand";
Write-Output "SVN version: $(& $script:szSvnCommand --version --quiet)";
Write-Output "Fixture path: $WorkRoot";
Write-Output "Function starts at line $($nFunctionLine + 1); first end is line
$($nFunctionEndLine + 1); inserted call is line $($nInsertedCallLine + 1).";
if ($NormalizeBranchEol) {
if ($nInsertedCallLine -lt $nFunctionEndLine) {
Write-Output "CONTROL PASSED: CRLF normalization kept the inserted call
inside the function.";
exit 0;
}
throw "CONTROL FAILED: CRLF normalization did not keep the inserted call
inside the function.";
}
if ($nInsertedCallLine -gt $nFunctionEndLine) {
Write-Output "REPRODUCED: svn merge inserted the call after the function's
end statement.";
exit 0;
}
throw "NOT REPRODUCED: the inserted call remained inside the function.";