Skip to content

Instantly share code, notes, and snippets.

@HowardvanRooijen
Last active November 27, 2019 13:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save HowardvanRooijen/a4c4c463e1c27b8857cb to your computer and use it in GitHub Desktop.
Save HowardvanRooijen/a4c4c463e1c27b8857cb to your computer and use it in GitHub Desktop.
AutoFix-VisualStudioFiles.ps1 which formats and alphabetically organises elements in .config & .csproj files which commonly cause git merge conflicts
<#
.SYNOPSIS
Scans the solution folder (the parent of the .git folder) for all app.config, web.config and *.csproj files
and auto-formats them to minimise the possibility of getting merge conflicts based on the ordering of
elements within these files.
N.B. Use of this script is entirely at your own risk. We shall not be liable for any damage which may
result from using it.
.DESCRIPTION
app.config & web.config files - sorts appSettings elements by key, in alphabetic order, sorts
assemblyBinding.dependentAssembly elements alphabetically based on the assemblyIdentity.name
attribute
.csproj files - sorts appSettings elements by key, in alphabetic order, sorts Reference,
ProjectReference & Compile elements
.NOTES
File Name : AutoFix-VisualStudioFiles.ps1
Author : Howard van Rooijen (@HowardvRooijen)
Requires : PowerShell v3
.LINK
#>
Function AutoFix-WebConfig([string] $rootDirectory)
{
$files = Get-ChildItem -Path $rootDirectory -Filter web.config -Recurse
return Scan-ConfigFiles($files)
}
Function AutoFix-AppConfig([string] $rootDirectory)
{
$files = Get-ChildItem -Path $rootDirectory -Filter app.config -Recurse
return Scan-ConfigFiles($files)
}
Function Scan-ConfigFiles([System.IO.FileInfo[]] $files)
{
$modifiedfiles = @()
foreach($file in $files)
{
$original = [xml] (Get-Content $file.FullName)
$workingCopy = $original.Clone()
if ($workingCopy.configuration.appSettings -ne $null){
$sorted = $workingCopy.configuration.appSettings.add | sort { [string]$_.key }
$lastChild = $sorted[-1]
$sorted[0..($sorted.Length-2)] | foreach {$workingCopy.configuration.appSettings.InsertBefore($_, $lastChild)} | Out-Null
}
if ($workingCopy.configuration.runtime.assemblyBinding -ne $null){
$sorted = $workingCopy.configuration.runtime.assemblyBinding.dependentAssembly | sort { [string]$_.assemblyIdentity.name }
$lastChild = $sorted[-1]
$sorted[0..($sorted.Length-2)] | foreach {$workingCopy.configuration.runtime.assemblyBinding.InsertBefore($_,$lastChild)} | Out-Null
}
$differencesCount = (Compare-Object -ReferenceObject (Select-Xml -Xml $original -XPath "//*") -DifferenceObject (Select-Xml -Xml $workingCopy -XPath "//*")).Length
if ($differencesCount -ne 0)
{
$workingCopy.Save($file.FullName) | Out-Null
$modifiedfiles += $file.FullName
}
}
return $modifiedfiles
}
Function AutoFix-CsProj([string] $rootDirectory)
{
$files = Get-ChildItem -Path $rootDirectory -Filter *.csproj -Recurse
$modifiedfiles = @()
foreach($file in $files)
{
$original = [xml] (Get-Content $file.FullName)
$workingCopy = $original.Clone()
foreach($itemGroup in $workingCopy.Project.ItemGroup){
# Sort the reference elements
if ($itemGroup.Reference -ne $null){
$sorted = $itemGroup.Reference | sort { [string]$_.Include }
$itemGroup.RemoveAll() | Out-Null
foreach($item in $sorted){
$itemGroup.AppendChild($item) | Out-Null
}
}
# Sort the compile elements
if ($itemGroup.Compile -ne $null){
$sorted = $itemGroup.Compile | sort { [string]$_.Include }
$itemGroup.RemoveAll() | Out-Null
foreach($item in $sorted){
$itemGroup.AppendChild($item) | Out-Null
}
}
# Sort the project references elements
if ($itemGroup.ProjectReference -ne $null){
$sorted = $itemGroup.ProjectReference | sort { [string]$_.Include }
$itemGroup.RemoveAll() | Out-Null
foreach($item in $sorted){
$itemGroup.AppendChild($item) | Out-Null
}
}
}
$differencesCount = (Compare-Object -ReferenceObject (Select-Xml -Xml $original -XPath "//*") -DifferenceObject (Select-Xml -Xml $workingCopy -XPath "//*")).Length
if ($differencesCount -ne 0)
{
$workingCopy.Save($file.FullName) | Out-Null
$modifiedfiles += $file.FullName
}
}
return $modifiedfiles
}
$rootDirectory = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "\..\..\"
$exitCode = 0;
$changedfiles = @()
$changedfiles += AutoFix-AppConfig($rootDirectory)
$changedfiles += AutoFix-CsProj($rootDirectory)
$changedfiles += AutoFix-WebConfig($rootDirectory)
if ($changedfiles.Count -gt 0)
{
Write-Host "=== endjin git hooks ==="
Write-Host "The following files have been auto-formatted"
Write-Host "to reduce the likelyhood of merge conflicts:"
foreach($file in $changedfiles)
{
Write-Host $file
}
$exitCode = 1;
}
exit $exitcode
@hugoj-goncalves
Copy link

Entries like this

-    <ProjectReference Include="..\Folder\Project.csproj">
-      <Project>{BC1989BA-C301-4BD3-BOB7-DI3D040F3A55}</Project>
-      <Name>Project</Name>
-    </ProjectReference>

gets removed after running this script.

I think this exception that is getting thrown is related, I never really needed to use powershell until now.
Don't really know how to debug it. (I'm going to try to fix it, but if u could help I'd appreciate ;x)

Exceção ao chamar "InsertBefore" com "2" argumento(s): "Referência de objeto não definida para uma instância de um
objeto."
No C:\workspace\Conecta\.git\hooks\AutoFix-VisualStudioFiles.ps1:53 caractere:55
+ ...  | foreach {$workingCopy.configuration.appSettings.InsertBefore($_, $ ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

UPDATE:
Actually the exception seems to be about another thing.
I believe this lines shouldn't be there, right?
https://gist.github.com/HowardvanRooijen/a4c4c463e1c27b8857cb#file-autofix-visualstudiofiles-ps1-L91
https://gist.github.com/HowardvanRooijen/a4c4c463e1c27b8857cb#file-autofix-visualstudiofiles-ps1-L103
https://gist.github.com/HowardvanRooijen/a4c4c463e1c27b8857cb#file-autofix-visualstudiofiles-ps1-L115

UPDATE2:
I created a Fork and made a few changes to your script.
Seems to be working fine now. ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment